{"thread":{"id":"61132","subject":"[PATCH 0/6] Make trailer_info struct private (plus sequencer cleanup)","startedAt":"2024-03-16T06:27:11Z","lastAt":"2024-05-13T15:13:30Z","messageCount":66,"participants":["Linus Arver via GitGitGadget","Junio C Hamano","Linus Arver","Christian Couder","Phillip Wood"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"490765","messageId":"pull.1696.git.1710570428.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":null,"subject":"[PATCH 0/6] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-16T06:27:02Z","receivedAt":"2024-03-16T06:27:11Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"NOTE: This series is based on the la/format-trailer-info topic branch (see\nits discussion at [1]).\n\nThis series is based on the initial series [2], notably the v4 version of\npatches 17-20 as suggested by Christian [3]. This version addresses the\nreview comments for those patches, namely the splitting up of Patch 19 there\ninto 3 separate patches [4] (as Patches 03-05 here) .\n\nThe central idea is to make the trailer_info struct private (that is, move\nits definition from trailer.h to trailer.c) --- aka the \"pimpl\" idiom. See\nthe detailed commit message for Patch 05 for the motivation behind the\nchange.\n\nPatch 02 makes sequencer.c a well-behaved trailer API consumer, by making\nuse of the trailer iterator. Patch 01 prepares us for Patch 02. Patch 06\nslightly reduces the weight of the API by removing (from the API surface) an\nunused function.\n\n[1]\nhttps://lore.kernel.org/git/pull.1694.git.1710485706.gitgitgadget@gmail.com/\n[2]\nhttps://lore.kernel.org/git/pull.1632.v4.git.1707196348.gitgitgadget@gmail.com/\n[3]\nhttps://lore.kernel.org/git/CAP8UFD08F0V13X0+CJ1uhMPzPWVMs2okGVMJch0DkQg5M3BWLA@mail.gmail.com/\n[4]\nhttps://lore.kernel.org/git/CAP8UFD1twELGKvvesxgCrZrypKZpgSt04ira3mvurG1UbpDfxQ@mail.gmail.com/\n\nLinus Arver (6):\n  trailer: teach iterator about non-trailer lines\n  sequencer: use the trailer iterator\n  interpret-trailers: access trailer_info with new helpers\n  trailer: make parse_trailers() return trailer_info pointer\n  trailer: make trailer_info struct private\n  trailer: retire trailer_info_get() from API\n\n builtin/interpret-trailers.c |  12 +--\n sequencer.c                  |  27 +++---\n trailer.c                    | 161 ++++++++++++++++++++++-------------\n trailer.h                    |  46 ++++------\n 4 files changed, 137 insertions(+), 109 deletions(-)\n\n\nbase-commit: 3452d173241c8b87ecdd67f91f594cb14327e394\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1696%2Flistx%2Ftrailer-api-part-3-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1696/listx/trailer-api-part-3-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1696\n-- \ngitgitgadget\n"},{"id":"490766","messageId":"32ad0397737375b826d8af893e14a04628e9e803.1710570428.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"[PATCH 1/6] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-16T06:27:03Z","receivedAt":"2024-03-16T06:27:12Z","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 commit 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 even with this\ncommit).\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 3e4dab9c065..4700c441442 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1146,17 +1146,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":"490767","messageId":"dc873c3b82093a6411e34369fc516242f1bb0d6c.1710570428.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"[PATCH 2/6] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-16T06:27:04Z","receivedAt":"2024-03-16T06:27:13Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\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 (char **trailers)\nin trailer_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 ea1441e6174..4c1f6c675e7 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":"490768","messageId":"872e67286c8407da28ae704b9900ab63b7c407c1.1710570428.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"[PATCH 3/6] interpret-trailers: access trailer_info with new helpers","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-16T06:27:05Z","receivedAt":"2024-03-16T06:27:15Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nInstead of directly accessing trailer_info members, access them\nindirectly through new helper functions exposed by the trailer API.\n\nThis is the first of two preparatory commits which will allow us to\nuse the so-called \"pimpl\" (pointer to implementation) idiom for the\ntrailer API, by making the trailer_info struct private to the trailer\nimplementation (and thus hidden from the API).\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 12 ++++++------\n trailer.c                    | 21 +++++++++++++++++++++\n trailer.h                    |  4 ++++\n 3 files changed, 31 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 11f4ce9e4a2..f3240682e35 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 = trailer_info_new();\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+\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+\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 4700c441442..95b4c9b8f19 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,6 +952,12 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+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@@ -1000,6 +1006,21 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \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 void trailer_info_get(const struct process_trailer_options *opts,\n \t\t      const char *str,\n \t\t      struct trailer_info *info)\ndiff --git a/trailer.h b/trailer.h\nindex ebafa3657e4..9ac4be853c5 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -97,6 +97,10 @@ void parse_trailers(const struct process_trailer_options *,\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+struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n-- \ngitgitgadget\n\n"},{"id":"490769","messageId":"c55ae2cbda9aea15f9f3c0bb3122aa6535624b24.1710570428.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"[PATCH 4/6] trailer: make parse_trailers() return trailer_info pointer","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-16T06:27:06Z","receivedAt":"2024-03-16T06:27:16Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is the second and final preparatory commit for making the\ntrailer_info struct private to the trailer implementation.\n\nMake trailer_info_get() do the actual work of allocating a new\ntrailer_info struct, and return a pointer to it. Because\nparse_trailers() wraps around trailer_info_get(), it too can return this\npointer to the caller. From the trailer API user's perspective, the call\nto trailer_info_new() can be replaced with parse_trailers(); do so in\ninterpret-trailers.\n\nBecause trailer_info_new() is no longer called by interpret-trailers,\nremove this function from the trailer API.\n\nWith this change, we no longer allocate trailer_info on the stack ---\nall uses of it are via a pointer where the actual data is always\nallocated at runtime through trailer_info_new(). Make\ntrailer_info_release() free this dynamically allocated memory.\n\nFinally, due to the way the function signatures of parse_trailers() and\ntrailer_info_get() have changed, update the callsites in\nformat_trailers_from_commit() and trailer_iterator_init() accordingly.\n\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                    | 41 +++++++++++++++++++-----------------\n trailer.h                    | 17 ++++++---------\n 3 files changed, 31 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f3240682e35..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 = trailer_info_new();\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,7 +151,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-\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)\ndiff --git a/trailer.c b/trailer.c\nindex 95b4c9b8f19..9179dd802c6 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,7 +952,7 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n-struct trailer_info *trailer_info_new(void)\n+static struct trailer_info *trailer_info_new(void)\n {\n \tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n \treturn info;\n@@ -962,16 +962,16 @@ struct trailer_info *trailer_info_new(void)\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@@ -995,6 +995,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@@ -1021,10 +1023,10 @@ int blank_line_before_trailer_block(struct trailer_info *info)\n \treturn info->blank_line_before_trailer;\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+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@@ -1063,6 +1065,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@@ -1071,6 +1075,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@@ -1138,21 +1143,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(trailer_objects);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(opts, &info, msg, &trailer_objects);\n+\tstruct trailer_info *info = parse_trailers(opts, 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 \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, &trailer_objects, out);\n \n \tfree_trailers(&trailer_objects);\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@@ -1161,14 +1164,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@@ -1185,7 +1188,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 9ac4be853c5..b32213a9e23 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -89,18 +89,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-\n-void trailer_info_get(const struct process_trailer_options *,\n-\t\t      const char *str,\n-\t\t      struct trailer_info *);\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 int blank_line_before_trailer_block(struct trailer_info *);\n-struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -142,7 +139,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":"490770","messageId":"cf59dee506441a11b2b295d046a7bf255ca7c1cf.1710570428.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"[PATCH 5/6] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-16T06:27:07Z","receivedAt":"2024-03-16T06:27:18Z","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\n(The disadvantages have already been observed in the two preparatory\ncommits that precede this one.) This commit believes that the benefits\noutweigh the disadvantages for designing 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 commit 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: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 21 +++++++++++++++++++++\n trailer.h | 23 ++---------------------\n 2 files changed, 23 insertions(+), 21 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 9179dd802c6..6167b707ae0 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;\ndiff --git a/trailer.h b/trailer.h\nindex b32213a9e23..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-- \ngitgitgadget\n\n"},{"id":"490771","messageId":"19de7c64171668503b3676bee9fa9e8660671b17.1710570428.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"[PATCH 6/6] trailer: retire trailer_info_get() from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-03-16T06:27:08Z","receivedAt":"2024-03-16T06:27:18Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nMake trailer_info_get() \"static\" to be file-scoped to trailer.c, because\nno one outside of trailer.c uses it. Remove its declaration from\n<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 6167b707ae0..33b6aa7e8bd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -979,6 +979,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@@ -1044,52 +1090,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"},{"id":"490788","messageId":"xmqqjzm2m7h2.fsf@gitster.g","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/6] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-16T17:06:49Z","receivedAt":"2024-03-16T17:06:54Z","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> NOTE: This series is based on the la/format-trailer-info topic branch (see\n> its discussion at [1]).\n\nFolks, a quick review of the base topic is highly appreciated.  Not\nhaving much review to talk about in [1] makes it a bit premature to\nbuild another series on top of it.\n\nThanks.\n"},{"id":"491613","messageId":"xmqqjzlo7ixe.fsf@gitster.g","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/6] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-26T22:00:13Z","receivedAt":"2024-03-26T22:00: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> NOTE: This series is based on the la/format-trailer-info topic branch (see\n> its discussion at [1]).\n\nThis unfortunately depends on another series, which has seen no\nreviews after 10 days X-<.  It did not help that this was sent\nalmost immediately after that unreviewed series that it depends on.\n\nAny takers?  There must be some folks who know the trailer code very\nwell, no?\n\nThanks.\n"},{"id":"493185","messageId":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.git.1710570428.gitgitgadget@gmail.com","subject":"[PATCH v2 0/8] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:25Z","receivedAt":"2024-04-19T05:22:37Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"NOTE: This series is based on the la/format-trailer-info topic branch (see\nits discussion at [1]).\n\nThis series is based on the initial series [2], notably the v4 version of\npatches 17-20 as suggested by Christian [3]. This version addresses the\nreview comments for those patches, namely the splitting up of Patch 19 there\ninto 3 separate patches [4] (as Patches 05-07 here) .\n\nThe central idea is to make the trailer_info struct private (that is, move\nits definition from trailer.h to trailer.c) --- aka the \"pimpl\" idiom. See\nthe detailed commit message for Patch 07 for the motivation behind the\nchange.\n\nPatch 04 makes sequencer.c a well-behaved trailer API consumer, by making\nuse of the trailer iterator. Patch 03 prepares us for Patch 04. Patch 08\nslightly reduces the weight of the API by removing (from the API surface) an\nunused function.\n\n\nNotable changes in v2\n=====================\n\n * Add unit tests at the beginning of the series (Patches 01 and 02) and use\n   it to verify that the other edge cases remain unchanged when we add the\n   \"raw\" member (Patch 03)\n\n[1]\nhttps://lore.kernel.org/git/pull.1694.git.1710485706.gitgitgadget@gmail.com/\n[2]\nhttps://lore.kernel.org/git/pull.1632.v4.git.1707196348.gitgitgadget@gmail.com/\n[3]\nhttps://lore.kernel.org/git/CAP8UFD08F0V13X0+CJ1uhMPzPWVMs2okGVMJch0DkQg5M3BWLA@mail.gmail.com/\n[4]\nhttps://lore.kernel.org/git/CAP8UFD1twELGKvvesxgCrZrypKZpgSt04ira3mvurG1UbpDfxQ@mail.gmail.com/\n\nLinus Arver (8):\n  Makefile: sort UNIT_TEST_PROGRAMS\n  trailer: add unit tests for trailer iterator\n  trailer: teach iterator about non-trailer lines\n  sequencer: use the trailer iterator\n  interpret-trailers: access trailer_info with new helpers\n  trailer: make parse_trailers() return trailer_info pointer\n  trailer: make trailer_info struct private\n  trailer: retire trailer_info_get() from API\n\n Makefile                     |   5 +-\n builtin/interpret-trailers.c |  12 +--\n sequencer.c                  |  27 +++---\n t/unit-tests/t-trailer.c     | 181 +++++++++++++++++++++++++++++++++++\n trailer.c                    | 161 +++++++++++++++++++------------\n trailer.h                    |  46 ++++-----\n 6 files changed, 321 insertions(+), 111 deletions(-)\n create mode 100644 t/unit-tests/t-trailer.c\n\n\nbase-commit: 3452d173241c8b87ecdd67f91f594cb14327e394\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1696%2Flistx%2Ftrailer-api-part-3-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1696/listx/trailer-api-part-3-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1696\n\nRange-diff vs v1:\n\n -:  ----------- > 1:  b6a1304f8ae Makefile: sort UNIT_TEST_PROGRAMS\n -:  ----------- > 2:  e1fa05143ac trailer: add unit tests for trailer iterator\n 1:  32ad0397737 ! 3:  5520a98e296 trailer: teach iterator about non-trailer lines\n     @@ Commit message\n      \n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n     + ## t/unit-tests/t-trailer.c ##\n     +@@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n     + \t\t\t\"not a trailer line\\n\"\n     + \t\t\t\"not a trailer line\\n\"\n     + \t\t\t\"Signed-off-by: x\\n\",\n     +-\t\t\t1\n     ++\t\t\t/*\n     ++\t\t\t * Even though there is only really 1 real \"trailer\"\n     ++\t\t\t * (Signed-off-by), we still have 4 trailer objects\n     ++\t\t\t * because we still want to iterate through the entire\n     ++\t\t\t * block.\n     ++\t\t\t */\n     ++\t\t\t4\n     + \t\t},\n     + \t\t{\n     + \t\t\t\"with non-trailer lines (one too many) in trailer block\",\n     +\n       ## trailer.c ##\n      @@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n       \n 2:  dc873c3b820 = 4:  84897cf5c83 sequencer: use the trailer iterator\n 3:  872e67286c8 = 5:  e961d49cd40 interpret-trailers: access trailer_info with new helpers\n 4:  c55ae2cbda9 = 6:  093f68f3658 trailer: make parse_trailers() return trailer_info pointer\n 5:  cf59dee5064 = 7:  0e9ae049b88 trailer: make trailer_info struct private\n 6:  19de7c64171 = 8:  eca77a1a462 trailer: retire trailer_info_get() from API\n\n-- \ngitgitgadget\n"},{"id":"493186","messageId":"b6a1304f8aefdfebe2eefc0e07460d6d8dbb004d.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 1/8] Makefile: sort UNIT_TEST_PROGRAMS","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:26Z","receivedAt":"2024-04-19T05:22:38Z","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 Makefile | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 4e255c81f22..d3a3f16f076 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1343,10 +1343,10 @@ THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n UNIT_TEST_PROGRAMS += t-basic\n-UNIT_TEST_PROGRAMS += t-mem-pool\n-UNIT_TEST_PROGRAMS += t-strbuf\n UNIT_TEST_PROGRAMS += t-ctype\n+UNIT_TEST_PROGRAMS += t-mem-pool\n UNIT_TEST_PROGRAMS += t-prio-queue\n+UNIT_TEST_PROGRAMS += t-strbuf\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n-- \ngitgitgadget\n\n"},{"id":"493187","messageId":"e1fa05143ac63e8fe8dbc8ccb76a89b7a008c412.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 2/8] trailer: add unit tests for trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:27Z","receivedAt":"2024-04-19T05:22:40Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nTest the number of trailers found by the iterator (to be more precise,\nthe parsing mechanism which the iterator just walks over) when given\nsome some arbitrary log message.\n\nWe test the iterator because it is a public interface function exposed\nby the trailer API (we generally don't want to test internal\nimplementation details which are, unlike the API, subject to drastic\nchanges).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n Makefile                 |   1 +\n t/unit-tests/t-trailer.c | 175 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 176 insertions(+)\n create mode 100644 t/unit-tests/t-trailer.c\n\ndiff --git a/Makefile b/Makefile\nindex d3a3f16f076..5418ddd03be 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1347,6 +1347,7 @@ UNIT_TEST_PROGRAMS += t-ctype\n UNIT_TEST_PROGRAMS += t-mem-pool\n UNIT_TEST_PROGRAMS += t-prio-queue\n UNIT_TEST_PROGRAMS += t-strbuf\n+UNIT_TEST_PROGRAMS += t-trailer\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nnew file mode 100644\nindex 00000000000..147a51b66b9\n--- /dev/null\n+++ b/t/unit-tests/t-trailer.c\n@@ -0,0 +1,175 @@\n+#include \"test-lib.h\"\n+#include \"trailer.h\"\n+\n+static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n+{\n+\tstruct trailer_iterator iter;\n+\tsize_t i = 0;\n+\n+\ttrailer_iterator_init(&iter, msg);\n+\twhile (trailer_iterator_advance(&iter)) {\n+\t\ti++;\n+\t}\n+\ttrailer_iterator_release(&iter);\n+\n+\tcheck_uint(i, ==, num_expected_trailers);\n+}\n+\n+static void run_t_trailer_iterator(void)\n+{\n+\tstatic struct test_cases {\n+\t\tconst char *name;\n+\t\tconst char *msg;\n+\t\tsize_t num_expected_trailers;\n+\t} tc[] = {\n+\t\t{\n+\t\t\t\"empty input\",\n+\t\t\t\"\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"no newline at beginning\",\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"newline at beginning\",\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t3\n+\t\t},\n+\t\t{\n+\t\t\t\"without body text\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t3\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, without divider\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t4\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, without divider (second trailer block)\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * Because this is the last trailer block, it takes\n+\t\t\t * precedence over the first one encountered above.\n+\t\t\t */\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t2\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, with divider\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"---\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This trailer still counts because the iterator\n+\t\t\t * always ignores the divider.\n+\t\t\t */\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t1\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines in trailer block\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * Even though this trailer block has a non-trailer line\n+\t\t\t * in it, it's still a valid trailer block because it's\n+\t\t\t * at least 25% trailers and is Git-generated.\n+\t\t\t */\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t1\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines (one too many) in trailer block\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This block has only 20% trailers, so it's below the\n+\t\t\t * 25% threshold.\n+\t\t\t */\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines (only 1) in trailer block, but no Git-generated trailers\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This block has only 1 non-trailer out of 10 (IOW, 90%\n+\t\t\t * trailers) but is not considered a trailer because the\n+\t\t\t * 25% threshold only applies to cases where there was a\n+\t\t\t * Git-generated trailer (see git_generated_prefixes[]\n+\t\t\t * in trailer.c).\n+\t\t\t */\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"not a trailer line\\n\",\n+\t\t\t0\n+\t\t},\n+\t};\n+\n+\tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n+\t\tTEST(t_trailer_iterator(tc[i].msg,\n+\t\t\t\t\ttc[i].num_expected_trailers),\n+\t\t     \"%s\", tc[i].name);\n+\t}\n+}\n+\n+int cmd_main(int argc, const char **argv)\n+{\n+\trun_t_trailer_iterator();\n+\treturn test_done();\n+}\n-- \ngitgitgadget\n\n"},{"id":"493188","messageId":"5520a98e2967401977dbc16f0d1ca77a8001178e.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 3/8] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:28Z","receivedAt":"2024-04-19T05:22:40Z","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 commit 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 even with this\ncommit).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n t/unit-tests/t-trailer.c |  8 +++++++-\n trailer.c                | 12 +++++-------\n trailer.h                |  8 ++++++++\n 3 files changed, 20 insertions(+), 8 deletions(-)\n\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nindex 147a51b66b9..50c696b969b 100644\n--- a/t/unit-tests/t-trailer.c\n+++ b/t/unit-tests/t-trailer.c\n@@ -119,7 +119,13 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t1\n+\t\t\t/*\n+\t\t\t * Even though there is only really 1 real \"trailer\"\n+\t\t\t * (Signed-off-by), we still have 4 trailer objects\n+\t\t\t * because we still want to iterate through the entire\n+\t\t\t * block.\n+\t\t\t */\n+\t\t\t4\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines (one too many) in trailer block\",\ndiff --git a/trailer.c b/trailer.c\nindex 3e4dab9c065..4700c441442 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1146,17 +1146,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":"493189","messageId":"84897cf5c83eb67c023603016b49fb7b56870aa3.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 4/8] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:29Z","receivedAt":"2024-04-19T05:22:41Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\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 (char **trailers)\nin trailer_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 ea1441e6174..4c1f6c675e7 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":"493190","messageId":"e961d49cd40f54f63df64ab0e42b9632d35d7a20.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 5/8] interpret-trailers: access trailer_info with new helpers","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:30Z","receivedAt":"2024-04-19T05:22:43Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nInstead of directly accessing trailer_info members, access them\nindirectly through new helper functions exposed by the trailer API.\n\nThis is the first of two preparatory commits which will allow us to\nuse the so-called \"pimpl\" (pointer to implementation) idiom for the\ntrailer API, by making the trailer_info struct private to the trailer\nimplementation (and thus hidden from the API).\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 12 ++++++------\n trailer.c                    | 21 +++++++++++++++++++++\n trailer.h                    |  4 ++++\n 3 files changed, 31 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 11f4ce9e4a2..f3240682e35 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 = trailer_info_new();\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+\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+\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 4700c441442..95b4c9b8f19 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,6 +952,12 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+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@@ -1000,6 +1006,21 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \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 void trailer_info_get(const struct process_trailer_options *opts,\n \t\t      const char *str,\n \t\t      struct trailer_info *info)\ndiff --git a/trailer.h b/trailer.h\nindex ebafa3657e4..9ac4be853c5 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -97,6 +97,10 @@ void parse_trailers(const struct process_trailer_options *,\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+struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n-- \ngitgitgadget\n\n"},{"id":"493191","messageId":"0e9ae049b8861fecf49c097e8d52e734f7a9c9b3.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 7/8] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:32Z","receivedAt":"2024-04-19T05:22:45Z","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\n(The disadvantages have already been observed in the two preparatory\ncommits that precede this one.) This commit believes that the benefits\noutweigh the disadvantages for designing 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 commit 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: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c | 21 +++++++++++++++++++++\n trailer.h | 23 ++---------------------\n 2 files changed, 23 insertions(+), 21 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 9179dd802c6..6167b707ae0 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;\ndiff --git a/trailer.h b/trailer.h\nindex b32213a9e23..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-- \ngitgitgadget\n\n"},{"id":"493192","messageId":"093f68f365801bc8801f29dc3e6eaa9b07fcd57d.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 6/8] trailer: make parse_trailers() return trailer_info pointer","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:31Z","receivedAt":"2024-04-19T05:22:45Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is the second and final preparatory commit for making the\ntrailer_info struct private to the trailer implementation.\n\nMake trailer_info_get() do the actual work of allocating a new\ntrailer_info struct, and return a pointer to it. Because\nparse_trailers() wraps around trailer_info_get(), it too can return this\npointer to the caller. From the trailer API user's perspective, the call\nto trailer_info_new() can be replaced with parse_trailers(); do so in\ninterpret-trailers.\n\nBecause trailer_info_new() is no longer called by interpret-trailers,\nremove this function from the trailer API.\n\nWith this change, we no longer allocate trailer_info on the stack ---\nall uses of it are via a pointer where the actual data is always\nallocated at runtime through trailer_info_new(). Make\ntrailer_info_release() free this dynamically allocated memory.\n\nFinally, due to the way the function signatures of parse_trailers() and\ntrailer_info_get() have changed, update the callsites in\nformat_trailers_from_commit() and trailer_iterator_init() accordingly.\n\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                    | 41 +++++++++++++++++++-----------------\n trailer.h                    | 17 ++++++---------\n 3 files changed, 31 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f3240682e35..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 = trailer_info_new();\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,7 +151,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-\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)\ndiff --git a/trailer.c b/trailer.c\nindex 95b4c9b8f19..9179dd802c6 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,7 +952,7 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n-struct trailer_info *trailer_info_new(void)\n+static struct trailer_info *trailer_info_new(void)\n {\n \tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n \treturn info;\n@@ -962,16 +962,16 @@ struct trailer_info *trailer_info_new(void)\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@@ -995,6 +995,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@@ -1021,10 +1023,10 @@ int blank_line_before_trailer_block(struct trailer_info *info)\n \treturn info->blank_line_before_trailer;\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+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@@ -1063,6 +1065,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@@ -1071,6 +1075,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@@ -1138,21 +1143,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(trailer_objects);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(opts, &info, msg, &trailer_objects);\n+\tstruct trailer_info *info = parse_trailers(opts, 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 \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, &trailer_objects, out);\n \n \tfree_trailers(&trailer_objects);\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@@ -1161,14 +1164,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@@ -1185,7 +1188,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 9ac4be853c5..b32213a9e23 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -89,18 +89,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-\n-void trailer_info_get(const struct process_trailer_options *,\n-\t\t      const char *str,\n-\t\t      struct trailer_info *);\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 int blank_line_before_trailer_block(struct trailer_info *);\n-struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -142,7 +139,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":"493193","messageId":"eca77a1a462a624f988fc50f7588d831678f34f4.1713504153.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v2 8/8] trailer: retire trailer_info_get() from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-19T05:22:33Z","receivedAt":"2024-04-19T05:22:46Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nMake trailer_info_get() \"static\" to be file-scoped to trailer.c, because\nno one outside of trailer.c uses it. Remove its declaration from\n<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 6167b707ae0..33b6aa7e8bd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -979,6 +979,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@@ -1044,52 +1090,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"},{"id":"493194","messageId":"owly8r19or1v.fsf@fine.c.googlers.com","threadId":"61132","inReplyTo":"e1fa05143ac63e8fe8dbc8ccb76a89b7a008c412.1713504153.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/8] trailer: add unit tests for trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-04-19T05:33:00Z","receivedAt":"2024-04-19T05:33:02Z","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> +\t\t{\n> +\t\t\t\"with non-trailer lines in trailer block\",\n> +\t\t\t\"subject: foo bar\\n\"\n> +\t\t\t\"\\n\"\n> +\t\t\t/*\n> +\t\t\t * Even though this trailer block has a non-trailer line\n> +\t\t\t * in it, it's still a valid trailer block because it's\n> +\t\t\t * at least 25% trailers and is Git-generated.\n> +\t\t\t */\n \nIn the next reroll (sometime next week?), I should put the\n\n    ... (see git_generated_prefixes[] in trailer.c).\n\ncomment up here (where we first mention \"Git-generated\" trailers)\ninstead of down in the last test case below.\n\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"Signed-off-by: x\\n\",\n> +\t\t\t1\n> +\t\t},\n> +\t\t{\n> +\t\t\t\"with non-trailer lines (one too many) in trailer block\",\n> +\t\t\t\"subject: foo bar\\n\"\n> +\t\t\t\"\\n\"\n> +\t\t\t/*\n> +\t\t\t * This block has only 20% trailers, so it's below the\n> +\t\t\t * 25% threshold.\n> +\t\t\t */\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"Signed-off-by: x\\n\",\n> +\t\t\t0\n> +\t\t},\n> +\t\t{\n> +\t\t\t\"with non-trailer lines (only 1) in trailer block, but no Git-generated trailers\",\n> +\t\t\t\"subject: foo bar\\n\"\n> +\t\t\t\"\\n\"\n> +\t\t\t/*\n> +\t\t\t * This block has only 1 non-trailer out of 10 (IOW, 90%\n> +\t\t\t * trailers) but is not considered a trailer because the\n> +\t\t\t * 25% threshold only applies to cases where there was a\n> +\t\t\t * Git-generated trailer (see git_generated_prefixes[]\n> +\t\t\t * in trailer.c).\n> +\t\t\t */\n> +\t\t\t\"Reviewed-by: x\\n\"\n> +\t\t\t\"Reviewed-by: x\\n\"\n> +\t\t\t\"Reviewed-by: x\\n\"\n> +\t\t\t\"Helped-by: x\\n\"\n> +\t\t\t\"Helped-by: x\\n\"\n> +\t\t\t\"Helped-by: x\\n\"\n> +\t\t\t\"Acked-by: x\\n\"\n> +\t\t\t\"Acked-by: x\\n\"\n> +\t\t\t\"Acked-by: x\\n\"\n> +\t\t\t\"not a trailer line\\n\",\n> +\t\t\t0\n> +\t\t},\n> +\t};\n"},{"id":"493195","messageId":"owly5xwdoqvm.fsf@fine.c.googlers.com","threadId":"61132","inReplyTo":"xmqqjzlo7ixe.fsf@gitster.g","subject":"Re: [PATCH 0/6] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-04-19T05:36:45Z","receivedAt":"2024-04-19T05:36:47Z","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>> NOTE: This series is based on the la/format-trailer-info topic branch (see\n>> its discussion at [1]).\n>\n> This unfortunately depends on another series, which has seen no\n> reviews after 10 days X-<.  It did not help that this was sent\n> almost immediately after that unreviewed series that it depends on.\n>\n> Any takers?  There must be some folks who know the trailer code very\n> well, no?\n>\n\nI've added some unit test cases in v2 Patch 02 to make this series a bit\nmore appealing for reviewers. Cheers.\n"},{"id":"493232","messageId":"owlya5lp5gws.fsf@fine.c.googlers.com","threadId":"61132","inReplyTo":"e1fa05143ac63e8fe8dbc8ccb76a89b7a008c412.1713504153.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/8] trailer: add unit tests for trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-04-19T18:46:59Z","receivedAt":"2024-04-19T18:47:01Z","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> [...]\n> \n> +\t\t{\n> +\t\t\t\"with non-trailer lines (only 1) in trailer block, but no Git-generated trailers\",\n> +\t\t\t\"subject: foo bar\\n\"\n> +\t\t\t\"\\n\"\n> +\t\t\t/*\n> +\t\t\t * This block has only 1 non-trailer out of 10 (IOW, 90%\n> +\t\t\t * trailers) but is not considered a trailer because the\n\ns/a trailer/a trailer block\n\n> +\t\t\t * 25% threshold only applies to cases where there was a\n> +\t\t\t * Git-generated trailer (see git_generated_prefixes[]\n> +\t\t\t * in trailer.c).\n> +\t\t\t */\n> +\t\t\t\"Reviewed-by: x\\n\"\n> +\t\t\t\"Reviewed-by: x\\n\"\n> +\t\t\t\"Reviewed-by: x\\n\"\n> +\t\t\t\"Helped-by: x\\n\"\n> +\t\t\t\"Helped-by: x\\n\"\n> +\t\t\t\"Helped-by: x\\n\"\n> +\t\t\t\"Acked-by: x\\n\"\n> +\t\t\t\"Acked-by: x\\n\"\n> +\t\t\t\"Acked-by: x\\n\"\n> +\t\t\t\"not a trailer line\\n\",\n> +\t\t\t0\n> +\t\t},\n"},{"id":"493239","messageId":"xmqq5xwd58b9.fsf@gitster.g","threadId":"61132","inReplyTo":"e1fa05143ac63e8fe8dbc8ccb76a89b7a008c412.1713504153.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/8] trailer: add unit tests for trailer iterator","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-19T21:52:42Z","receivedAt":"2024-04-19T21:52:48Z","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> +UNIT_TEST_PROGRAMS += t-trailer\n>  UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n>  UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n>  UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n\nTotally offtopic, but does it bother folks who are interested in\nadding more unit tests that they do not seem to interact very well\nwith GIT_SKIP_TESTS environment variable?\n\n> diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\n> new file mode 100644\n> index 00000000000..147a51b66b9\n> --- /dev/null\n> +++ b/t/unit-tests/t-trailer.c\n> @@ -0,0 +1,175 @@\n> +#include \"test-lib.h\"\n> +#include \"trailer.h\"\n> +\n> +static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n> +{\n> +\tstruct trailer_iterator iter;\n> +\tsize_t i = 0;\n> +\n> +\ttrailer_iterator_init(&iter, msg);\n> +\twhile (trailer_iterator_advance(&iter)) {\n> +\t\ti++;\n> +\t}\n\nUnnecessary {braces} around a single-statement block?\n\n> +\ttrailer_iterator_release(&iter);\n> +\n> +\tcheck_uint(i, ==, num_expected_trailers);\n> +}\n> +\n> +static void run_t_trailer_iterator(void)\n> +{\n> +\tstatic struct test_cases {\n> +\t\tconst char *name;\n> +\t\tconst char *msg;\n> +\t\tsize_t num_expected_trailers;\n\nThis is more like number of lines in the trailer block, not\nlimiting its count only to true trailers, no?\n\n> +\t} tc[] = {\n> ...\n> +\t\t{\n> +\t\t\t\"with non-trailer lines in trailer block\",\n> +\t\t\t\"subject: foo bar\\n\"\n> +\t\t\t\"\\n\"\n> +\t\t\t/*\n> +\t\t\t * Even though this trailer block has a non-trailer line\n> +\t\t\t * in it, it's still a valid trailer block because it's\n> +\t\t\t * at least 25% trailers and is Git-generated.\n> +\t\t\t */\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"not a trailer line\\n\"\n> +\t\t\t\"Signed-off-by: x\\n\",\n> +\t\t\t1\n> +\t\t},\n\nIt is OK to leave it num_expected_trailers in this step and then\nrename it when you update this \"1\" (number of real trailer lines)\nto \"4\" (number of lines in the trailer block).\n\nI wonder if you'd want to make more data available to the test.  At\nleast it would be more useful if the number of true trailer lines\nand the number of lines in the trialer block are available\nseparately.\n\nThe interface into the trailers that is being tested by this code is\n\"the caller repeatedly calls the iterator, and the caller can\ninspect the iterator's state available as its .raw, .key and .val\nmembers and use them as it sees fit\", so you could check, if you\nwanted to, the following given the above sample data:\n\n * the first iteration finds no key/value pair (optionally, the\n   contents found in the .raw member is as expected).\n * the second iteration finds no key/value pair (ditto).\n * the third iteration finds no key/value pair (ditto).\n * the fourth iteration finds key=\"Signed-off-by\" value=\"x\".\n * there is no fifth iteration.\n\nbut the current code only checks the last condition and nothing\nelse.  I dunno.\n"},{"id":"493243","messageId":"owly7cgs6gc7.fsf@fine.c.googlers.com","threadId":"61132","inReplyTo":"xmqq5xwd58b9.fsf@gitster.g","subject":"Re: [PATCH v2 2/8] trailer: add unit tests for trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-04-20T00:14:00Z","receivedAt":"2024-04-20T00:14: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>> +UNIT_TEST_PROGRAMS += t-trailer\n>>  UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n>>  UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n>>  UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n>\n> Totally offtopic, but does it bother folks who are interested in\n> adding more unit tests that they do not seem to interact very well\n> with GIT_SKIP_TESTS environment variable?\n\nFWIW I am not bothered (not that I've actually used GIT_SKIP_TESTS)\nmainly because the unit tests finish so quickly.\n\n>\n>> diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\n>> new file mode 100644\n>> index 00000000000..147a51b66b9\n>> --- /dev/null\n>> +++ b/t/unit-tests/t-trailer.c\n>> @@ -0,0 +1,175 @@\n>> +#include \"test-lib.h\"\n>> +#include \"trailer.h\"\n>> +\n>> +static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n>> +{\n>> +\tstruct trailer_iterator iter;\n>> +\tsize_t i = 0;\n>> +\n>> +\ttrailer_iterator_init(&iter, msg);\n>> +\twhile (trailer_iterator_advance(&iter)) {\n>> +\t\ti++;\n>> +\t}\n>\n> Unnecessary {braces} around a single-statement block?\n\nGah, I blame writing too much Go. Will fix.\n\nI also wonder if there's a C linter that could catch this... I am not\nvery familiar with C tooling. Would be great to run that in CI (GGG).\n\n>> +\ttrailer_iterator_release(&iter);\n>> +\n>> +\tcheck_uint(i, ==, num_expected_trailers);\n>> +}\n>> +\n>> +static void run_t_trailer_iterator(void)\n>> +{\n>> +\tstatic struct test_cases {\n>> +\t\tconst char *name;\n>> +\t\tconst char *msg;\n>> +\t\tsize_t num_expected_trailers;\n>\n> This is more like number of lines in the trailer block, not\n> limiting its count only to true trailers, no?\n\nYes, but to be even more precise, it would be the number of trailer\nobjects in the trailer block (a single trailer could be folded over\nmultiple lines). Will update to \"num_expected_objects\".\n\n>\n>> +\t} tc[] = {\n>> ...\n>> +\t\t{\n>> +\t\t\t\"with non-trailer lines in trailer block\",\n>> +\t\t\t\"subject: foo bar\\n\"\n>> +\t\t\t\"\\n\"\n>> +\t\t\t/*\n>> +\t\t\t * Even though this trailer block has a non-trailer line\n>> +\t\t\t * in it, it's still a valid trailer block because it's\n>> +\t\t\t * at least 25% trailers and is Git-generated.\n>> +\t\t\t */\n>> +\t\t\t\"not a trailer line\\n\"\n>> +\t\t\t\"not a trailer line\\n\"\n>> +\t\t\t\"not a trailer line\\n\"\n>> +\t\t\t\"Signed-off-by: x\\n\",\n>> +\t\t\t1\n>> +\t\t},\n>\n> It is OK to leave it num_expected_trailers in this step and then\n> rename it when you update this \"1\" (number of real trailer lines)\n> to \"4\" (number of lines in the trailer block).\n>\n> I wonder if you'd want to make more data available to the test.  At\n> least it would be more useful if the number of true trailer lines\n> and the number of lines in the trialer block are available\n> separately.\n\nI purposely did the simplest test possible in order to keep the patch\nsimple. Totally OK with expanding the data available to the test though,\nif you'd prefer that (although that could also be in a separate series\nlater when we start converting some of the existing shell tests to these\nunit tests).\n\n> The interface into the trailers that is being tested by this code is\n> \"the caller repeatedly calls the iterator, and the caller can\n> inspect the iterator's state available as its .raw, .key and .val\n> members and use them as it sees fit\", so you could check, if you\n> wanted to, the following given the above sample data:\n>\n>  * the first iteration finds no key/value pair (optionally, the\n>    contents found in the .raw member is as expected).\n>  * the second iteration finds no key/value pair (ditto).\n>  * the third iteration finds no key/value pair (ditto).\n>  * the fourth iteration finds key=\"Signed-off-by\" value=\"x\".\n>  * there is no fifth iteration.\n>\n> but the current code only checks the last condition and nothing\n> else.  I dunno.\n\nYeah, this sounds like the natural thing to do. Basically have an exact\nlist of \"this is the linked list of trailer objects I expect to see\nafter parsing is complete\".\n\nI do plan on making the trailer iterator struct private in a future\nseries though, so maybe it's best to do the above after that series (to\navoid churn)? IDK.\n\n@Christian thoughts?\n"},{"id":"493364","messageId":"xmqqh6frojz9.fsf@gitster.g","threadId":"61132","inReplyTo":"84897cf5c83eb67c023603016b49fb7b56870aa3.1713504153.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/8] sequencer: use the trailer iterator","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-23T21:19:22Z","receivedAt":"2024-04-23T21:19:31Z","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> 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>\n> Note how we have to use \"iter.raw\" in order to get the same behavior as\n> before when we iterated over the unparsed string array (char **trailers)\n> in trailer_info.\n\n> Signed-off-by: Linus Arver <linusa@google.com>\n> ---\n>  sequencer.c | 27 ++++++++++++---------------\n>  1 file changed, 12 insertions(+), 15 deletions(-)\n\nOK.  The code reduction primarily comes from the fact that the\noriginal was manually iterating over the trailer lines that you can\nget from the iterator API.\n\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> +\tif (!i)\n> +\t\treturn 0;\n>  \n> +\tfound_sob_last = (int)i == found_sob;\n\nThis is slightly harder to reason about, as we cannot directly say\n\"the collection being iterated over has .nr members, and what we\nfound was at the end\" like the original could do in its loop.\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\nAs 'i' is incremented before we set found_sob to it in the new loop,\nwhen it is assigned in the loop, the value of found_sob will never\nbe zero.  It used to be that found_sob takes only 0 or 1, but\nbecause we only care about found_sob and found_sob_last being\nzero/non-zero in the remainder of the code, this does not affect the\ncorrectness of the code.\n\nLooking good.\n\n\n"},{"id":"493380","messageId":"xmqq8r136557.fsf@gitster.g","threadId":"61132","inReplyTo":"093f68f365801bc8801f29dc3e6eaa9b07fcd57d.1713504153.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 6/8] trailer: make parse_trailers() return trailer_info pointer","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-23T23:17:08Z","receivedAt":"2024-04-23T23:17:14Z","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 the trailer API user's perspective, the call\n> to trailer_info_new() can be replaced with parse_trailers(); do so in\n> interpret-trailers.\n\nAnd from the trailer API users' perspective, it would now help to\nhave a bit of comment on parse_trailers() function.  The users need\nto know at least:\n\n - what the function returns (i.e. a pointer to an opaque\n   trailer_info structure),\n\n - what operations can be done to the opaque structure, and \n\n - what informations can be extracted out of the opaque structure.\n\nWhen appropriately typed and named members in the structure are\nvisible, the latter two are obvious in well written programs, but\nnow you are going to hide the data structure, \"Show me your data\nstructures, and I won't usually need your code; it'll be obvious.\"\nwould no longer work.  You'd need to compensate for making the\nstructure opaque to rob the clarity from the readers with good\ncomments to the function and the API to help them.\n\nThe changes in this patch look more-or-less obvious.  Looking good.\n"},{"id":"493381","messageId":"xmqq34rb64oj.fsf@gitster.g","threadId":"61132","inReplyTo":"0e9ae049b8861fecf49c097e8d52e734f7a9c9b3.1713504153.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 7/8] trailer: make trailer_info struct private","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-23T23:27:08Z","receivedAt":"2024-04-23T23:27: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> 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    (C) without good documentation on the API, the opaque struct is\n        hostile to programmers by going opposite to \"Show me your\n        data structures, and I won't usually need your code; it'll\n        be obvious.\" mantra.\n\nThe comment inside trailer.c does not count (the API users are not\nsupposed to peek in it---that's the whole point of making the\nstructure opaque).  You'd need to compensate with a bit more doc in\ntrailer.h to help the API users.\n\nOther than that, looks \"correct\".\n"},{"id":"493382","messageId":"xmqqwmon4q3z.fsf@gitster.g","threadId":"61132","inReplyTo":"eca77a1a462a624f988fc50f7588d831678f34f4.1713504153.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 8/8] trailer: retire trailer_info_get() from API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-23T23:27:12Z","receivedAt":"2024-04-23T23:27:17Z","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> Make trailer_info_get() \"static\" to be file-scoped to trailer.c, because\n> no one outside of trailer.c uses it. Remove its declaration from\n> <trailer.h>.\n\nYay.  But the callers still need to know what parse_trailers() gives\nthem, and what they can do with the struct.\n\nOther than that, an obvious last step that concludes the series.\nNicely structured.\n\n"},{"id":"493383","messageId":"xmqqil074nc3.fsf@gitster.g","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/8] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-24T00:27:08Z","receivedAt":"2024-04-24T00:27:16Z","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> NOTE: This series is based on the la/format-trailer-info topic branch (see\n> its discussion at [1]).\n>\n> This series is based on the initial series [2], notably the v4 version of\n> patches 17-20 as suggested by Christian [3]. This version addresses the\n> review comments for those patches, namely the splitting up of Patch 19 there\n> into 3 separate patches [4] (as Patches 05-07 here) .\n>\n> The central idea is to make the trailer_info struct private (that is, move\n> its definition from trailer.h to trailer.c) --- aka the \"pimpl\" idiom. See\n> the detailed commit message for Patch 07 for the motivation behind the\n> change.\n>\n> Patch 04 makes sequencer.c a well-behaved trailer API consumer, by making\n> use of the trailer iterator. Patch 03 prepares us for Patch 04. Patch 08\n> slightly reduces the weight of the API by removing (from the API surface) an\n> unused function.\n\nAs we haven't seen any interest or reviews to this series over its\ntwo iterations, I took a look myself and it looked mostly OK to me.\n\nSo, I'll mark the topic for 'next' unless somebody objects (I really\nwas hoping that Christian would utter something on the topic as it\nhas been his area all along), but given that we'd be in pre-release\nfreeze for one more week, there is no need to rush.\n\nThanks.\n"},{"id":"493455","messageId":"owlyzfti3zc4.fsf@fine.c.googlers.com","threadId":"61132","inReplyTo":"xmqq34rb64oj.fsf@gitster.g","subject":"Re: [PATCH v2 7/8] trailer: make trailer_info struct private","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-04-25T03:17:47Z","receivedAt":"2024-04-25T03:17:49Z","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>> 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>     (C) without good documentation on the API, the opaque struct is\n>         hostile to programmers by going opposite to \"Show me your\n>         data structures, and I won't usually need your code; it'll\n>         be obvious.\" mantra.\n>\n> The comment inside trailer.c does not count (the API users are not\n> supposed to peek in it---that's the whole point of making the\n> structure opaque).  You'd need to compensate with a bit more doc in\n> trailer.h to help the API users.\n\nSGTM. I can reroll again by the end of the week to add docs for would-be\nAPI users. Cheers\n"},{"id":"493494","messageId":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v2.git.1713504153.gitgitgadget@gmail.com","subject":"[PATCH v3 00/10] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:00Z","receivedAt":"2024-04-26T00:26:14Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"NOTE: This series is based on the la/format-trailer-info topic branch (see\nits discussion at [1]).\n\nThis series is based on the initial series [2], notably the v4 version of\npatches 17-20 as suggested by Christian [3]. This version addresses the\nreview comments for those patches, namely the splitting up of Patch 19 there\ninto 3 separate patches [4] (as Patches 05-07 here) .\n\nThe central idea is to make the trailer_info struct private (that is, move\nits definition from trailer.h to trailer.c) --- aka the \"pimpl\" idiom. See\nthe detailed commit message for Patch 07 for the motivation behind the\nchange.\n\nPatch 04 makes sequencer.c a well-behaved trailer API consumer, by making\nuse of the trailer iterator. Patch 03 prepares us for Patch 04. Patch 08\nslightly reduces the weight of the API by removing (from the API surface) an\nunused function.\n\n\nNotable changes in v3\n=====================\n\n * (NEW Patch 10) Expand test coverage to check the contents of each\n   iteration (raw, key, val fields), not just the total number of iterations\n * (NEW Patch 09) Add documentation in <trailer.h> for using\n   parse_trailers()\n * (unrelated) I will lose access to my linusa@google.com email address\n   tomorrow (I'm switching jobs!) and so future emails from me will come\n   from linus@ucla.edu [5]. I've added the latter email to the CC list here\n   so things should just work. Cheers\n\n\nNotable changes in v2\n=====================\n\n * Add unit tests at the beginning of the series (Patches 01 and 02) and use\n   it to verify that the other edge cases remain unchanged when we add the\n   \"raw\" member (Patch 03)\n\n[1]\nhttps://lore.kernel.org/git/pull.1694.git.1710485706.gitgitgadget@gmail.com/\n[2]\nhttps://lore.kernel.org/git/pull.1632.v4.git.1707196348.gitgitgadget@gmail.com/\n[3]\nhttps://lore.kernel.org/git/CAP8UFD08F0V13X0+CJ1uhMPzPWVMs2okGVMJch0DkQg5M3BWLA@mail.gmail.com/\n[4]\nhttps://lore.kernel.org/git/CAP8UFD1twELGKvvesxgCrZrypKZpgSt04ira3mvurG1UbpDfxQ@mail.gmail.com/\n[5]\nhttps://lore.kernel.org/git/pull.1720.git.1713309711217.gitgitgadget@gmail.com/\n\nLinus Arver (10):\n  Makefile: sort UNIT_TEST_PROGRAMS\n  trailer: add unit tests for trailer iterator\n  trailer: teach iterator about non-trailer lines\n  sequencer: use the trailer iterator\n  interpret-trailers: access trailer_info with new helpers\n  trailer: make parse_trailers() return trailer_info pointer\n  trailer: make trailer_info struct private\n  trailer: retire trailer_info_get() from API\n  trailer: document parse_trailers() usage\n  trailer unit tests: inspect iterator contents\n\n Makefile                     |   5 +-\n builtin/interpret-trailers.c |  12 +-\n sequencer.c                  |  27 ++-\n t/unit-tests/t-trailer.c     | 315 +++++++++++++++++++++++++++++++++++\n trailer.c                    | 167 ++++++++++++-------\n trailer.h                    |  95 +++++++----\n 6 files changed, 507 insertions(+), 114 deletions(-)\n create mode 100644 t/unit-tests/t-trailer.c\n\n\nbase-commit: 3452d173241c8b87ecdd67f91f594cb14327e394\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1696%2Flistx%2Ftrailer-api-part-3-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1696/listx/trailer-api-part-3-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1696\n\nRange-diff vs v2:\n\n  1:  b6a1304f8ae =  1:  b6a1304f8ae Makefile: sort UNIT_TEST_PROGRAMS\n  2:  e1fa05143ac !  2:  4ad0fbbb33c trailer: add unit tests for trailer iterator\n     @@ t/unit-tests/t-trailer.c (new)\n      +\tsize_t i = 0;\n      +\n      +\ttrailer_iterator_init(&iter, msg);\n     -+\twhile (trailer_iterator_advance(&iter)) {\n     ++\twhile (trailer_iterator_advance(&iter))\n      +\t\ti++;\n     -+\t}\n      +\ttrailer_iterator_release(&iter);\n      +\n      +\tcheck_uint(i, ==, num_expected_trailers);\n     @@ t/unit-tests/t-trailer.c (new)\n      +\t\t\t/*\n      +\t\t\t * Even though this trailer block has a non-trailer line\n      +\t\t\t * in it, it's still a valid trailer block because it's\n     -+\t\t\t * at least 25% trailers and is Git-generated.\n     ++\t\t\t * at least 25% trailers and is Git-generated (see\n     ++\t\t\t * git_generated_prefixes[] in trailer.c).\n      +\t\t\t */\n      +\t\t\t\"not a trailer line\\n\"\n      +\t\t\t\"not a trailer line\\n\"\n     @@ t/unit-tests/t-trailer.c (new)\n      +\t\t\t\"\\n\"\n      +\t\t\t/*\n      +\t\t\t * This block has only 1 non-trailer out of 10 (IOW, 90%\n     -+\t\t\t * trailers) but is not considered a trailer because the\n     -+\t\t\t * 25% threshold only applies to cases where there was a\n     -+\t\t\t * Git-generated trailer (see git_generated_prefixes[]\n     -+\t\t\t * in trailer.c).\n     ++\t\t\t * trailers) but is not considered a trailer block\n     ++\t\t\t * because the 25% threshold only applies to cases where\n     ++\t\t\t * there was a Git-generated trailer.\n      +\t\t\t */\n      +\t\t\t\"Reviewed-by: x\\n\"\n      +\t\t\t\"Reviewed-by: x\\n\"\n  3:  5520a98e296 !  3:  9077d5a315d trailer: teach iterator about non-trailer lines\n     @@ Commit message\n          for non-trailer lines, making the comparison still work even with this\n          commit).\n      \n     +    Rename \"num_expected_trailers\" to \"num_expected_objects\" in\n     +    t/unit-tests/t-trailer.c because the items we iterate over now include\n     +    non-trailer lines.\n     +\n          Signed-off-by: Linus Arver <linusa@google.com>\n      \n       ## t/unit-tests/t-trailer.c ##\n     +@@\n     + #include \"test-lib.h\"\n     + #include \"trailer.h\"\n     + \n     +-static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n     ++static void t_trailer_iterator(const char *msg, size_t num_expected_objects)\n     + {\n     + \tstruct trailer_iterator iter;\n     + \tsize_t i = 0;\n     +@@ t/unit-tests/t-trailer.c: static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n     + \t\ti++;\n     + \ttrailer_iterator_release(&iter);\n     + \n     +-\tcheck_uint(i, ==, num_expected_trailers);\n     ++\tcheck_uint(i, ==, num_expected_objects);\n     + }\n     + \n     + static void run_t_trailer_iterator(void)\n     +@@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n     + \tstatic struct test_cases {\n     + \t\tconst char *name;\n     + \t\tconst char *msg;\n     +-\t\tsize_t num_expected_trailers;\n     ++\t\tsize_t num_expected_objects;\n     + \t} tc[] = {\n     + \t\t{\n     + \t\t\t\"empty input\",\n      @@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n       \t\t\t\"not a trailer line\\n\"\n       \t\t\t\"not a trailer line\\n\"\n     @@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n       \t\t},\n       \t\t{\n       \t\t\t\"with non-trailer lines (one too many) in trailer block\",\n     +@@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n     + \n     + \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n     + \t\tTEST(t_trailer_iterator(tc[i].msg,\n     +-\t\t\t\t\ttc[i].num_expected_trailers),\n     ++\t\t\t\t\ttc[i].num_expected_objects),\n     + \t\t     \"%s\", tc[i].name);\n     + \t}\n     + }\n      \n       ## trailer.c ##\n      @@ trailer.c: void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n  4:  84897cf5c83 =  4:  4a1d18da574 sequencer: use the trailer iterator\n  5:  e961d49cd40 =  5:  460979ba964 interpret-trailers: access trailer_info with new helpers\n  6:  093f68f3658 =  6:  d217858c637 trailer: make parse_trailers() return trailer_info pointer\n  7:  0e9ae049b88 !  7:  49c66c48cc1 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     -    There are a couple disadvantages:\n     +    There are a few disadvantages:\n      \n            (A) every time the member of the struct is accessed an extra pointer\n                dereference must be done, and\n     @@ Commit message\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     +          appropriately deallocated by the user, and\n     +\n     +      (C) without good documentation on the API, the opaque struct is\n     +          hostile to programmers by going opposite to the \"Show me your\n     +          data structures, and I won't usually need your code; it'll\n     +          be obvious.\" mantra [2].\n      \n          (The disadvantages have already been observed in the two preparatory\n          commits that precede this one.) This commit believes that the benefits\n     @@ Commit message\n          [1] Hanson, David R. \"C Interfaces and Implementations: Techniques for\n              Creating Reusable Software\". Addison Wesley, 1997. p. 22\n      \n     +    [2] Raymond, Eric S. \"The Cathedral and the Bazaar: Musings on Linux and\n     +        Open Source by an Accidental Revolutionary\". O'Reilly, 1999.\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  8:  eca77a1a462 =  8:  56e1cca4b7b trailer: retire trailer_info_get() from API\n  -:  ----------- >  9:  35304837e08 trailer: document parse_trailers() usage\n  -:  ----------- > 10:  4d53707f836 trailer unit tests: inspect iterator contents\n\n-- \ngitgitgadget\n"},{"id":"493495","messageId":"b6a1304f8aefdfebe2eefc0e07460d6d8dbb004d.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 01/10] Makefile: sort UNIT_TEST_PROGRAMS","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:01Z","receivedAt":"2024-04-26T00:26:15Z","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 Makefile | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 4e255c81f22..d3a3f16f076 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1343,10 +1343,10 @@ THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n UNIT_TEST_PROGRAMS += t-basic\n-UNIT_TEST_PROGRAMS += t-mem-pool\n-UNIT_TEST_PROGRAMS += t-strbuf\n UNIT_TEST_PROGRAMS += t-ctype\n+UNIT_TEST_PROGRAMS += t-mem-pool\n UNIT_TEST_PROGRAMS += t-prio-queue\n+UNIT_TEST_PROGRAMS += t-strbuf\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n-- \ngitgitgadget\n\n"},{"id":"493496","messageId":"4ad0fbbb33cab9d5841689cc5660befe6921d515.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 02/10] trailer: add unit tests for trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:02Z","receivedAt":"2024-04-26T00:26:15Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nTest the number of trailers found by the iterator (to be more precise,\nthe parsing mechanism which the iterator just walks over) when given\nsome some arbitrary log message.\n\nWe test the iterator because it is a public interface function exposed\nby the trailer API (we generally don't want to test internal\nimplementation details which are, unlike the API, subject to drastic\nchanges).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n Makefile                 |   1 +\n t/unit-tests/t-trailer.c | 174 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 175 insertions(+)\n create mode 100644 t/unit-tests/t-trailer.c\n\ndiff --git a/Makefile b/Makefile\nindex d3a3f16f076..5418ddd03be 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1347,6 +1347,7 @@ UNIT_TEST_PROGRAMS += t-ctype\n UNIT_TEST_PROGRAMS += t-mem-pool\n UNIT_TEST_PROGRAMS += t-prio-queue\n UNIT_TEST_PROGRAMS += t-strbuf\n+UNIT_TEST_PROGRAMS += t-trailer\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nnew file mode 100644\nindex 00000000000..c1f897235c7\n--- /dev/null\n+++ b/t/unit-tests/t-trailer.c\n@@ -0,0 +1,174 @@\n+#include \"test-lib.h\"\n+#include \"trailer.h\"\n+\n+static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n+{\n+\tstruct trailer_iterator iter;\n+\tsize_t i = 0;\n+\n+\ttrailer_iterator_init(&iter, msg);\n+\twhile (trailer_iterator_advance(&iter))\n+\t\ti++;\n+\ttrailer_iterator_release(&iter);\n+\n+\tcheck_uint(i, ==, num_expected_trailers);\n+}\n+\n+static void run_t_trailer_iterator(void)\n+{\n+\tstatic struct test_cases {\n+\t\tconst char *name;\n+\t\tconst char *msg;\n+\t\tsize_t num_expected_trailers;\n+\t} tc[] = {\n+\t\t{\n+\t\t\t\"empty input\",\n+\t\t\t\"\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"no newline at beginning\",\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"newline at beginning\",\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t3\n+\t\t},\n+\t\t{\n+\t\t\t\"without body text\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t3\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, without divider\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t4\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, without divider (second trailer block)\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * Because this is the last trailer block, it takes\n+\t\t\t * precedence over the first one encountered above.\n+\t\t\t */\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t2\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, with divider\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"---\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This trailer still counts because the iterator\n+\t\t\t * always ignores the divider.\n+\t\t\t */\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t1\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines in trailer block\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * Even though this trailer block has a non-trailer line\n+\t\t\t * in it, it's still a valid trailer block because it's\n+\t\t\t * at least 25% trailers and is Git-generated (see\n+\t\t\t * git_generated_prefixes[] in trailer.c).\n+\t\t\t */\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t1\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines (one too many) in trailer block\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This block has only 20% trailers, so it's below the\n+\t\t\t * 25% threshold.\n+\t\t\t */\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines (only 1) in trailer block, but no Git-generated trailers\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This block has only 1 non-trailer out of 10 (IOW, 90%\n+\t\t\t * trailers) but is not considered a trailer block\n+\t\t\t * because the 25% threshold only applies to cases where\n+\t\t\t * there was a Git-generated trailer.\n+\t\t\t */\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"not a trailer line\\n\",\n+\t\t\t0\n+\t\t},\n+\t};\n+\n+\tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n+\t\tTEST(t_trailer_iterator(tc[i].msg,\n+\t\t\t\t\ttc[i].num_expected_trailers),\n+\t\t     \"%s\", tc[i].name);\n+\t}\n+}\n+\n+int cmd_main(int argc, const char **argv)\n+{\n+\trun_t_trailer_iterator();\n+\treturn test_done();\n+}\n-- \ngitgitgadget\n\n"},{"id":"493497","messageId":"9077d5a315d0d7272266856bf75a75b0a24df91d.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 03/10] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:03Z","receivedAt":"2024-04-26T00:26:16Z","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 commit 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 even with this\ncommit).\n\nRename \"num_expected_trailers\" to \"num_expected_objects\" in\nt/unit-tests/t-trailer.c because the items we iterate over now include\nnon-trailer lines.\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n t/unit-tests/t-trailer.c | 16 +++++++++++-----\n trailer.c                | 12 +++++-------\n trailer.h                |  8 ++++++++\n 3 files changed, 24 insertions(+), 12 deletions(-)\n\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nindex c1f897235c7..262e2838273 100644\n--- a/t/unit-tests/t-trailer.c\n+++ b/t/unit-tests/t-trailer.c\n@@ -1,7 +1,7 @@\n #include \"test-lib.h\"\n #include \"trailer.h\"\n \n-static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n+static void t_trailer_iterator(const char *msg, size_t num_expected_objects)\n {\n \tstruct trailer_iterator iter;\n \tsize_t i = 0;\n@@ -11,7 +11,7 @@ static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n \t\ti++;\n \ttrailer_iterator_release(&iter);\n \n-\tcheck_uint(i, ==, num_expected_trailers);\n+\tcheck_uint(i, ==, num_expected_objects);\n }\n \n static void run_t_trailer_iterator(void)\n@@ -19,7 +19,7 @@ static void run_t_trailer_iterator(void)\n \tstatic struct test_cases {\n \t\tconst char *name;\n \t\tconst char *msg;\n-\t\tsize_t num_expected_trailers;\n+\t\tsize_t num_expected_objects;\n \t} tc[] = {\n \t\t{\n \t\t\t\"empty input\",\n@@ -119,7 +119,13 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t1\n+\t\t\t/*\n+\t\t\t * Even though there is only really 1 real \"trailer\"\n+\t\t\t * (Signed-off-by), we still have 4 trailer objects\n+\t\t\t * because we still want to iterate through the entire\n+\t\t\t * block.\n+\t\t\t */\n+\t\t\t4\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines (one too many) in trailer block\",\n@@ -162,7 +168,7 @@ static void run_t_trailer_iterator(void)\n \n \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n \t\tTEST(t_trailer_iterator(tc[i].msg,\n-\t\t\t\t\ttc[i].num_expected_trailers),\n+\t\t\t\t\ttc[i].num_expected_objects),\n \t\t     \"%s\", tc[i].name);\n \t}\n }\ndiff --git a/trailer.c b/trailer.c\nindex 3e4dab9c065..4700c441442 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1146,17 +1146,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":"493498","messageId":"4a1d18da574de8d0dbcde62b2d33c05026da1ec1.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 04/10] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:04Z","receivedAt":"2024-04-26T00:26:17Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\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 (char **trailers)\nin trailer_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 ea1441e6174..4c1f6c675e7 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":"493499","messageId":"460979ba9643c5b7eb0fb93a51ba4817396bcbf7.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 05/10] interpret-trailers: access trailer_info with new helpers","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:05Z","receivedAt":"2024-04-26T00:26:19Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nInstead of directly accessing trailer_info members, access them\nindirectly through new helper functions exposed by the trailer API.\n\nThis is the first of two preparatory commits which will allow us to\nuse the so-called \"pimpl\" (pointer to implementation) idiom for the\ntrailer API, by making the trailer_info struct private to the trailer\nimplementation (and thus hidden from the API).\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n builtin/interpret-trailers.c | 12 ++++++------\n trailer.c                    | 21 +++++++++++++++++++++\n trailer.h                    |  4 ++++\n 3 files changed, 31 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 11f4ce9e4a2..f3240682e35 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 = trailer_info_new();\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+\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+\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 4700c441442..95b4c9b8f19 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,6 +952,12 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+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@@ -1000,6 +1006,21 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \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 void trailer_info_get(const struct process_trailer_options *opts,\n \t\t      const char *str,\n \t\t      struct trailer_info *info)\ndiff --git a/trailer.h b/trailer.h\nindex ebafa3657e4..9ac4be853c5 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -97,6 +97,10 @@ void parse_trailers(const struct process_trailer_options *,\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+struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n-- \ngitgitgadget\n\n"},{"id":"493500","messageId":"d217858c637c06836f83d7e6e0c1388326190222.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 06/10] trailer: make parse_trailers() return trailer_info pointer","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:06Z","receivedAt":"2024-04-26T00:26:20Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nThis is the second and final preparatory commit for making the\ntrailer_info struct private to the trailer implementation.\n\nMake trailer_info_get() do the actual work of allocating a new\ntrailer_info struct, and return a pointer to it. Because\nparse_trailers() wraps around trailer_info_get(), it too can return this\npointer to the caller. From the trailer API user's perspective, the call\nto trailer_info_new() can be replaced with parse_trailers(); do so in\ninterpret-trailers.\n\nBecause trailer_info_new() is no longer called by interpret-trailers,\nremove this function from the trailer API.\n\nWith this change, we no longer allocate trailer_info on the stack ---\nall uses of it are via a pointer where the actual data is always\nallocated at runtime through trailer_info_new(). Make\ntrailer_info_release() free this dynamically allocated memory.\n\nFinally, due to the way the function signatures of parse_trailers() and\ntrailer_info_get() have changed, update the callsites in\nformat_trailers_from_commit() and trailer_iterator_init() accordingly.\n\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                    | 41 +++++++++++++++++++-----------------\n trailer.h                    | 17 ++++++---------\n 3 files changed, 31 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f3240682e35..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 = trailer_info_new();\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,7 +151,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-\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)\ndiff --git a/trailer.c b/trailer.c\nindex 95b4c9b8f19..9179dd802c6 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,7 +952,7 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n-struct trailer_info *trailer_info_new(void)\n+static struct trailer_info *trailer_info_new(void)\n {\n \tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n \treturn info;\n@@ -962,16 +962,16 @@ struct trailer_info *trailer_info_new(void)\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@@ -995,6 +995,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@@ -1021,10 +1023,10 @@ int blank_line_before_trailer_block(struct trailer_info *info)\n \treturn info->blank_line_before_trailer;\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+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@@ -1063,6 +1065,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@@ -1071,6 +1075,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@@ -1138,21 +1143,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(trailer_objects);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(opts, &info, msg, &trailer_objects);\n+\tstruct trailer_info *info = parse_trailers(opts, 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 \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, &trailer_objects, out);\n \n \tfree_trailers(&trailer_objects);\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@@ -1161,14 +1164,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@@ -1185,7 +1188,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 9ac4be853c5..b32213a9e23 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -89,18 +89,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-\n-void trailer_info_get(const struct process_trailer_options *,\n-\t\t      const char *str,\n-\t\t      struct trailer_info *);\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 int blank_line_before_trailer_block(struct trailer_info *);\n-struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -142,7 +139,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":"493501","messageId":"49c66c48cc1793744e7565f7ac1ef887c76da4a0.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 07/10] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:07Z","receivedAt":"2024-04-26T00:26:21Z","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 few 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, and\n\n  (C) without good documentation on the API, the opaque struct is\n      hostile to programmers by going opposite to the \"Show me your\n      data structures, and I won't usually need your code; it'll\n      be obvious.\" mantra [2].\n\n(The disadvantages have already been observed in the two preparatory\ncommits that precede this one.) This commit believes that the benefits\noutweigh the disadvantages for designing 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 commit 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\n[2] Raymond, Eric S. \"The Cathedral and the Bazaar: Musings on Linux and\n    Open Source by an Accidental Revolutionary\". O'Reilly, 1999.\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 trailer.c | 21 +++++++++++++++++++++\n trailer.h | 23 ++---------------------\n 2 files changed, 23 insertions(+), 21 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 9179dd802c6..6167b707ae0 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;\ndiff --git a/trailer.h b/trailer.h\nindex b32213a9e23..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-- \ngitgitgadget\n\n"},{"id":"493502","messageId":"56e1cca4b7b63725c3db60157b863ea36453d02a.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 08/10] trailer: retire trailer_info_get() from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:08Z","receivedAt":"2024-04-26T00:26:22Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nMake trailer_info_get() \"static\" to be file-scoped to trailer.c, because\nno one outside of trailer.c uses it. Remove its declaration from\n<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 6167b707ae0..33b6aa7e8bd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -979,6 +979,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@@ -1044,52 +1090,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":"493503","messageId":"4d53707f83612111a150c326bf60ec04db96f852.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 10/10] trailer unit tests: inspect iterator contents","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:10Z","receivedAt":"2024-04-26T00:26:24Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nPreviously we only checked whether we would iterate a certain (expected)\nnumber of times.\n\nAlso check the parsed \"raw\", \"key\" and \"val\" fields during each\niteration.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n t/unit-tests/t-trailer.c | 161 +++++++++++++++++++++++++++++++++++----\n 1 file changed, 148 insertions(+), 13 deletions(-)\n\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nindex 262e2838273..2abba913d00 100644\n--- a/t/unit-tests/t-trailer.c\n+++ b/t/unit-tests/t-trailer.c\n@@ -1,14 +1,27 @@\n #include \"test-lib.h\"\n #include \"trailer.h\"\n \n-static void t_trailer_iterator(const char *msg, size_t num_expected_objects)\n+struct trailer_assertions {\n+\tconst char *raw;\n+\tconst char *key;\n+\tconst char *val;\n+};\n+\n+static void t_trailer_iterator(const char *msg, size_t num_expected_objects,\n+\t\t\t       struct trailer_assertions *trailer_assertions)\n {\n \tstruct trailer_iterator iter;\n \tsize_t i = 0;\n \n \ttrailer_iterator_init(&iter, msg);\n-\twhile (trailer_iterator_advance(&iter))\n+\twhile (trailer_iterator_advance(&iter)) {\n+\t\tif (num_expected_objects) {\n+\t\t\tcheck_str(iter.raw, trailer_assertions[i].raw);\n+\t\t\tcheck_str(iter.key.buf, trailer_assertions[i].key);\n+\t\t\tcheck_str(iter.val.buf, trailer_assertions[i].val);\n+\t\t}\n \t\ti++;\n+\t}\n \ttrailer_iterator_release(&iter);\n \n \tcheck_uint(i, ==, num_expected_objects);\n@@ -16,22 +29,26 @@ static void t_trailer_iterator(const char *msg, size_t num_expected_objects)\n \n static void run_t_trailer_iterator(void)\n {\n+\n \tstatic struct test_cases {\n \t\tconst char *name;\n \t\tconst char *msg;\n \t\tsize_t num_expected_objects;\n+\t\tstruct trailer_assertions trailer_assertions[10];\n \t} tc[] = {\n \t\t{\n \t\t\t\"empty input\",\n \t\t\t\"\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t\t{\n \t\t\t\"no newline at beginning\",\n \t\t\t\"Fixes: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t\t{\n \t\t\t\"newline at beginning\",\n@@ -39,7 +56,27 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Fixes: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\",\n-\t\t\t3\n+\t\t\t3,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Fixes: x\\n\",\n+\t\t\t\t\t.key = \"Fixes\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Acked-by: x\\n\",\n+\t\t\t\t\t.key = \"Acked-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Reviewed-by: x\\n\",\n+\t\t\t\t\t.key = \"Reviewed-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"without body text\",\n@@ -48,7 +85,27 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Fixes: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\",\n-\t\t\t3\n+\t\t\t3,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Fixes: x\\n\",\n+\t\t\t\t\t.key = \"Fixes\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Acked-by: x\\n\",\n+\t\t\t\t\t.key = \"Acked-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Reviewed-by: x\\n\",\n+\t\t\t\t\t.key = \"Reviewed-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with body text, without divider\",\n@@ -63,7 +120,32 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t4\n+\t\t\t4,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Fixes: x\\n\",\n+\t\t\t\t\t.key = \"Fixes\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Acked-by: x\\n\",\n+\t\t\t\t\t.key = \"Acked-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Reviewed-by: x\\n\",\n+\t\t\t\t\t.key = \"Reviewed-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with body text, without divider (second trailer block)\",\n@@ -85,7 +167,22 @@ static void run_t_trailer_iterator(void)\n \t\t\t */\n \t\t\t\"Helped-by: x\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t2\n+\t\t\t2,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Helped-by: x\\n\",\n+\t\t\t\t\t.key = \"Helped-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with body text, with divider\",\n@@ -103,7 +200,17 @@ static void run_t_trailer_iterator(void)\n \t\t\t * always ignores the divider.\n \t\t\t */\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t1\n+\t\t\t1,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines in trailer block\",\n@@ -125,7 +232,32 @@ static void run_t_trailer_iterator(void)\n \t\t\t * because we still want to iterate through the entire\n \t\t\t * block.\n \t\t\t */\n-\t\t\t4\n+\t\t\t4,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"not a trailer line\\n\",\n+\t\t\t\t\t.key = \"not a trailer line\",\n+\t\t\t\t\t.val = \"\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"not a trailer line\\n\",\n+\t\t\t\t\t.key = \"not a trailer line\",\n+\t\t\t\t\t.val = \"\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"not a trailer line\\n\",\n+\t\t\t\t\t.key = \"not a trailer line\",\n+\t\t\t\t\t.val = \"\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines (one too many) in trailer block\",\n@@ -140,7 +272,8 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines (only 1) in trailer block, but no Git-generated trailers\",\n@@ -162,13 +295,15 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"not a trailer line\\n\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t};\n \n \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n \t\tTEST(t_trailer_iterator(tc[i].msg,\n-\t\t\t\t\ttc[i].num_expected_objects),\n+\t\t\t\t\ttc[i].num_expected_objects,\n+\t\t\t\t\ttc[i].trailer_assertions),\n \t\t     \"%s\", tc[i].name);\n \t}\n }\n-- \ngitgitgadget\n"},{"id":"493504","messageId":"35304837e08aa1ecf6bebb47aa31813a80f2a2f4.1714091170.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v3 09/10] trailer: document parse_trailers() usage","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-04-26T00:26:09Z","receivedAt":"2024-04-26T00:26:24Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linusa@google.com>\n\nExplain how to use parse_trailers(), because earlier we made the\ntrailer_info struct opaque. That is, because clients can no longer peek\ninside it, we should give them guidance about how the (pointer to the)\nopaque struct can still be useful to them.\n\nRename \"head\" struct to \"trailer_objects\" to make the wording of the new\ncomments a bit easier to read (because \"head\" itself doesn't really have\nany domain-specific meaning here).\n\nSigned-off-by: Linus Arver <linusa@google.com>\n---\n trailer.c |  8 ++++----\n trailer.h | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 54 insertions(+), 5 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 33b6aa7e8bd..406745264aa 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1026,12 +1026,12 @@ static struct trailer_info *trailer_info_get(const struct process_trailer_option\n }\n \n /*\n- * Parse trailers in \"str\", populating the trailer info and \"head\"\n+ * Parse trailers in \"str\", populating the trailer info and \"trailer_objects\"\n  * linked 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+\t\t\t\t    struct list_head *trailer_objects)\n {\n \tstruct trailer_info *info;\n \tstruct strbuf tok = STRBUF_INIT;\n@@ -1051,13 +1051,13 @@ struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n \t\t\t\t      separator_pos);\n \t\t\tif (opts->unfold)\n \t\t\t\tunfold_value(&val);\n-\t\t\tadd_trailer_item(head,\n+\t\t\tadd_trailer_item(trailer_objects,\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_strip_suffix(&val, \"\\n\");\n-\t\t\tadd_trailer_item(head,\n+\t\t\tadd_trailer_item(trailer_objects,\n \t\t\t\t\t NULL,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t}\ndiff --git a/trailer.h b/trailer.h\nindex 1b7422fa2b0..647d48aa2de 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -70,14 +70,63 @@ 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+/*\n+ * Given some input string \"str\", return a pointer to an opaque trailer_info\n+ * structure. Also populate the trailer_objects list with parsed trailer\n+ * objects. Internally this calls trailer_info_get() to get the opaque pointer,\n+ * but does some extra work to populate the trailer_objects linked list.\n+ *\n+ * The opaque trailer_info pointer can be used to check the position of the\n+ * trailer block as offsets relative to the beginning of \"str\" in\n+ * trailer_block_start() and trailer_block_end().\n+ * blank_line_before_trailer_block() returns 1 if there is a blank line just\n+ * before the trailer block. All of these functions are useful for preserving\n+ * the input before and after the trailer block, if we were to write out the\n+ * original input (but with the trailer block itself modified); see\n+ * builtin/interpret-trailers.c for an example.\n+ *\n+ * For iterating through the parsed trailer block (if you don't care about the\n+ * position of the trailer block itself in the context of the larger string text\n+ * from which it was parsed), please see trailer_iterator_init() which uses the\n+ * trailer_info struct internally.\n+ *\n+ * Lastly, callers should call trailer_info_release() when they are done using\n+ * the opaque pointer.\n+ *\n+ * NOTE: Callers should treat both trailer_info and trailer_objects as\n+ * read-only items, because there is some overlap between the two (trailer_info\n+ * has \"char **trailers\" string array, and trailer_objects will have the same\n+ * data but as a linked list of trailer_item objects). This API does not perform\n+ * any synchronization between the two. In the future we should be able to\n+ * reduce the duplication and use just the linked list.\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+\t\t\t\t    struct list_head *trailer_objects);\n \n+/*\n+ * Return the offset of the start of the trailer block. That is, 0 is the start\n+ * of the input (\"str\" in parse_trailers()) and some other positive number\n+ * indicates how many bytes we have to skip over before we get to the beginning\n+ * of the trailer block.\n+ */\n size_t trailer_block_start(struct trailer_info *);\n+\n+/*\n+ * Return the end of the trailer block, again relative to the start of the\n+ * input.\n+ */\n size_t trailer_block_end(struct trailer_info *);\n+\n+/*\n+ * Return 1 if the trailer block had an extra newline (blank line) just before\n+ * it.\n+ */\n int blank_line_before_trailer_block(struct trailer_info *);\n \n+/*\n+ * Free trailer_info struct.\n+ */\n void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n-- \ngitgitgadget\n\n"},{"id":"493513","messageId":"CAP8UFD0ZHpo7US6dx_WK6F_1JYsBdHunBaW86qM1CzOKaPC-aA@mail.gmail.com","threadId":"61132","inReplyTo":"4ad0fbbb33cab9d5841689cc5660befe6921d515.1714091170.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 02/10] trailer: add unit tests for trailer iterator","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-04-26T14:51:54Z","receivedAt":"2024-04-26T14:52:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 26, 2024 at 2:26 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\n> Test the number of trailers found by the iterator (to be more precise,\n> the parsing mechanism which the iterator just walks over) when given\n> some some arbitrary log message.\n\ns/some some/some/\n\n> We test the iterator because it is a public interface function exposed\n> by the trailer API (we generally don't want to test internal\n> implementation details which are, unlike the API, subject to drastic\n> changes).\n>\n> Signed-off-by: Linus Arver <linusa@google.com>\n\n\n> +static void run_t_trailer_iterator(void)\n> +{\n> +       static struct test_cases {\n> +               const char *name;\n> +               const char *msg;\n> +               size_t num_expected_trailers;\n> +       } tc[] = {\n\n...\n\n> +       };\n> +\n> +       for (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n> +               TEST(t_trailer_iterator(tc[i].msg,\n> +                                       tc[i].num_expected_trailers),\n> +                    \"%s\", tc[i].name);\n\nNit: the members of struct test_cases are used in the (msg,\nnum_expected_trailers, name) order, while they are declared in the\n(name, msg, num_expected_trailers) order. I think it would make it a\nbit easier to use in struct test_cases the same order in which they\nare used in the TEST() macro.\n\n> +       }\n> +}\n> +\n> +int cmd_main(int argc, const char **argv)\n> +{\n> +       run_t_trailer_iterator();\n> +       return test_done();\n> +}\n\nLGTM otherwise.\n"},{"id":"493523","messageId":"xmqqle50ayeh.fsf@gitster.g","threadId":"61132","inReplyTo":"CAP8UFD0ZHpo7US6dx_WK6F_1JYsBdHunBaW86qM1CzOKaPC-aA@mail.gmail.com","subject":"Re: [PATCH v3 02/10] trailer: add unit tests for trailer iterator","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-26T16:20:38Z","receivedAt":"2024-04-26T16:20:44Z","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 Fri, Apr 26, 2024 at 2:26 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Test the number of trailers found by the iterator (to be more precise,\n>> the parsing mechanism which the iterator just walks over) when given\n>> some some arbitrary log message.\n>\n> s/some some/some/\n\nRight.\n\n>> +static void run_t_trailer_iterator(void)\n>> +{\n>> +       static struct test_cases {\n>> +               const char *name;\n>> +               const char *msg;\n>> +               size_t num_expected_trailers;\n>> +       } tc[] = {\n>\n> ...\n>\n>> +       };\n>> +\n>> +       for (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n>> +               TEST(t_trailer_iterator(tc[i].msg,\n>> +                                       tc[i].num_expected_trailers),\n>> +                    \"%s\", tc[i].name);\n>\n> Nit: the members of struct test_cases are used in the (msg,\n> num_expected_trailers, name) order, while they are declared in the\n> (name, msg, num_expected_trailers) order. I think it would make it a\n> bit easier to use in struct test_cases the same order in which they\n> are used in the TEST() macro.\n\nI am not sure if I agree.  In the array of struct, being able to\nidentify each array item with its .name component makes quite a lot\nof sense, especially when the .name member is not really part of the\ndata used in tests but is used as an identifier for the tuple made\nof other members (i.e., <msg, num_expected_trailers> in this case).\n\nThe TEST() macro is unable to take \"name\" as an early parameter than\nothers due to how it wants to create the identifying string (i.e.,\ndoing an equivalent of strfmt() on tc[i].name), but reordering the\nstruct members to match the peculiar order the members are used\nsmells like a tail wagging a dog.\n\n>\n>> +       }\n>> +}\n>> +\n>> +int cmd_main(int argc, const char **argv)\n>> +{\n>> +       run_t_trailer_iterator();\n>> +       return test_done();\n>> +}\n>\n> LGTM otherwise.\n\nThanks.\n"},{"id":"493524","messageId":"owlywmok3xcy.fsf@fine.c.googlers.com","threadId":"61132","inReplyTo":"CAP8UFD0ZHpo7US6dx_WK6F_1JYsBdHunBaW86qM1CzOKaPC-aA@mail.gmail.com","subject":"Re: [PATCH v3 02/10] trailer: add unit tests for trailer iterator","fromName":"Linus Arver","fromEmail":"linusa@google.com","sentAt":"2024-04-26T16:25:01Z","receivedAt":"2024-04-26T16:25:03Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Hello Christian!\n\nChristian Couder <christian.couder@gmail.com> writes:\n\n> On Fri, Apr 26, 2024 at 2:26 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\n>> Test the number of trailers found by the iterator (to be more precise,\n>> the parsing mechanism which the iterator just walks over) when given\n>> some some arbitrary log message.\n>\n> s/some some/some/\n\nFixed locally, thanks. Will send as part of a reroll pending further\nreview comments.\n\n>> We test the iterator because it is a public interface function exposed\n>> by the trailer API (we generally don't want to test internal\n>> implementation details which are, unlike the API, subject to drastic\n>> changes).\n>>\n>> Signed-off-by: Linus Arver <linusa@google.com>\n>\n>\n>> +static void run_t_trailer_iterator(void)\n>> +{\n>> +       static struct test_cases {\n>> +               const char *name;\n>> +               const char *msg;\n>> +               size_t num_expected_trailers;\n>> +       } tc[] = {\n>\n> ...\n>\n>> +       };\n>> +\n>> +       for (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n>> +               TEST(t_trailer_iterator(tc[i].msg,\n>> +                                       tc[i].num_expected_trailers),\n>> +                    \"%s\", tc[i].name);\n>\n> Nit: the members of struct test_cases are used in the (msg,\n> num_expected_trailers, name) order, while they are declared in the\n> (name, msg, num_expected_trailers) order. I think it would make it a\n> bit easier to use in struct test_cases the same order in which they\n> are used in the TEST() macro.\n\nThis bothered me as well, but ultimately I preferred to see the test\nnames first in the actual test cases where each one is defined like\n\n     {\n        \"name of test\",\n        ...\n     },\n     {\n        \"name of another test\",\n        ...\n     }\n     ...\n\ninstead of the other way around. FWIW this style comes from Golang where\nit is the standard practice there. I suppose in this instance we have\ntest cases like\n\n     {\n             \"without body text\",\n             \"subject: foo bar\\n\"\n             \"\\n\"\n             \"Fixes: x\\n\"\n             \"Acked-by: x\\n\"\n             \"Reviewed-by: x\\n\",\n             3\n     },\n\nand the separation between \"name\" vs \"msg\" could be a bit confusing on\nfirst glance, but I don't think that's a big deal. Plus our\ntest_expect_success shell functions also expect the name as the first\nparameter, so it would be consistent with that style.\n\nIt's unfortunate that we cannot put __VA_ARGS__ as the \"first parameter\"\nto the TEST() macro, like\n\n    TEST(\"%s\", tc[i].name,\n         t_trailer_iterator(tc[i].msg,\n                            tc[i].num_expected_trailers),\n        );\n\nbut I suppose that's a limitation of __VA_ARGS__. I also do wonder\nwhether we even need the test case name to be __VA_ARGS__ at all though\n(we certainly don't *need* it here as the test case names are already\nunique) --- so it might be fine to have another macro that only takes\nthe test name and a test function. Something like\n\n    #define TC(name, t) ...\n\non top of the\n\n    #define TEST(t, ...) ...\n\nwe already have, perhaps? IDK.\n"},{"id":"493538","messageId":"CAP8UFD0OATdCaEN1SrvYMTZP3b1uWCZw57cXHhUNPW9eTj+x0Q@mail.gmail.com","threadId":"61132","inReplyTo":"9077d5a315d0d7272266856bf75a75b0a24df91d.1714091170.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/10] trailer: teach iterator about non-trailer lines","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-04-27T12:50:21Z","receivedAt":"2024-04-27T12:50:35Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"(Sorry I just realized that I had sent this email to Linus only.)\n\nOn Fri, Apr 26, 2024 at 2:26 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Linus Arver <linusa@google.com>\n>\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> 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> 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> 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> The next commit 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> 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>     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 even with this\n> commit).\n>\n> Rename \"num_expected_trailers\" to \"num_expected_objects\" in\n> t/unit-tests/t-trailer.c because the items we iterate over now include\n> non-trailer lines.\n\nI think it would be simpler if the previous patch used just\n\"num_expected\" or \"expected\". It's not like the other fields in the\nstruct (\"msg\" and \"name\") are very explicit, so why this one only?\n\n> Signed-off-by: Linus Arver <linusa@google.com>\n\n\n> diff --git a/trailer.c b/trailer.c\n> index 3e4dab9c065..4700c441442 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1146,17 +1146,15 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n>\n>  int trailer_iterator_advance(struct trailer_iterator *iter)\n>  {\n> -       while (iter->internal.cur < iter->internal.info.trailer_nr) {\n> -               char *trailer = iter->internal.info.trailers[iter->internal.cur++];\n> -               int separator_pos = find_separator(trailer, separators);\n> -\n> -               if (separator_pos < 1)\n> -                       continue; /* not a real trailer */\n> +       if (iter->internal.cur < iter->internal.info.trailer_nr) {\n> +               char *line = iter->internal.info.trailers[iter->internal.cur++];\n> +               int separator_pos = find_separator(line, separators);\n>\n> +               iter->raw = line;\n>                 strbuf_reset(&iter->key);\n>                 strbuf_reset(&iter->val);\n>                 parse_trailer(&iter->key, &iter->val, NULL,\n> -                             trailer, separator_pos);\n> +                             line, separator_pos);\n>                 /* Always unfold values during iteration. */\n>                 unfold_value(&iter->val);\n>                 return 1;\n> diff --git a/trailer.h b/trailer.h\n> index 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> +       /*\n> +        * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n> +        * key/val pair as part of a trailer block. A trailer block can be\n> +        * either 100% trailer lines, or mixed in with non-trailer lines (in\n> +        * which case at least 25% must be trailer lines).\n\nI don't think 25% is important here. What is more important is to just\nsay that this field could not be an actual trailer, and to tell what\nthe 'key' and 'val' fields below will contain then.\n\n\n> +        */\n> +       const char *raw;\n> +\n>         struct strbuf key;\n>         struct strbuf val;\n"},{"id":"493539","messageId":"CAP8UFD3Vzs2KvsE2qmx_54UX9ktAngr2FkpKfWVjRMD=t1B7Wg@mail.gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/10] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-04-27T12:51:39Z","receivedAt":"2024-04-27T12:51:53Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"(I had sent this email only to Linus too, sorry.)\n\nOn Fri, Apr 26, 2024 at 2:26 AM Linus Arver via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> Linus Arver (10):\n>   Makefile: sort UNIT_TEST_PROGRAMS\n>   trailer: add unit tests for trailer iterator\n>   trailer: teach iterator about non-trailer lines\n>   sequencer: use the trailer iterator\n>   interpret-trailers: access trailer_info with new helpers\n>   trailer: make parse_trailers() return trailer_info pointer\n>   trailer: make trailer_info struct private\n>   trailer: retire trailer_info_get() from API\n>   trailer: document parse_trailers() usage\n>   trailer unit tests: inspect iterator contents\n\nI took a look at the whole series and had only a few minor comments on\nsome patches.\n\nThanks!\n"},{"id":"493723","messageId":"CAMo6p=EggYjK60fWmNC2XRFf0zRf4PCcUT-vmFM6RH2J0i3hrw@mail.gmail.com","threadId":"61132","inReplyTo":"CAP8UFD0OATdCaEN1SrvYMTZP3b1uWCZw57cXHhUNPW9eTj+x0Q@mail.gmail.com","subject":"Re: [PATCH v3 03/10] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver","fromEmail":"linus@ucla.edu","sentAt":"2024-04-30T04:42:18Z","receivedAt":"2024-04-30T04:42:19Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> (Sorry I just realized that I had sent this email to Linus only.)\n>\n> On Fri, Apr 26, 2024 at 2:26 AM Linus Arver via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>>\n>> From: Linus Arver <linusa@google.com>\n>>\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>> 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>> 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>> 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>> The next commit 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>> 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>>     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 even with this\n>> commit).\n>>\n>> Rename \"num_expected_trailers\" to \"num_expected_objects\" in\n>> t/unit-tests/t-trailer.c because the items we iterate over now include\n>> non-trailer lines.\n>\n> I think it would be simpler if the previous patch used just\n> \"num_expected\" or \"expected\". It's not like the other fields in the\n> struct (\"msg\" and \"name\") are very explicit, so why this one only?\n\nI didn't give it much thought TBH. \"num_expected\" SGTM. Will update.\n\n>> Signed-off-by: Linus Arver <linusa@google.com>\n>\n>\n>> diff --git a/trailer.c b/trailer.c\n>> index 3e4dab9c065..4700c441442 100644\n>> --- a/trailer.c\n>> +++ b/trailer.c\n>> @@ -1146,17 +1146,15 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)\n>>\n>>  int trailer_iterator_advance(struct trailer_iterator *iter)\n>>  {\n>> -       while (iter->internal.cur < iter->internal.info.trailer_nr) {\n>> -               char *trailer = iter->internal.info.trailers[iter->internal.cur++];\n>> -               int separator_pos = find_separator(trailer, separators);\n>> -\n>> -               if (separator_pos < 1)\n>> -                       continue; /* not a real trailer */\n>> +       if (iter->internal.cur < iter->internal.info.trailer_nr) {\n>> +               char *line = iter->internal.info.trailers[iter->internal.cur++];\n>> +               int separator_pos = find_separator(line, separators);\n>>\n>> +               iter->raw = line;\n>>                 strbuf_reset(&iter->key);\n>>                 strbuf_reset(&iter->val);\n>>                 parse_trailer(&iter->key, &iter->val, NULL,\n>> -                             trailer, separator_pos);\n>> +                             line, separator_pos);\n>>                 /* Always unfold values during iteration. */\n>>                 unfold_value(&iter->val);\n>>                 return 1;\n>> diff --git a/trailer.h b/trailer.h\n>> index 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>> +       /*\n>> +        * Raw line (e.g., \"foo: bar baz\") before being parsed as a trailer\n>> +        * key/val pair as part of a trailer block. A trailer block can be\n>> +        * either 100% trailer lines, or mixed in with non-trailer lines (in\n>> +        * which case at least 25% must be trailer lines).\n>\n> I don't think 25% is important here.\n\nSG, will remove 25% language (FWIW we already have such language in\ntrailer.c if devs want to take a more closer look, so it's not like\nwe're losing any info overall).\n\n> What is more important is to just\n> say that this field could not be an actual trailer, and to tell what\n> the 'key' and 'val' fields below will contain then.\n\nWill update.\n\n>\n>> +        */\n>> +       const char *raw;\n>> +\n>>         struct strbuf key;\n>>         struct strbuf val;\n\n\nBTW I will be on vacation for the next several weeks. However as the\nsuggested changes are minor, I think I can still get to them and push up\na v4 sometime this week. Cheers.\n"},{"id":"493727","messageId":"CAMo6p=GRcmy6NDGRskuNrauMYTkAgk_p_0TJJTPC2=axVrWuvQ@mail.gmail.com","threadId":"61132","inReplyTo":"CAMo6p=EggYjK60fWmNC2XRFf0zRf4PCcUT-vmFM6RH2J0i3hrw@mail.gmail.com","subject":"Re: [PATCH v3 03/10] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver","fromEmail":"linus@ucla.edu","sentAt":"2024-04-30T04:55:53Z","receivedAt":"2024-04-30T04:55:54Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Linus Arver <linus@ucla.edu> writes:\n\n> Christian Couder <christian.couder@gmail.com> writes:\n>\n>> (Sorry I just realized that I had sent this email to Linus only.)\n>>\n>> On Fri, Apr 26, 2024 at 2:26 AM Linus Arver via GitGitGadget\n>> <gitgitgadget@gmail.com> wrote:\n>>>\n>>> From: Linus Arver <linusa@google.com>\n>>>\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>>> 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>>> 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>>> 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>>> The next commit 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>>> 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>>>     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 even with this\n>>> commit).\n>>>\n>>> Rename \"num_expected_trailers\" to \"num_expected_objects\" in\n>>> t/unit-tests/t-trailer.c because the items we iterate over now include\n>>> non-trailer lines.\n>>\n>> I think it would be simpler if the previous patch used just\n>> \"num_expected\" or \"expected\". It's not like the other fields in the\n>> struct (\"msg\" and \"name\") are very explicit, so why this one only?\n>\n> I didn't give it much thought TBH. \"num_expected\" SGTM. Will update.\n\nAnother thing: I will rename \"trailer_assertions\" in Path 10 to probably\n\"trailer_contents\" because it sounds simpler. I am replying here instead\nof on Patch 10 because my mail setup still has some rough edges for the\ntransition away from @google.com (I no longer work there).\n\nAnd on that note, I'll have to update the SOB lines to match my new\nemail address.\n"},{"id":"493858","messageId":"8a9f71442d8a5ae6bb0cc9e2d098e85b7e4a4764.1714625667.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 01/10] Makefile: sort UNIT_TEST_PROGRAMS","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:18Z","receivedAt":"2024-05-02T04:54:32Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\n\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n Makefile | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 4e255c81f22..d3a3f16f076 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1343,10 +1343,10 @@ THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n UNIT_TEST_PROGRAMS += t-basic\n-UNIT_TEST_PROGRAMS += t-mem-pool\n-UNIT_TEST_PROGRAMS += t-strbuf\n UNIT_TEST_PROGRAMS += t-ctype\n+UNIT_TEST_PROGRAMS += t-mem-pool\n UNIT_TEST_PROGRAMS += t-prio-queue\n+UNIT_TEST_PROGRAMS += t-strbuf\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\n-- \ngitgitgadget\n\n"},{"id":"493859","messageId":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v3.git.1714091170.gitgitgadget@gmail.com","subject":"[PATCH v4 00/10] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:17Z","receivedAt":"2024-05-02T04:54:32Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"NOTE: This series is based on the la/format-trailer-info topic branch (see\nits discussion at [1]).\n\nThis series is based on the initial series [2], notably the v4 version of\npatches 17-20 as suggested by Christian [3]. This version addresses the\nreview comments for those patches, namely the splitting up of Patch 19 there\ninto 3 separate patches [4] (as Patches 05-07 here) .\n\nThe central idea is to make the trailer_info struct private (that is, move\nits definition from trailer.h to trailer.c) --- aka the \"pimpl\" idiom. See\nthe detailed commit message for Patch 07 for the motivation behind the\nchange.\n\nPatch 04 makes sequencer.c a well-behaved trailer API consumer, by making\nuse of the trailer iterator. Patch 03 prepares us for Patch 04. Patch 08\nslightly reduces the weight of the API by removing (from the API surface) an\nunused function.\n\n\nNotable changes in v4\n=====================\n\n * Drop \"25%\" language in Patch 03\n * Rename some variables\n * Update patch emails to personal (linus@ucla.edu) email\n\n\nNotable changes in v3\n=====================\n\n * (NEW Patch 10) Expand test coverage to check the contents of each\n   iteration (raw, key, val fields), not just the total number of iterations\n * (NEW Patch 09) Add documentation in <trailer.h> for using\n   parse_trailers()\n * (unrelated) I will lose access to my linusa@google.com email address\n   tomorrow (I'm switching jobs!) and so future emails from me will come\n   from linus@ucla.edu [5]. I've added the latter email to the CC list here\n   so things should just work. Cheers\n\n\nNotable changes in v2\n=====================\n\n * Add unit tests at the beginning of the series (Patches 01 and 02) and use\n   it to verify that the other edge cases remain unchanged when we add the\n   \"raw\" member (Patch 03)\n\n[1]\nhttps://lore.kernel.org/git/pull.1694.git.1710485706.gitgitgadget@gmail.com/\n[2]\nhttps://lore.kernel.org/git/pull.1632.v4.git.1707196348.gitgitgadget@gmail.com/\n[3]\nhttps://lore.kernel.org/git/CAP8UFD08F0V13X0+CJ1uhMPzPWVMs2okGVMJch0DkQg5M3BWLA@mail.gmail.com/\n[4]\nhttps://lore.kernel.org/git/CAP8UFD1twELGKvvesxgCrZrypKZpgSt04ira3mvurG1UbpDfxQ@mail.gmail.com/\n[5]\nhttps://lore.kernel.org/git/pull.1720.git.1713309711217.gitgitgadget@gmail.com/\n\nLinus Arver (10):\n  Makefile: sort UNIT_TEST_PROGRAMS\n  trailer: add unit tests for trailer iterator\n  trailer: teach iterator about non-trailer lines\n  sequencer: use the trailer iterator\n  interpret-trailers: access trailer_info with new helpers\n  trailer: make parse_trailers() return trailer_info pointer\n  trailer: make trailer_info struct private\n  trailer: retire trailer_info_get() from API\n  trailer: document parse_trailers() usage\n  trailer unit tests: inspect iterator contents\n\n Makefile                     |   5 +-\n builtin/interpret-trailers.c |  12 +-\n sequencer.c                  |  27 ++-\n t/unit-tests/t-trailer.c     | 315 +++++++++++++++++++++++++++++++++++\n trailer.c                    | 167 ++++++++++++-------\n trailer.h                    |  94 +++++++----\n 6 files changed, 506 insertions(+), 114 deletions(-)\n create mode 100644 t/unit-tests/t-trailer.c\n\n\nbase-commit: 3452d173241c8b87ecdd67f91f594cb14327e394\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1696%2Flistx%2Ftrailer-api-part-3-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1696/listx/trailer-api-part-3-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1696\n\nRange-diff vs v3:\n\n  1:  b6a1304f8ae !  1:  8a9f71442d8 Makefile: sort UNIT_TEST_PROGRAMS\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          Makefile: sort UNIT_TEST_PROGRAMS\n      \n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## Makefile ##\n      @@ Makefile: THIRD_PARTY_SOURCES += sha1collisiondetection/%\n  2:  4ad0fbbb33c !  2:  b503b539c6f trailer: add unit tests for trailer iterator\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          trailer: add unit tests for trailer iterator\n     @@ Commit message\n          implementation details which are, unlike the API, subject to drastic\n          changes).\n      \n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## Makefile ##\n      @@ Makefile: UNIT_TEST_PROGRAMS += t-ctype\n  3:  9077d5a315d !  3:  4aeb48050b1 trailer: teach iterator about non-trailer lines\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          trailer: teach iterator about non-trailer lines\n     @@ Commit message\n          for non-trailer lines, making the comparison still work even with this\n          commit).\n      \n     -    Rename \"num_expected_trailers\" to \"num_expected_objects\" in\n     +    Rename \"num_expected_trailers\" to \"num_expected\" in\n          t/unit-tests/t-trailer.c because the items we iterate over now include\n          non-trailer lines.\n      \n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## t/unit-tests/t-trailer.c ##\n      @@\n     @@ t/unit-tests/t-trailer.c\n       #include \"trailer.h\"\n       \n      -static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n     -+static void t_trailer_iterator(const char *msg, size_t num_expected_objects)\n     ++static void t_trailer_iterator(const char *msg, size_t num_expected)\n       {\n       \tstruct trailer_iterator iter;\n       \tsize_t i = 0;\n     @@ t/unit-tests/t-trailer.c: static void t_trailer_iterator(const char *msg, size_t\n       \ttrailer_iterator_release(&iter);\n       \n      -\tcheck_uint(i, ==, num_expected_trailers);\n     -+\tcheck_uint(i, ==, num_expected_objects);\n     ++\tcheck_uint(i, ==, num_expected);\n       }\n       \n       static void run_t_trailer_iterator(void)\n     @@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n       \t\tconst char *name;\n       \t\tconst char *msg;\n      -\t\tsize_t num_expected_trailers;\n     -+\t\tsize_t num_expected_objects;\n     ++\t\tsize_t num_expected;\n       \t} tc[] = {\n       \t\t{\n       \t\t\t\"empty input\",\n     @@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n       \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n       \t\tTEST(t_trailer_iterator(tc[i].msg,\n      -\t\t\t\t\ttc[i].num_expected_trailers),\n     -+\t\t\t\t\ttc[i].num_expected_objects),\n     ++\t\t\t\t\ttc[i].num_expected),\n       \t\t     \"%s\", tc[i].name);\n       \t}\n       }\n     @@ trailer.h: void format_trailers_from_commit(const struct process_trailer_options\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 * key/val pair as part of a trailer block (as the \"key\" and \"val\"\n     ++\t * fields below). If a line fails to parse as a trailer, then the \"key\"\n     ++\t * will be the entire line and \"val\" will be the empty string.\n      +\t */\n      +\tconst char *raw;\n     -+\n       \tstruct strbuf key;\n       \tstruct strbuf val;\n       \n  4:  4a1d18da574 !  4:  a3d080d4d6c sequencer: use the trailer iterator\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          sequencer: use the trailer iterator\n     @@ Commit message\n          before when we iterated over the unparsed string array (char **trailers)\n          in trailer_info.\n      \n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## sequencer.c ##\n      @@ sequencer.c: static const char *get_todo_path(const struct replay_opts *opts)\n  5:  460979ba964 !  5:  44df42ca503 interpret-trailers: access trailer_info with new helpers\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          interpret-trailers: access trailer_info with new helpers\n     @@ Commit message\n          implementation (and thus hidden from the API).\n      \n          Helped-by: Christian Couder <chriscool@tuxfamily.org>\n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## builtin/interpret-trailers.c ##\n      @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n  6:  d217858c637 !  6:  9ed7cef9d29 trailer: make parse_trailers() return trailer_info pointer\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          trailer: make parse_trailers() return trailer_info pointer\n     @@ Commit message\n          format_trailers_from_commit() and trailer_iterator_init() accordingly.\n      \n          Helped-by: Christian Couder <chriscool@tuxfamily.org>\n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## builtin/interpret-trailers.c ##\n      @@ builtin/interpret-trailers.c: static void interpret_trailers(const struct process_trailer_options *opts,\n  7:  49c66c48cc1 !  7:  246ac9a5d07 trailer: make trailer_info struct private\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          trailer: make trailer_info struct private\n     @@ Commit message\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     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## trailer.c ##\n      @@\n  8:  56e1cca4b7b !  8:  ca6f0c4208c trailer: retire trailer_info_get() from API\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          trailer: retire trailer_info_get() from API\n     @@ Commit message\n          We have to also reposition it to be above parse_trailers(), which\n          depends on it.\n      \n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## trailer.c ##\n      @@ trailer.c: static struct trailer_info *trailer_info_new(void)\n  9:  35304837e08 !  9:  c1a0f1bed04 trailer: document parse_trailers() usage\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          trailer: document parse_trailers() usage\n     @@ Commit message\n          comments a bit easier to read (because \"head\" itself doesn't really have\n          any domain-specific meaning here).\n      \n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## trailer.c ##\n      @@ trailer.c: static struct trailer_info *trailer_info_get(const struct process_trailer_option\n 10:  4d53707f836 ! 10:  310b632ddfd trailer unit tests: inspect iterator contents\n     @@\n       ## Metadata ##\n     -Author: Linus Arver <linusa@google.com>\n     +Author: Linus Arver <linus@ucla.edu>\n      \n       ## Commit message ##\n          trailer unit tests: inspect iterator contents\n     @@ Commit message\n          iteration.\n      \n          Helped-by: Junio C Hamano <gitster@pobox.com>\n     -    Signed-off-by: Linus Arver <linusa@google.com>\n     +    Signed-off-by: Linus Arver <linus@ucla.edu>\n      \n       ## t/unit-tests/t-trailer.c ##\n      @@\n       #include \"test-lib.h\"\n       #include \"trailer.h\"\n       \n     --static void t_trailer_iterator(const char *msg, size_t num_expected_objects)\n     -+struct trailer_assertions {\n     +-static void t_trailer_iterator(const char *msg, size_t num_expected)\n     ++struct contents {\n      +\tconst char *raw;\n      +\tconst char *key;\n      +\tconst char *val;\n      +};\n      +\n     -+static void t_trailer_iterator(const char *msg, size_t num_expected_objects,\n     -+\t\t\t       struct trailer_assertions *trailer_assertions)\n     ++static void t_trailer_iterator(const char *msg, size_t num_expected,\n     ++\t\t\t       struct contents *contents)\n       {\n       \tstruct trailer_iterator iter;\n       \tsize_t i = 0;\n     @@ t/unit-tests/t-trailer.c\n       \ttrailer_iterator_init(&iter, msg);\n      -\twhile (trailer_iterator_advance(&iter))\n      +\twhile (trailer_iterator_advance(&iter)) {\n     -+\t\tif (num_expected_objects) {\n     -+\t\t\tcheck_str(iter.raw, trailer_assertions[i].raw);\n     -+\t\t\tcheck_str(iter.key.buf, trailer_assertions[i].key);\n     -+\t\t\tcheck_str(iter.val.buf, trailer_assertions[i].val);\n     ++\t\tif (num_expected) {\n     ++\t\t\tcheck_str(iter.raw, contents[i].raw);\n     ++\t\t\tcheck_str(iter.key.buf, contents[i].key);\n     ++\t\t\tcheck_str(iter.val.buf, contents[i].val);\n      +\t\t}\n       \t\ti++;\n      +\t}\n       \ttrailer_iterator_release(&iter);\n       \n     - \tcheck_uint(i, ==, num_expected_objects);\n     -@@ t/unit-tests/t-trailer.c: static void t_trailer_iterator(const char *msg, size_t num_expected_objects)\n     + \tcheck_uint(i, ==, num_expected);\n     +@@ t/unit-tests/t-trailer.c: static void t_trailer_iterator(const char *msg, size_t num_expected)\n       \n       static void run_t_trailer_iterator(void)\n       {\n     @@ t/unit-tests/t-trailer.c: static void t_trailer_iterator(const char *msg, size_t\n       \tstatic struct test_cases {\n       \t\tconst char *name;\n       \t\tconst char *msg;\n     - \t\tsize_t num_expected_objects;\n     -+\t\tstruct trailer_assertions trailer_assertions[10];\n     + \t\tsize_t num_expected;\n     ++\t\tstruct contents contents[10];\n       \t} tc[] = {\n       \t\t{\n       \t\t\t\"empty input\",\n     @@ t/unit-tests/t-trailer.c: static void run_t_trailer_iterator(void)\n       \n       \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n       \t\tTEST(t_trailer_iterator(tc[i].msg,\n     --\t\t\t\t\ttc[i].num_expected_objects),\n     -+\t\t\t\t\ttc[i].num_expected_objects,\n     -+\t\t\t\t\ttc[i].trailer_assertions),\n     +-\t\t\t\t\ttc[i].num_expected),\n     ++\t\t\t\t\ttc[i].num_expected,\n     ++\t\t\t\t\ttc[i].contents),\n       \t\t     \"%s\", tc[i].name);\n       \t}\n       }\n\n-- \ngitgitgadget\n"},{"id":"493860","messageId":"b503b539c6fff7a3894c613dfc603a8241c790a1.1714625667.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 02/10] trailer: add unit tests for trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:19Z","receivedAt":"2024-05-02T04:54:33Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\n\nTest the number of trailers found by the iterator (to be more precise,\nthe parsing mechanism which the iterator just walks over) when given\nsome some arbitrary log message.\n\nWe test the iterator because it is a public interface function exposed\nby the trailer API (we generally don't want to test internal\nimplementation details which are, unlike the API, subject to drastic\nchanges).\n\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n Makefile                 |   1 +\n t/unit-tests/t-trailer.c | 174 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 175 insertions(+)\n create mode 100644 t/unit-tests/t-trailer.c\n\ndiff --git a/Makefile b/Makefile\nindex d3a3f16f076..5418ddd03be 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1347,6 +1347,7 @@ UNIT_TEST_PROGRAMS += t-ctype\n UNIT_TEST_PROGRAMS += t-mem-pool\n UNIT_TEST_PROGRAMS += t-prio-queue\n UNIT_TEST_PROGRAMS += t-strbuf\n+UNIT_TEST_PROGRAMS += t-trailer\n UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))\n UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nnew file mode 100644\nindex 00000000000..c1f897235c7\n--- /dev/null\n+++ b/t/unit-tests/t-trailer.c\n@@ -0,0 +1,174 @@\n+#include \"test-lib.h\"\n+#include \"trailer.h\"\n+\n+static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n+{\n+\tstruct trailer_iterator iter;\n+\tsize_t i = 0;\n+\n+\ttrailer_iterator_init(&iter, msg);\n+\twhile (trailer_iterator_advance(&iter))\n+\t\ti++;\n+\ttrailer_iterator_release(&iter);\n+\n+\tcheck_uint(i, ==, num_expected_trailers);\n+}\n+\n+static void run_t_trailer_iterator(void)\n+{\n+\tstatic struct test_cases {\n+\t\tconst char *name;\n+\t\tconst char *msg;\n+\t\tsize_t num_expected_trailers;\n+\t} tc[] = {\n+\t\t{\n+\t\t\t\"empty input\",\n+\t\t\t\"\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"no newline at beginning\",\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"newline at beginning\",\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t3\n+\t\t},\n+\t\t{\n+\t\t\t\"without body text\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\",\n+\t\t\t3\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, without divider\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t4\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, without divider (second trailer block)\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"Fixes: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * Because this is the last trailer block, it takes\n+\t\t\t * precedence over the first one encountered above.\n+\t\t\t */\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t2\n+\t\t},\n+\t\t{\n+\t\t\t\"with body text, with divider\",\n+\t\t\t\"my subject\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"my body which is long\\n\"\n+\t\t\t\"and contains some special\\n\"\n+\t\t\t\"chars like : = ? !\\n\"\n+\t\t\t\"hello\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t\"---\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This trailer still counts because the iterator\n+\t\t\t * always ignores the divider.\n+\t\t\t */\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t1\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines in trailer block\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * Even though this trailer block has a non-trailer line\n+\t\t\t * in it, it's still a valid trailer block because it's\n+\t\t\t * at least 25% trailers and is Git-generated (see\n+\t\t\t * git_generated_prefixes[] in trailer.c).\n+\t\t\t */\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t1\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines (one too many) in trailer block\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This block has only 20% trailers, so it's below the\n+\t\t\t * 25% threshold.\n+\t\t\t */\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"not a trailer line\\n\"\n+\t\t\t\"Signed-off-by: x\\n\",\n+\t\t\t0\n+\t\t},\n+\t\t{\n+\t\t\t\"with non-trailer lines (only 1) in trailer block, but no Git-generated trailers\",\n+\t\t\t\"subject: foo bar\\n\"\n+\t\t\t\"\\n\"\n+\t\t\t/*\n+\t\t\t * This block has only 1 non-trailer out of 10 (IOW, 90%\n+\t\t\t * trailers) but is not considered a trailer block\n+\t\t\t * because the 25% threshold only applies to cases where\n+\t\t\t * there was a Git-generated trailer.\n+\t\t\t */\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Reviewed-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Helped-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"Acked-by: x\\n\"\n+\t\t\t\"not a trailer line\\n\",\n+\t\t\t0\n+\t\t},\n+\t};\n+\n+\tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n+\t\tTEST(t_trailer_iterator(tc[i].msg,\n+\t\t\t\t\ttc[i].num_expected_trailers),\n+\t\t     \"%s\", tc[i].name);\n+\t}\n+}\n+\n+int cmd_main(int argc, const char **argv)\n+{\n+\trun_t_trailer_iterator();\n+\treturn test_done();\n+}\n-- \ngitgitgadget\n\n"},{"id":"493861","messageId":"4aeb48050b14e44ec65cfa651a4d98587a6cd860.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 03/10] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:20Z","receivedAt":"2024-05-02T04:54:35Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\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 commit 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 even with this\ncommit).\n\nRename \"num_expected_trailers\" to \"num_expected\" in\nt/unit-tests/t-trailer.c because the items we iterate over now include\nnon-trailer lines.\n\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n t/unit-tests/t-trailer.c | 16 +++++++++++-----\n trailer.c                | 12 +++++-------\n trailer.h                |  7 +++++++\n 3 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nindex c1f897235c7..4f640d2a4b8 100644\n--- a/t/unit-tests/t-trailer.c\n+++ b/t/unit-tests/t-trailer.c\n@@ -1,7 +1,7 @@\n #include \"test-lib.h\"\n #include \"trailer.h\"\n \n-static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n+static void t_trailer_iterator(const char *msg, size_t num_expected)\n {\n \tstruct trailer_iterator iter;\n \tsize_t i = 0;\n@@ -11,7 +11,7 @@ static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n \t\ti++;\n \ttrailer_iterator_release(&iter);\n \n-\tcheck_uint(i, ==, num_expected_trailers);\n+\tcheck_uint(i, ==, num_expected);\n }\n \n static void run_t_trailer_iterator(void)\n@@ -19,7 +19,7 @@ static void run_t_trailer_iterator(void)\n \tstatic struct test_cases {\n \t\tconst char *name;\n \t\tconst char *msg;\n-\t\tsize_t num_expected_trailers;\n+\t\tsize_t num_expected;\n \t} tc[] = {\n \t\t{\n \t\t\t\"empty input\",\n@@ -119,7 +119,13 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t1\n+\t\t\t/*\n+\t\t\t * Even though there is only really 1 real \"trailer\"\n+\t\t\t * (Signed-off-by), we still have 4 trailer objects\n+\t\t\t * because we still want to iterate through the entire\n+\t\t\t * block.\n+\t\t\t */\n+\t\t\t4\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines (one too many) in trailer block\",\n@@ -162,7 +168,7 @@ static void run_t_trailer_iterator(void)\n \n \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n \t\tTEST(t_trailer_iterator(tc[i].msg,\n-\t\t\t\t\ttc[i].num_expected_trailers),\n+\t\t\t\t\ttc[i].num_expected),\n \t\t     \"%s\", tc[i].name);\n \t}\n }\ndiff --git a/trailer.c b/trailer.c\nindex 3e4dab9c065..4700c441442 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1146,17 +1146,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..7e36da7d13c 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -125,6 +125,13 @@ 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 (as the \"key\" and \"val\"\n+\t * fields below). If a line fails to parse as a trailer, then the \"key\"\n+\t * will be the entire line and \"val\" will be the empty string.\n+\t */\n+\tconst char *raw;\n \tstruct strbuf key;\n \tstruct strbuf val;\n \n-- \ngitgitgadget\n\n"},{"id":"493862","messageId":"a3d080d4d6cace4b331a5dd54e45d24db53c38f4.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 04/10] sequencer: use the trailer iterator","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:21Z","receivedAt":"2024-05-02T04:54:36Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\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 (char **trailers)\nin trailer_info.\n\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n sequencer.c | 27 ++++++++++++---------------\n 1 file changed, 12 insertions(+), 15 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex ea1441e6174..4c1f6c675e7 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":"493863","messageId":"44df42ca503a6007e897f8b5b7c6a8c7d7c27f6e.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 05/10] interpret-trailers: access trailer_info with new helpers","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:22Z","receivedAt":"2024-05-02T04:54:37Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\n\nInstead of directly accessing trailer_info members, access them\nindirectly through new helper functions exposed by the trailer API.\n\nThis is the first of two preparatory commits which will allow us to\nuse the so-called \"pimpl\" (pointer to implementation) idiom for the\ntrailer API, by making the trailer_info struct private to the trailer\nimplementation (and thus hidden from the API).\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n builtin/interpret-trailers.c | 12 ++++++------\n trailer.c                    | 21 +++++++++++++++++++++\n trailer.h                    |  4 ++++\n 3 files changed, 31 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 11f4ce9e4a2..f3240682e35 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 = trailer_info_new();\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+\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+\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 4700c441442..95b4c9b8f19 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,6 +952,12 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n+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@@ -1000,6 +1006,21 @@ void free_trailers(struct list_head *trailers)\n \t}\n }\n \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 void trailer_info_get(const struct process_trailer_options *opts,\n \t\t      const char *str,\n \t\t      struct trailer_info *info)\ndiff --git a/trailer.h b/trailer.h\nindex 7e36da7d13c..9ba96721398 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -97,6 +97,10 @@ void parse_trailers(const struct process_trailer_options *,\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+struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n-- \ngitgitgadget\n\n"},{"id":"493864","messageId":"9ed7cef9d294bc4cc8920975451e610974bb75e0.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 06/10] trailer: make parse_trailers() return trailer_info pointer","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:23Z","receivedAt":"2024-05-02T04:54:38Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\n\nThis is the second and final preparatory commit for making the\ntrailer_info struct private to the trailer implementation.\n\nMake trailer_info_get() do the actual work of allocating a new\ntrailer_info struct, and return a pointer to it. Because\nparse_trailers() wraps around trailer_info_get(), it too can return this\npointer to the caller. From the trailer API user's perspective, the call\nto trailer_info_new() can be replaced with parse_trailers(); do so in\ninterpret-trailers.\n\nBecause trailer_info_new() is no longer called by interpret-trailers,\nremove this function from the trailer API.\n\nWith this change, we no longer allocate trailer_info on the stack ---\nall uses of it are via a pointer where the actual data is always\nallocated at runtime through trailer_info_new(). Make\ntrailer_info_release() free this dynamically allocated memory.\n\nFinally, due to the way the function signatures of parse_trailers() and\ntrailer_info_get() have changed, update the callsites in\nformat_trailers_from_commit() and trailer_iterator_init() accordingly.\n\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n builtin/interpret-trailers.c |  4 ++--\n trailer.c                    | 41 +++++++++++++++++++-----------------\n trailer.h                    | 17 ++++++---------\n 3 files changed, 31 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex f3240682e35..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 = trailer_info_new();\n+\tstruct trailer_info *info;\n \tFILE *outfile = stdout;\n \n \ttrailer_config_init();\n@@ -151,7 +151,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-\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)\ndiff --git a/trailer.c b/trailer.c\nindex 95b4c9b8f19..9179dd802c6 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -952,7 +952,7 @@ static void unfold_value(struct strbuf *val)\n \tstrbuf_release(&out);\n }\n \n-struct trailer_info *trailer_info_new(void)\n+static struct trailer_info *trailer_info_new(void)\n {\n \tstruct trailer_info *info = xcalloc(1, sizeof(*info));\n \treturn info;\n@@ -962,16 +962,16 @@ struct trailer_info *trailer_info_new(void)\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@@ -995,6 +995,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@@ -1021,10 +1023,10 @@ int blank_line_before_trailer_block(struct trailer_info *info)\n \treturn info->blank_line_before_trailer;\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+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@@ -1063,6 +1065,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@@ -1071,6 +1075,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@@ -1138,21 +1143,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,\n \t\t\t\t struct strbuf *out)\n {\n \tLIST_HEAD(trailer_objects);\n-\tstruct trailer_info info;\n-\n-\tparse_trailers(opts, &info, msg, &trailer_objects);\n+\tstruct trailer_info *info = parse_trailers(opts, 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 \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, &trailer_objects, out);\n \n \tfree_trailers(&trailer_objects);\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@@ -1161,14 +1164,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@@ -1185,7 +1188,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 9ba96721398..c8c0018c54d 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -89,18 +89,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-\n-void trailer_info_get(const struct process_trailer_options *,\n-\t\t      const char *str,\n-\t\t      struct trailer_info *);\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 int blank_line_before_trailer_block(struct trailer_info *);\n-struct trailer_info *trailer_info_new(void);\n \n void trailer_info_release(struct trailer_info *info);\n \n@@ -141,7 +138,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":"493865","messageId":"246ac9a5d075870e07fe1485b0687f035facd3b8.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 07/10] trailer: make trailer_info struct private","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:24Z","receivedAt":"2024-05-02T04:54:39Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\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 few 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, and\n\n  (C) without good documentation on the API, the opaque struct is\n      hostile to programmers by going opposite to the \"Show me your\n      data structures, and I won't usually need your code; it'll\n      be obvious.\" mantra [2].\n\n(The disadvantages have already been observed in the two preparatory\ncommits that precede this one.) This commit believes that the benefits\noutweigh the disadvantages for designing 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 commit 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\n[2] Raymond, Eric S. \"The Cathedral and the Bazaar: Musings on Linux and\n    Open Source by an Accidental Revolutionary\". O'Reilly, 1999.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n trailer.c | 21 +++++++++++++++++++++\n trailer.h | 23 ++---------------------\n 2 files changed, 23 insertions(+), 21 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 9179dd802c6..6167b707ae0 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;\ndiff --git a/trailer.h b/trailer.h\nindex c8c0018c54d..4e0a6789d7f 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-- \ngitgitgadget\n\n"},{"id":"493866","messageId":"ca6f0c4208c50bfb35af044e0a8962395eea9375.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 08/10] trailer: retire trailer_info_get() from API","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:25Z","receivedAt":"2024-05-02T04:54:41Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\n\nMake trailer_info_get() \"static\" to be file-scoped to trailer.c, because\nno one outside of trailer.c uses it. Remove its declaration from\n<trailer.h>.\n\nWe have to also reposition it to be above parse_trailers(), which\ndepends on it.\n\nSigned-off-by: Linus Arver <linus@ucla.edu>\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 6167b707ae0..33b6aa7e8bd 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -979,6 +979,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@@ -1044,52 +1090,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 4e0a6789d7f..b0ec7658c67 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":"493867","messageId":"c1a0f1bed043ee00b6aaf0a65aa3f8aa3b396497.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 09/10] trailer: document parse_trailers() usage","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:26Z","receivedAt":"2024-05-02T04:54:43Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\n\nExplain how to use parse_trailers(), because earlier we made the\ntrailer_info struct opaque. That is, because clients can no longer peek\ninside it, we should give them guidance about how the (pointer to the)\nopaque struct can still be useful to them.\n\nRename \"head\" struct to \"trailer_objects\" to make the wording of the new\ncomments a bit easier to read (because \"head\" itself doesn't really have\nany domain-specific meaning here).\n\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n trailer.c |  8 ++++----\n trailer.h | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 54 insertions(+), 5 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 33b6aa7e8bd..406745264aa 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1026,12 +1026,12 @@ static struct trailer_info *trailer_info_get(const struct process_trailer_option\n }\n \n /*\n- * Parse trailers in \"str\", populating the trailer info and \"head\"\n+ * Parse trailers in \"str\", populating the trailer info and \"trailer_objects\"\n  * linked 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+\t\t\t\t    struct list_head *trailer_objects)\n {\n \tstruct trailer_info *info;\n \tstruct strbuf tok = STRBUF_INIT;\n@@ -1051,13 +1051,13 @@ struct trailer_info *parse_trailers(const struct process_trailer_options *opts,\n \t\t\t\t      separator_pos);\n \t\t\tif (opts->unfold)\n \t\t\t\tunfold_value(&val);\n-\t\t\tadd_trailer_item(head,\n+\t\t\tadd_trailer_item(trailer_objects,\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_strip_suffix(&val, \"\\n\");\n-\t\t\tadd_trailer_item(head,\n+\t\t\tadd_trailer_item(trailer_objects,\n \t\t\t\t\t NULL,\n \t\t\t\t\t strbuf_detach(&val, NULL));\n \t\t}\ndiff --git a/trailer.h b/trailer.h\nindex b0ec7658c67..82104912d70 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -70,14 +70,63 @@ 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+/*\n+ * Given some input string \"str\", return a pointer to an opaque trailer_info\n+ * structure. Also populate the trailer_objects list with parsed trailer\n+ * objects. Internally this calls trailer_info_get() to get the opaque pointer,\n+ * but does some extra work to populate the trailer_objects linked list.\n+ *\n+ * The opaque trailer_info pointer can be used to check the position of the\n+ * trailer block as offsets relative to the beginning of \"str\" in\n+ * trailer_block_start() and trailer_block_end().\n+ * blank_line_before_trailer_block() returns 1 if there is a blank line just\n+ * before the trailer block. All of these functions are useful for preserving\n+ * the input before and after the trailer block, if we were to write out the\n+ * original input (but with the trailer block itself modified); see\n+ * builtin/interpret-trailers.c for an example.\n+ *\n+ * For iterating through the parsed trailer block (if you don't care about the\n+ * position of the trailer block itself in the context of the larger string text\n+ * from which it was parsed), please see trailer_iterator_init() which uses the\n+ * trailer_info struct internally.\n+ *\n+ * Lastly, callers should call trailer_info_release() when they are done using\n+ * the opaque pointer.\n+ *\n+ * NOTE: Callers should treat both trailer_info and trailer_objects as\n+ * read-only items, because there is some overlap between the two (trailer_info\n+ * has \"char **trailers\" string array, and trailer_objects will have the same\n+ * data but as a linked list of trailer_item objects). This API does not perform\n+ * any synchronization between the two. In the future we should be able to\n+ * reduce the duplication and use just the linked list.\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+\t\t\t\t    struct list_head *trailer_objects);\n \n+/*\n+ * Return the offset of the start of the trailer block. That is, 0 is the start\n+ * of the input (\"str\" in parse_trailers()) and some other positive number\n+ * indicates how many bytes we have to skip over before we get to the beginning\n+ * of the trailer block.\n+ */\n size_t trailer_block_start(struct trailer_info *);\n+\n+/*\n+ * Return the end of the trailer block, again relative to the start of the\n+ * input.\n+ */\n size_t trailer_block_end(struct trailer_info *);\n+\n+/*\n+ * Return 1 if the trailer block had an extra newline (blank line) just before\n+ * it.\n+ */\n int blank_line_before_trailer_block(struct trailer_info *);\n \n+/*\n+ * Free trailer_info struct.\n+ */\n void trailer_info_release(struct trailer_info *info);\n \n void trailer_config_init(void);\n-- \ngitgitgadget\n\n"},{"id":"493868","messageId":"310b632ddfdeae3251c18690b1fe0b0113a602b6.1714625668.git.gitgitgadget@gmail.com","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"[PATCH v4 10/10] trailer unit tests: inspect iterator contents","fromName":"Linus Arver via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-02T04:54:27Z","receivedAt":"2024-05-02T04:54:44Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"From: Linus Arver <linus@ucla.edu>\n\nPreviously we only checked whether we would iterate a certain (expected)\nnumber of times.\n\nAlso check the parsed \"raw\", \"key\" and \"val\" fields during each\niteration.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Linus Arver <linus@ucla.edu>\n---\n t/unit-tests/t-trailer.c | 161 +++++++++++++++++++++++++++++++++++----\n 1 file changed, 148 insertions(+), 13 deletions(-)\n\ndiff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\nindex 4f640d2a4b8..2ecca359d96 100644\n--- a/t/unit-tests/t-trailer.c\n+++ b/t/unit-tests/t-trailer.c\n@@ -1,14 +1,27 @@\n #include \"test-lib.h\"\n #include \"trailer.h\"\n \n-static void t_trailer_iterator(const char *msg, size_t num_expected)\n+struct contents {\n+\tconst char *raw;\n+\tconst char *key;\n+\tconst char *val;\n+};\n+\n+static void t_trailer_iterator(const char *msg, size_t num_expected,\n+\t\t\t       struct contents *contents)\n {\n \tstruct trailer_iterator iter;\n \tsize_t i = 0;\n \n \ttrailer_iterator_init(&iter, msg);\n-\twhile (trailer_iterator_advance(&iter))\n+\twhile (trailer_iterator_advance(&iter)) {\n+\t\tif (num_expected) {\n+\t\t\tcheck_str(iter.raw, contents[i].raw);\n+\t\t\tcheck_str(iter.key.buf, contents[i].key);\n+\t\t\tcheck_str(iter.val.buf, contents[i].val);\n+\t\t}\n \t\ti++;\n+\t}\n \ttrailer_iterator_release(&iter);\n \n \tcheck_uint(i, ==, num_expected);\n@@ -16,22 +29,26 @@ static void t_trailer_iterator(const char *msg, size_t num_expected)\n \n static void run_t_trailer_iterator(void)\n {\n+\n \tstatic struct test_cases {\n \t\tconst char *name;\n \t\tconst char *msg;\n \t\tsize_t num_expected;\n+\t\tstruct contents contents[10];\n \t} tc[] = {\n \t\t{\n \t\t\t\"empty input\",\n \t\t\t\"\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t\t{\n \t\t\t\"no newline at beginning\",\n \t\t\t\"Fixes: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t\t{\n \t\t\t\"newline at beginning\",\n@@ -39,7 +56,27 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Fixes: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\",\n-\t\t\t3\n+\t\t\t3,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Fixes: x\\n\",\n+\t\t\t\t\t.key = \"Fixes\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Acked-by: x\\n\",\n+\t\t\t\t\t.key = \"Acked-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Reviewed-by: x\\n\",\n+\t\t\t\t\t.key = \"Reviewed-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"without body text\",\n@@ -48,7 +85,27 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Fixes: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\",\n-\t\t\t3\n+\t\t\t3,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Fixes: x\\n\",\n+\t\t\t\t\t.key = \"Fixes\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Acked-by: x\\n\",\n+\t\t\t\t\t.key = \"Acked-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Reviewed-by: x\\n\",\n+\t\t\t\t\t.key = \"Reviewed-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with body text, without divider\",\n@@ -63,7 +120,32 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Reviewed-by: x\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t4\n+\t\t\t4,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Fixes: x\\n\",\n+\t\t\t\t\t.key = \"Fixes\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Acked-by: x\\n\",\n+\t\t\t\t\t.key = \"Acked-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Reviewed-by: x\\n\",\n+\t\t\t\t\t.key = \"Reviewed-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with body text, without divider (second trailer block)\",\n@@ -85,7 +167,22 @@ static void run_t_trailer_iterator(void)\n \t\t\t */\n \t\t\t\"Helped-by: x\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t2\n+\t\t\t2,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Helped-by: x\\n\",\n+\t\t\t\t\t.key = \"Helped-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with body text, with divider\",\n@@ -103,7 +200,17 @@ static void run_t_trailer_iterator(void)\n \t\t\t * always ignores the divider.\n \t\t\t */\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t1\n+\t\t\t1,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines in trailer block\",\n@@ -125,7 +232,32 @@ static void run_t_trailer_iterator(void)\n \t\t\t * because we still want to iterate through the entire\n \t\t\t * block.\n \t\t\t */\n-\t\t\t4\n+\t\t\t4,\n+\t\t\t{\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"not a trailer line\\n\",\n+\t\t\t\t\t.key = \"not a trailer line\",\n+\t\t\t\t\t.val = \"\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"not a trailer line\\n\",\n+\t\t\t\t\t.key = \"not a trailer line\",\n+\t\t\t\t\t.val = \"\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"not a trailer line\\n\",\n+\t\t\t\t\t.key = \"not a trailer line\",\n+\t\t\t\t\t.val = \"\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t.raw = \"Signed-off-by: x\\n\",\n+\t\t\t\t\t.key = \"Signed-off-by\",\n+\t\t\t\t\t.val = \"x\",\n+\t\t\t\t},\n+\t\t\t\t{\n+\t\t\t\t\t0\n+\t\t\t\t},\n+\t\t\t},\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines (one too many) in trailer block\",\n@@ -140,7 +272,8 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"not a trailer line\\n\"\n \t\t\t\"Signed-off-by: x\\n\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t\t{\n \t\t\t\"with non-trailer lines (only 1) in trailer block, but no Git-generated trailers\",\n@@ -162,13 +295,15 @@ static void run_t_trailer_iterator(void)\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"Acked-by: x\\n\"\n \t\t\t\"not a trailer line\\n\",\n-\t\t\t0\n+\t\t\t0,\n+\t\t\t{{0}},\n \t\t},\n \t};\n \n \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n \t\tTEST(t_trailer_iterator(tc[i].msg,\n-\t\t\t\t\ttc[i].num_expected),\n+\t\t\t\t\ttc[i].num_expected,\n+\t\t\t\t\ttc[i].contents),\n \t\t     \"%s\", tc[i].name);\n \t}\n }\n-- \ngitgitgadget\n"},{"id":"493927","messageId":"xmqqttjgduhw.fsf@gitster.g","threadId":"61132","inReplyTo":"b503b539c6fff7a3894c613dfc603a8241c790a1.1714625667.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 02/10] trailer: add unit tests for trailer iterator","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T16:54:51Z","receivedAt":"2024-05-02T16:54:54Z","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 <linus@ucla.edu>\n>\n> Test the number of trailers found by the iterator (to be more precise,\n> the parsing mechanism which the iterator just walks over) when given\n> some some arbitrary log message.\n\n\"some some\", which I think you (or perhaps I while queuing) fixed in\nthe previous round.\n"},{"id":"493928","messageId":"xmqqmsp8dtj6.fsf@gitster.g","threadId":"61132","inReplyTo":"pull.1696.v4.git.1714625667.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/10] Make trailer_info struct private (plus sequencer cleanup)","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T17:15:41Z","receivedAt":"2024-05-02T17:15:47Z","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> NOTE: This series is based on the la/format-trailer-info topic branch (see\n> its discussion at [1]).\n\nI took Christian's \"I ... had only a few minor comments on some\npatches.\" [*] to imply that everything else without comments were\ngood, so hopefully this is good to go.  I've amended \"some some\" in\n[2/10] while queueing myself.\n\nLet me mark it for 'next' unless there is some objection in a few\ndays.\n\nThanks.\n\n\n[Reference]\n\n * https://lore.kernel.org/git/CAP8UFD3Vzs2KvsE2qmx_54UX9ktAngr2FkpKfWVjRMD=t1B7Wg@mail.gmail.com/\n"},{"id":"494081","messageId":"18343148-80d1-4558-b834-caaf8322467a@gmail.com","threadId":"61132","inReplyTo":"4aeb48050b14e44ec65cfa651a4d98587a6cd860.1714625668.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 03/10] trailer: teach iterator about non-trailer lines","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-04T15:33:47Z","receivedAt":"2024-05-04T15:33:56Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Linus\n\nSorry I'm late to the party here I've left a couple of thoughts below \nbut I don't want to derail this series if everyone else is happy.\n\nOn 02/05/2024 05:54, Linus Arver via GitGitGadget wrote:\n> From: Linus Arver <linus@ucla.edu>\n> \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> 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> 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> 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\nComparing the raw line is error prone as it ignores custom separators \nand variations in the amount of space between the key and the value. \nTherefore I'd argue that the sequencer should in fact be comparing the \ntrailer key and value separately rather than comparing the whole line. \nThere is an issue that we want to add a new Signed-off-by: trailer for \n\"C.O. Mitter\" when the trailers look like\n\n\tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n\tnon-trailer-line\n\nbut not when they look like\n\n\tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n\nso we still need some way of indicating that there was a non-trailer \nline after the last trailer though.\n\n> The next commit 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> 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\nAn interface that lets the caller pass a flag if they want to know about \nnon-trailer lines might be easier to use for the callers that don't want \nto worry about such lines and wouldn't need a justification as to why it \nwas safe for existing callers.\n\nBest Wishes\n\nPhillip\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> \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 even with this\n> commit).\n> \n> Rename \"num_expected_trailers\" to \"num_expected\" in\n> t/unit-tests/t-trailer.c because the items we iterate over now include\n> non-trailer lines.\n> \n> Signed-off-by: Linus Arver <linus@ucla.edu>\n> ---\n>   t/unit-tests/t-trailer.c | 16 +++++++++++-----\n>   trailer.c                | 12 +++++-------\n>   trailer.h                |  7 +++++++\n>   3 files changed, 23 insertions(+), 12 deletions(-)\n> \n> diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c\n> index c1f897235c7..4f640d2a4b8 100644\n> --- a/t/unit-tests/t-trailer.c\n> +++ b/t/unit-tests/t-trailer.c\n> @@ -1,7 +1,7 @@\n>   #include \"test-lib.h\"\n>   #include \"trailer.h\"\n>   \n> -static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n> +static void t_trailer_iterator(const char *msg, size_t num_expected)\n>   {\n>   \tstruct trailer_iterator iter;\n>   \tsize_t i = 0;\n> @@ -11,7 +11,7 @@ static void t_trailer_iterator(const char *msg, size_t num_expected_trailers)\n>   \t\ti++;\n>   \ttrailer_iterator_release(&iter);\n>   \n> -\tcheck_uint(i, ==, num_expected_trailers);\n> +\tcheck_uint(i, ==, num_expected);\n>   }\n>   \n>   static void run_t_trailer_iterator(void)\n> @@ -19,7 +19,7 @@ static void run_t_trailer_iterator(void)\n>   \tstatic struct test_cases {\n>   \t\tconst char *name;\n>   \t\tconst char *msg;\n> -\t\tsize_t num_expected_trailers;\n> +\t\tsize_t num_expected;\n>   \t} tc[] = {\n>   \t\t{\n>   \t\t\t\"empty input\",\n> @@ -119,7 +119,13 @@ static void run_t_trailer_iterator(void)\n>   \t\t\t\"not a trailer line\\n\"\n>   \t\t\t\"not a trailer line\\n\"\n>   \t\t\t\"Signed-off-by: x\\n\",\n> -\t\t\t1\n> +\t\t\t/*\n> +\t\t\t * Even though there is only really 1 real \"trailer\"\n> +\t\t\t * (Signed-off-by), we still have 4 trailer objects\n> +\t\t\t * because we still want to iterate through the entire\n> +\t\t\t * block.\n> +\t\t\t */\n> +\t\t\t4\n>   \t\t},\n>   \t\t{\n>   \t\t\t\"with non-trailer lines (one too many) in trailer block\",\n> @@ -162,7 +168,7 @@ static void run_t_trailer_iterator(void)\n>   \n>   \tfor (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {\n>   \t\tTEST(t_trailer_iterator(tc[i].msg,\n> -\t\t\t\t\ttc[i].num_expected_trailers),\n> +\t\t\t\t\ttc[i].num_expected),\n>   \t\t     \"%s\", tc[i].name);\n>   \t}\n>   }\n> diff --git a/trailer.c b/trailer.c\n> index 3e4dab9c065..4700c441442 100644\n> --- a/trailer.c\n> +++ b/trailer.c\n> @@ -1146,17 +1146,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;\n> diff --git a/trailer.h b/trailer.h\n> index 9f42aa75994..7e36da7d13c 100644\n> --- a/trailer.h\n> +++ b/trailer.h\n> @@ -125,6 +125,13 @@ 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 (as the \"key\" and \"val\"\n> +\t * fields below). If a line fails to parse as a trailer, then the \"key\"\n> +\t * will be the entire line and \"val\" will be the empty string.\n> +\t */\n> +\tconst char *raw;\n>   \tstruct strbuf key;\n>   \tstruct strbuf val;\n>   \n"},{"id":"494083","messageId":"CAMo6p=GJwmStLrW6cDDKrch2cXn_8fe0GsBHi3hpe5Uya72y=w@mail.gmail.com","threadId":"61132","inReplyTo":"18343148-80d1-4558-b834-caaf8322467a@gmail.com","subject":"Re: [PATCH v4 03/10] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver","fromEmail":"linus@ucla.edu","sentAt":"2024-05-05T01:37:56Z","receivedAt":"2024-05-05T01:37:57Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Linus\n>\n> Sorry I'm late to the party here I've left a couple of thoughts below\n> but I don't want to derail this series if everyone else is happy.\n\nHi Phillip, no problem.\n\n> On 02/05/2024 05:54, Linus Arver via GitGitGadget wrote:\n>> From: Linus Arver <linus@ucla.edu>\n>>\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>> 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>> 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>> 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> Comparing the raw line is error prone as it ignores custom separators\n> and variations in the amount of space between the key and the value.\n> Therefore I'd argue that the sequencer should in fact be comparing the\n> trailer key and value separately rather than comparing the whole line.\n\nI agree, but that is likely beyond the scope of this series as the\nbehavior of comparing the whole line was preserved (not introduced) by\nthis series.\n\nFor reference, the \"Signed-off-by: \" is hardcoded in \"sign_off_header\"\nin sequencer.c, and it is again hardcoded in \"git_generated_prefixes\" in\ntrailer.c. We always use the hardcoded key and colon \":\" separator in a\nfew areas, so changing the code to be more precise to check for only the\nkey (to account for variability in the separator and space around it as\nyou pointed out) would be a more involved change (I think many tests\nwould need to be updated).\n\n> There is an issue that we want to add a new Signed-off-by: trailer for\n> \"C.O. Mitter\" when the trailers look like\n>\n> \tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n> \tnon-trailer-line\n>\n> but not when they look like\n>\n> \tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n>\n> so we still need some way of indicating that there was a non-trailer\n> line after the last trailer though.\n\nWhat is the issue, exactly? Also can you clarify if the issue is\nintroduced by this series (did you spot a regression)?\n\n>> The next commit 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>> 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> An interface that lets the caller pass a flag if they want to know about\n> non-trailer lines might be easier to use for the callers that don't want\n> to worry about such lines and wouldn't need a justification as to why it\n> was safe for existing callers.\n\nMakes sense. But perhaps such API enhancements belong in a future\nseries, when other callers that need such flexibility could benefit from\nit?\n\n> Best Wishes\n>\n> Phillip\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>>\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 even with this\n>> commit).\n>>\n>> Rename \"num_expected_trailers\" to \"num_expected\" in\n>> t/unit-tests/t-trailer.c because the items we iterate over now include\n>> non-trailer lines.\n>>\n>> Signed-off-by: Linus Arver <linus@ucla.edu>\n>> [...]\n"},{"id":"494089","messageId":"a75133dc-a0bb-4f61-a616-988f2b4d5688@gmail.com","threadId":"61132","inReplyTo":"CAMo6p=GJwmStLrW6cDDKrch2cXn_8fe0GsBHi3hpe5Uya72y=w@mail.gmail.com","subject":"Re: [PATCH v4 03/10] trailer: teach iterator about non-trailer lines","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-05T14:09:21Z","receivedAt":"2024-05-05T14:09:24Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Linus\n\nOn 05/05/2024 02:37, Linus Arver wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> On 02/05/2024 05:54, Linus Arver via GitGitGadget wrote:\n>>> From: Linus Arver <linus@ucla.edu>\n>>>\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>> Comparing the raw line is error prone as it ignores custom separators\n>> and variations in the amount of space between the key and the value.\n>> Therefore I'd argue that the sequencer should in fact be comparing the\n>> trailer key and value separately rather than comparing the whole line.\n> \n> I agree, but that is likely beyond the scope of this series as the\n> behavior of comparing the whole line was preserved (not introduced) by\n> this series.\n\nRight but this series is changing the trailer iterator api to \naccommodate the sub-optimal sequencer code. My thought was that if the \nsequencer did the right thing we wouldn't need to expose the raw line in \nthe iterator in the first place.\n\n> For reference, the \"Signed-off-by: \" is hardcoded in \"sign_off_header\"\n> in sequencer.c, and it is again hardcoded in \"git_generated_prefixes\" in\n> trailer.c. We always use the hardcoded key and colon \":\" separator in a\n> few areas, so changing the code to be more precise to check for only the\n> key (to account for variability in the separator and space around it as\n> you pointed out) would be a more involved change (I think many tests\n> would need to be updated).\n\nSo the worry is that we'd create a \"Signed-off-by: \" trailer that we \nthen couldn't parse because the user didn't have ':' in trailer.separators?\n\n>> There is an issue that we want to add a new Signed-off-by: trailer for\n>> \"C.O. Mitter\" when the trailers look like\n>>\n>> \tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n>> \tnon-trailer-line\n>>\n>> but not when they look like\n>>\n>> \tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n>>\n>> so we still need some way of indicating that there was a non-trailer\n>> line after the last trailer though.\n> \n> What is the issue, exactly? Also can you clarify if the issue is\n> introduced by this series (did you spot a regression)?\n\nThere is no regression - the issue is with my suggestion. We only want \nto add an SOB trailer if the last trailer does not match the SOB we're \nadding. If we were to use the existing trailer iterator api in the \nsequencer we would not know that we should add an SOB in the first \nexample above as we'd only see the last trailer which matches the SOB \nwe're trying to add. We'd still need some way to tell the caller that \nthere was a non-trailer line following the last trailer.\n\n>>> The next commit 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>>> 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>> An interface that lets the caller pass a flag if they want to know about\n>> non-trailer lines might be easier to use for the callers that don't want\n>> to worry about such lines and wouldn't need a justification as to why it\n>> was safe for existing callers.\n> \n> Makes sense. But perhaps such API enhancements belong in a future\n> series, when other callers that need such flexibility could benefit from\n> it?\n\nFor me the main benefit would be that you don't have to spend time \nexplaining why the changes are safe for existing callers because they \nwould keep the existing iterator behavor.\n\nBest Wishes\n\nPhillip\n"},{"id":"494343","messageId":"CAMo6p=FS3ShvBdutprWBiAVef6A1XjsXB1UJSQBk0s5euN=tog@mail.gmail.com","threadId":"61132","inReplyTo":"a75133dc-a0bb-4f61-a616-988f2b4d5688@gmail.com","subject":"Re: [PATCH v4 03/10] trailer: teach iterator about non-trailer lines","fromName":"Linus Arver","fromEmail":"linus@ucla.edu","sentAt":"2024-05-09T07:11:28Z","receivedAt":"2024-05-09T07:11:29Z","isPatch":true,"sender":{"key":"linus@ucla.edu","avatar":null},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\nSorry for the delay.\n\n> Hi Linus\n>\n> On 05/05/2024 02:37, Linus Arver wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>> On 02/05/2024 05:54, Linus Arver via GitGitGadget wrote:\n>>>> From: Linus Arver <linus@ucla.edu>\n>>>>\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>>> Comparing the raw line is error prone as it ignores custom separators\n>>> and variations in the amount of space between the key and the value.\n>>> Therefore I'd argue that the sequencer should in fact be comparing the\n>>> trailer key and value separately rather than comparing the whole line.\n>>\n>> I agree, but that is likely beyond the scope of this series as the\n>> behavior of comparing the whole line was preserved (not introduced) by\n>> this series.\n>\n> Right but this series is changing the trailer iterator api to\n> accommodate the sub-optimal sequencer code. My thought was that if the\n> sequencer did the right thing we wouldn't need to expose the raw line in\n> the iterator in the first place.\n\nWell, having familiarized myself with the trailer machinery I was more\ncomfortable updating this area than reworking the details of the\nsequencer code.\n\nTo me the sequencer code is a bit hard to read so I feel more\ncomfortable updating the trailer code as I did here in this series. I\nalso have another 40, 50 patches in the trailer area I want to continue\npushing up for review, so I would rather focus on that first before\ncoming back to the sequencer to try to clean it up. My other trailer\npatches make the trailer API more precise and aware of the separator and\nspaces around it, so using those richer interfaces later would make it\neasier to clean up the sequencer area, I think.\n\nSo in summary I would rather not get into refactoring the sequencer at\nthis time.\n\n>> For reference, the \"Signed-off-by: \" is hardcoded in \"sign_off_header\"\n>> in sequencer.c, and it is again hardcoded in \"git_generated_prefixes\" in\n>> trailer.c. We always use the hardcoded key and colon \":\" separator in a\n>> few areas, so changing the code to be more precise to check for only the\n>> key (to account for variability in the separator and space around it as\n>> you pointed out) would be a more involved change (I think many tests\n>> would need to be updated).\n>\n> So the worry is that we'd create a \"Signed-off-by: \" trailer that we\n> then couldn't parse because the user didn't have ':' in trailer.separators?\n\nEven if the user currently doesn't have ':' in trailer.separators, we\nstill hardcode it in as a separator. So the trailer.separators setting\ndoesn't matter.\n\nThe worry is that any further refactorings of sequencer would be quickly\nobsoleted by the to-be-reviewed patches which enrich the trailer API\nfurther which are sitting in my local branch.\n\n>>> There is an issue that we want to add a new Signed-off-by: trailer for\n>>> \"C.O. Mitter\" when the trailers look like\n>>>\n>>> \tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n>>> \tnon-trailer-line\n>>>\n>>> but not when they look like\n>>>\n>>> \tSigned-off-by: C.O. Mitter <c.o.mitter@example.com>\n>>>\n>>> so we still need some way of indicating that there was a non-trailer\n>>> line after the last trailer though.\n>>\n>> What is the issue, exactly? Also can you clarify if the issue is\n>> introduced by this series (did you spot a regression)?\n>\n> There is no regression - the issue is with my suggestion. We only want\n> to add an SOB trailer if the last trailer does not match the SOB we're\n> adding.\n\nIf there is no regression then I don't understand the concern.\n\n> If we were to use the existing trailer iterator api in the\n> sequencer we would not know that we should add an SOB in the first\n> example above as we'd only see the last trailer which matches the SOB\n> we're trying to add.\n\nHmm, both the original code and the code in this patch iterate over\nnon-trailer lines. So the behavior is the same.\n\n> We'd still need some way to tell the caller that\n> there was a non-trailer line following the last trailer.\n\nFWIW in one of the patches I already have currently (to be sent in a\nfuture series), I expand the trailer API to let the caller check if the\ncurrent iteration is on a trailer or non-trailer object (they can do the\ncheck by looking into the key and value). And in another patch I make it\nso that the key field is never populated if the line is a non-trailer\nline. So the capability you seek is achievable with those patches.\n\n>>>> The next commit 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>>>> 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>>> An interface that lets the caller pass a flag if they want to know about\n>>> non-trailer lines might be easier to use for the callers that don't want\n>>> to worry about such lines and wouldn't need a justification as to why it\n>>> was safe for existing callers.\n>>\n>> Makes sense. But perhaps such API enhancements belong in a future\n>> series, when other callers that need such flexibility could benefit from\n>> it?\n>\n> For me the main benefit would be that you don't have to spend time\n> explaining why the changes are safe for existing callers because they\n> would keep the existing iterator behavor.\n\nBut, I've already written the explanation so this justification seems a bit\nmoot, no?\n\nBut ultimately I think it makes sense for the iterator to be able to\niterate over non-trailer lines because that just brings more power to\nthe callers that need or want it. The sequencer code is such an example\n--- whether it is suboptimal or not is a separate matter, I think\n(indeed I did not look too much into why the sequencer stuff wanted to\niterate over non-trailer lines when I was writing this patch).\n\nSo in summary, I'd prefer to keep this series as is. We can of course\nrevisit the sequencer code's use of the trailer API in the future. Thanks.\n\n> Best Wishes\n>\n> Phillip\n"},{"id":"494632","messageId":"f182c3f0-4d9e-491e-9b0d-59560cd93b8d@gmail.com","threadId":"61132","inReplyTo":"CAMo6p=FS3ShvBdutprWBiAVef6A1XjsXB1UJSQBk0s5euN=tog@mail.gmail.com","subject":"Re: [PATCH v4 03/10] trailer: teach iterator about non-trailer lines","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-13T15:11:55Z","receivedAt":"2024-05-13T15:12:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Linus\n\nOn 09/05/2024 08:11, Linus Arver wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> On 05/05/2024 02:37, Linus Arver wrote:\n>>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>>> On 02/05/2024 05:54, Linus Arver via GitGitGadget wrote:\n>>>>> From: Linus Arver <linus@ucla.edu>\n>>>>>\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>>>> Comparing the raw line is error prone as it ignores custom separators\n>>>> and variations in the amount of space between the key and the value.\n>>>> Therefore I'd argue that the sequencer should in fact be comparing the\n>>>> trailer key and value separately rather than comparing the whole line.\n>>>\n>>> I agree, but that is likely beyond the scope of this series as the\n>>> behavior of comparing the whole line was preserved (not introduced) by\n>>> this series.\n>>\n>> Right but this series is changing the trailer iterator api to\n>> accommodate the sub-optimal sequencer code. My thought was that if the\n>> sequencer did the right thing we wouldn't need to expose the raw line in\n>> the iterator in the first place.\n> \n> Well, having familiarized myself with the trailer machinery I was more\n> comfortable updating this area than reworking the details of the\n> sequencer code.\n> \n> To me the sequencer code is a bit hard to read so I feel more\n> comfortable updating the trailer code as I did here in this series. I\n> also have another 40, 50 patches in the trailer area I want to continue\n> pushing up for review, so I would rather focus on that first before\n> coming back to the sequencer to try to clean it up. My other trailer\n> patches make the trailer API more precise and aware of the separator and\n> spaces around it,\n\nI'm a bit confused by this as it looks like the \ntrailer_iterator_advance() already respects trailer.separators and \ntrims the key and value.\n\n> so using those richer interfaces later would make it\n> easier to clean up the sequencer area, I think.\n> \n> So in summary I would rather not get into refactoring the sequencer at\n> this time.\n\nFar enough, I hoped I might have time over the weekend to look at the \nsequencer code in more detail but that didn't work out. So long as we're \nnot building bad abstractions into the trailer iterator to accommodate \nthe sequencer we can come back and clean up append_signoff() later.\n\nBest Wishes\n\nPhillip\n"},{"id":"494633","messageId":"52853f9f-531c-4907-bcc8-b2a3841f03af@gmail.com","threadId":"61132","inReplyTo":"f182c3f0-4d9e-491e-9b0d-59560cd93b8d@gmail.com","subject":"Re: [PATCH v4 03/10] trailer: teach iterator about non-trailer lines","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-13T15:13:24Z","receivedAt":"2024-05-13T15:13:30Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 13/05/2024 16:11, Phillip Wood wrote:\n> Far enough,\n\nSorry that should have been \"Fair enough\"\n\nPhillip\n"}]}