{"thread":{"id":"64367","subject":"[PATCH v5 00/29] rebase: support --trailer","startedAt":"2025-10-22T05:40:13Z","lastAt":"2025-11-04T11:53:55Z","messageCount":33,"participants":["Li Chen","Phillip Wood"],"isPatch":true,"patchVersion":5,"patchTotal":29},"messages":[{"id":"529351","messageId":"20251022053951.602605-1-me@linux.beauty","threadId":"64367","inReplyTo":null,"subject":"[PATCH v5 00/29] rebase: support --trailer","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:20Z","receivedAt":"2025-10-22T05:40:13Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nThis patch series teaches git rebase a new\n--trailer <text> option and, as a prerequisite, moves all trailer\nhandling out of the external interpret-trailers helper and into the\nbuiltin code path, as suggested by Phillip Wood.\n\nPatch 0 switches trailer.c to an in-memory implementation\n(amend_strbuf_with_trailers()). It removes every fork/exec.\n\nPatch 1~8 fix all reviewer comments from v3 for patch 0. \n\nPatch 9 builds on that helper to implement\ngit rebase --trailer. When the option is given we:\nforce the merge backend (apply/am backend lacks a message filter),\nautomatically enable --force-rebase so that fast-forwarded\ncommits are rewritten, and append the requested trailer(s) to every\nrewritten commit.\nState is stored in $state_dir/trailer so an interrupted rebase can\nresume safely. A dedicated test-suite (t3440) exercises plain,\nconflict, --root, invalid-input scenarios and etc.\n\nThe rest patches address all reviewer comments from v3 for patch 9. \n\nAll t/*.sh testcases have run successfully.\n\nv5: fix all Kristoffer's review comments form v4[3] in place and without new patches. \nv4: fix all reviewer comments in v3. [2], and add patch 1~8 & 10~29 to fix review comments.\nv3: merges the remaining trailer paths into one in-process helper, dropping the\n    duplicate code, as pointed by Junio and Phillip [1]\nv2: fix issues pointed by Phillip \nRFC link: https://lore.kernel.org/git/196a5ac1393.f5b4db7d187309.2451613571977217927@linux.beauty/\n\nComments welcome!\n\n[1]: https://lore.kernel.org/git/xmqq8qlzkukw.fsf@gitster.g/\n[2]: https://lore.kernel.org/git/20250803150059.402017-1-me@linux.beauty/\n[3]: https://lore.kernel.org/git/20251014122452.1851103-1-me@linux.beauty/\n\nLi Chen (29):\n  trailer: append trailers in-process and drop the fork to\n    `interpret-trailers`\n  trailer: restore interpret_trailers helper\n  trailer: drop --trailer prefix handling in amend helper\n  trailer: move config_head and arg_head to if storage\n  trailer: use bool for had_trailer_before\n  interpret-trailers: buffer stdout output\n  trailer: mirror interpret-trailers output flow\n  trailer: handle trailer append failures gently\n  rebase: support --trailer\n  rebase: inline trailer state paths\n  rebase: reuse buffer for trailer args\n  rebase: drop redundant strbuf_release call\n  rebase: skip stripping of --trailer option prefix\n  rebase: die on invalid trailer args\n  rebase: validate trailers with configured separators\n  sequencer: add trailers to message before writing file\n  t3440: create expect files at point of use\n  t3440: check apply backend error includes option\n  t3440: use test_commit_message for trailer checks\n  t3440: drop redundant resets and pass branch to rebase where needed\n  t3440: assert trailer on HEAD after conflict rebase\n  rebase: persist --trailer options across restarts\n  t3440: remove redundant --keep-empty\n  t3440: use helper for trailer checks\n  t3440: test --trailer without values\n  t3440: convert ex.com to example.com\n  t3440: ensure trailers persist after rebase continue\n  t3440: exercise trailer config mapping\n  sequencer: honor --trailer with fixup -C\n\n Documentation/git-rebase.adoc |   9 ++-\n builtin/commit.c              |   2 +-\n builtin/interpret-trailers.c  |  94 +++++-------------------\n builtin/rebase.c              |  50 +++++++++++++\n builtin/tag.c                 |   3 +-\n sequencer.c                   |  34 +++++++++\n sequencer.h                   |   4 +-\n t/meson.build                 |   1 +\n t/t3440-rebase-trailer.sh     | 134 ++++++++++++++++++++++++++++++++++\n trailer.c                     | 130 ++++++++++++++++++++++++++++++---\n trailer.h                     |  23 +++++-\n wrapper.c                     |  16 ++++\n wrapper.h                     |   6 ++\n 13 files changed, 412 insertions(+), 94 deletions(-)\n create mode 100755 t/t3440-rebase-trailer.sh\n\n-- \n2.51.0\n\n"},{"id":"529352","messageId":"20251022053951.602605-2-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 01/29] trailer: append trailers in-process and drop the fork to `interpret-trailers`","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:21Z","receivedAt":"2025-10-22T05:40:25Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nRoute all trailer insertion through trailer_process() and make\nbuiltin/interpret-trailers just do file I/O before calling into it.\namend_file_with_trailers() now shares the same code path.\n\nThis removes the fork/exec and tempfile juggling, cutting overhead and\nsimplifying error handling. No functional change. It also\ncentralizes logic to prepare for follow-up rebase --trailer patch.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/interpret-trailers.c | 116 ++++++++------------------------\n trailer.c                    | 125 ++++++++++++++++++++++++++++++++---\n trailer.h                    |  18 ++++-\n 3 files changed, 157 insertions(+), 102 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 41b0750e5a..be0fa83f79 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -10,7 +10,6 @@\n #include \"gettext.h\"\n #include \"parse-options.h\"\n #include \"string-list.h\"\n-#include \"tempfile.h\"\n #include \"trailer.h\"\n #include \"config.h\"\n \n@@ -93,37 +92,6 @@ static int parse_opt_parse(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n-static struct tempfile *trailers_tempfile;\n-\n-static FILE *create_in_place_tempfile(const char *file)\n-{\n-\tstruct stat st;\n-\tstruct strbuf filename_template = STRBUF_INIT;\n-\tconst char *tail;\n-\tFILE *outfile;\n-\n-\tif (stat(file, &st))\n-\t\tdie_errno(_(\"could not stat %s\"), file);\n-\tif (!S_ISREG(st.st_mode))\n-\t\tdie(_(\"file %s is not a regular file\"), file);\n-\tif (!(st.st_mode & S_IWUSR))\n-\t\tdie(_(\"file %s is not writable by user\"), file);\n-\n-\t/* Create temporary file in the same directory as the original */\n-\ttail = strrchr(file, '/');\n-\tif (tail)\n-\t\tstrbuf_add(&filename_template, file, tail - file + 1);\n-\tstrbuf_addstr(&filename_template, \"git-interpret-trailers-XXXXXX\");\n-\n-\ttrailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);\n-\tstrbuf_release(&filename_template);\n-\toutfile = fdopen_tempfile(trailers_tempfile, \"w\");\n-\tif (!outfile)\n-\t\tdie_errno(_(\"could not open temporary file\"));\n-\n-\treturn outfile;\n-}\n-\n static void read_input_file(struct strbuf *sb, const char *file)\n {\n \tif (file) {\n@@ -136,61 +104,6 @@ static void read_input_file(struct strbuf *sb, const char *file)\n \tstrbuf_complete_line(sb);\n }\n \n-static void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\t       struct list_head *new_trailer_head,\n-\t\t\t       const char *file)\n-{\n-\tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct strbuf trailer_block_sb = STRBUF_INIT;\n-\tstruct trailer_block *trailer_block;\n-\tFILE *outfile = stdout;\n-\n-\ttrailer_config_init();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n-\n-\t/* Print the lines before the trailer block */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);\n-\n-\tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n-\t\tfprintf(outfile, \"\\n\");\n-\n-\n-\tif (!opts->only_input) {\n-\t\tLIST_HEAD(config_head);\n-\t\tLIST_HEAD(arg_head);\n-\t\tparse_trailers_from_config(&config_head);\n-\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n-\t\tlist_splice(&config_head, &arg_head);\n-\t\tprocess_trailers_lists(&head, &arg_head);\n-\t}\n-\n-\t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &trailer_block_sb);\n-\tfree_trailers(&head);\n-\tfwrite(trailer_block_sb.buf, 1, trailer_block_sb.len, outfile);\n-\tstrbuf_release(&trailer_block_sb);\n-\n-\t/* Print the lines after the trailer block as is. */\n-\tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + trailer_block_end(trailer_block), 1,\n-\t\t       sb.len - trailer_block_end(trailer_block), outfile);\n-\ttrailer_block_release(trailer_block);\n-\n-\tif (opts->in_place)\n-\t\tif (rename_tempfile(&trailers_tempfile, file))\n-\t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n-\n-\tstrbuf_release(&sb);\n-}\n-\n int cmd_interpret_trailers(int argc,\n \t\t\t   const char **argv,\n \t\t\t   const char *prefix,\n@@ -232,14 +145,37 @@ int cmd_interpret_trailers(int argc,\n \t\t\tgit_interpret_trailers_usage,\n \t\t\toptions);\n \n+\ttrailer_config_init();\n+\n \tif (argc) {\n \t\tint i;\n-\t\tfor (i = 0; i < argc; i++)\n-\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n+\t\tfor (i = 0; i < argc; i++) {\n+\t\t\tstruct strbuf in_buf = STRBUF_INIT;\n+\t\t\tstruct strbuf out_buf = STRBUF_INIT;\n+\n+\t\t\tread_input_file(&in_buf, argv[i]);\n+\t\t\tif (trailer_process(&opts, in_buf.buf, &trailers, &out_buf) < 0)\n+\t\t\t\tdie(_(\"failed to process trailers for %s\"), argv[i]);\n+\t\t\tif (opts.in_place)\n+\t\t\t\twrite_file_buf(argv[i], out_buf.buf, out_buf.len);\n+\t\t\telse\n+\t\t\t\tfwrite(out_buf.buf, 1, out_buf.len, stdout);\n+\t\t\tstrbuf_release(&in_buf);\n+\t\t\tstrbuf_release(&out_buf);\n+\t\t}\n \t} else {\n+\t\tstruct strbuf in_buf = STRBUF_INIT;\n+\t\tstruct strbuf out_buf = STRBUF_INIT;\n+\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n-\t\tinterpret_trailers(&opts, &trailers, NULL);\n+\n+\t\tread_input_file(&in_buf, NULL);\n+\t\tif (trailer_process(&opts, in_buf.buf, &trailers, &out_buf) < 0)\n+\t\t\tdie(_(\"failed to process trailers\"));\n+\t\tfwrite(out_buf.buf, 1, out_buf.len, stdout);\n+\t\tstrbuf_release(&in_buf);\n+\t\tstrbuf_release(&out_buf);\n \t}\n \n \tnew_trailers_clear(&trailers);\ndiff --git a/trailer.c b/trailer.c\nindex 911a81ed99..2fe49df23a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1224,14 +1224,121 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n \tstrbuf_release(&iter->key);\n }\n \n-int amend_file_with_trailers(const char *path, const struct strvec *trailer_args)\n+static int amend_strbuf_with_trailers(struct strbuf *buf,\n+\t\t\t\t\t\t\t\t\t  const struct strvec *trailer_args)\n {\n-\tstruct child_process run_trailer = CHILD_PROCESS_INIT;\n-\n-\trun_trailer.git_cmd = 1;\n-\tstrvec_pushl(&run_trailer.args, \"interpret-trailers\",\n-\t\t     \"--in-place\", \"--no-divider\",\n-\t\t     path, NULL);\n-\tstrvec_pushv(&run_trailer.args, trailer_args->v);\n-\treturn run_command(&run_trailer);\n+\tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n+\tLIST_HEAD(new_trailer_head);\n+\tstruct strbuf out = STRBUF_INIT;\n+\tsize_t i;\n+\n+\topts.no_divider = 1;\n+\n+\tfor (i = 0; i < trailer_args->nr; i++) {\n+\t\tconst char *arg = trailer_args->v[i];\n+\t\tconst char *text;\n+\t\tstruct new_trailer_item *item;\n+\n+\t\tif (!skip_prefix(arg, \"--trailer=\", &text))\n+\t\t\ttext = arg;\n+\t\tif (!*text)\n+\t\t\tcontinue;\n+\t\titem = xcalloc(1, sizeof(*item));\n+\t\tINIT_LIST_HEAD(&item->list);\n+\t\titem->text = text;\n+\t\tlist_add_tail(&item->list, &new_trailer_head);\n+\t}\n+\tif (trailer_process(&opts, buf->buf, &new_trailer_head, &out) < 0)\n+\t\treturn -1;\n+\tstrbuf_swap(buf, &out);\n+\tstrbuf_release(&out);\n+\twhile (!list_empty(&new_trailer_head)) {\n+\t\tstruct new_trailer_item *item =\n+\t\t\tlist_first_entry(&new_trailer_head, struct new_trailer_item, list);\n+\t\tlist_del(&item->list);\n+\t\tfree(item);\n+\t}\n+\treturn 0;\n+}\n+\n+int trailer_process(const struct process_trailer_options *opts,\n+\t\t    const char *msg,\n+\t\t    struct list_head *new_trailer_head,\n+\t\t    struct strbuf *out)\n+{\n+\tstruct trailer_block *blk;\n+\tLIST_HEAD(orig_head);\n+\tLIST_HEAD(config_head);\n+\tLIST_HEAD(arg_head);\n+\tstruct strbuf trailers_sb = STRBUF_INIT;\n+\tint had_trailer_before;\n+\n+\tblk = parse_trailers(opts, msg, &orig_head);\n+\thad_trailer_before = !list_empty(&orig_head);\n+\tif (!opts->only_input) {\n+\t\tparse_trailers_from_config(&config_head);\n+\t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n+\t\tlist_splice(&config_head, &arg_head);\n+\t\tprocess_trailers_lists(&orig_head, &arg_head);\n+\t}\n+\tformat_trailers(opts, &orig_head, &trailers_sb);\n+\tif (!opts->only_trailers && !opts->only_input && !opts->unfold &&\n+\t    !opts->trim_empty && list_empty(&orig_head) &&\n+\t    (list_empty(new_trailer_head) || opts->only_input)) {\n+\t\tsize_t split = trailer_block_start(blk); /* end-of-log-msg */\n+\t\tif (!blank_line_before_trailer_block(blk)) {\n+\t\t\tstrbuf_add(out, msg, split);\n+\t\t\tstrbuf_addch(out, '\\n');\n+\t\t\tstrbuf_addstr(out, msg + split);\n+\t\t} else\n+\t\t\tstrbuf_addstr(out, msg);\n+\n+\t\tstrbuf_release(&trailers_sb);\n+\t\ttrailer_block_release(blk);\n+\t\treturn 0;\n+\t}\n+\tif (opts->only_trailers) {\n+\t\tstrbuf_addbuf(out, &trailers_sb);\n+\t} else if (had_trailer_before) {\n+\t\tstrbuf_add(out, msg, trailer_block_start(blk));\n+\t\tif (!blank_line_before_trailer_block(blk))\n+\t\t\tstrbuf_addch(out, '\\n');\n+\t\tstrbuf_addbuf(out, &trailers_sb);\n+\t\tstrbuf_add(out, msg + trailer_block_end(blk),\n+\t\t\t   strlen(msg) - trailer_block_end(blk));\n+\t} else {\n+\t\tsize_t cpos = trailer_block_start(blk);\n+\t\tstrbuf_add(out, msg, cpos);\n+\t\tif (cpos == 0) /* empty body → just one \\n */\n+\t\t\tstrbuf_addch(out, '\\n');\n+\t\telse if (!blank_line_before_trailer_block(blk))\n+\t\t\tstrbuf_addch(out, '\\n'); /* body without trailing blank */\n+\n+\t\tstrbuf_addbuf(out, &trailers_sb);\n+\t\tstrbuf_add(out, msg + cpos, strlen(msg) - cpos);\n+\t}\n+\tstrbuf_release(&trailers_sb);\n+\tfree_trailers(&orig_head);\n+\ttrailer_block_release(blk);\n+\treturn 0;\n+}\n+\n+int amend_file_with_trailers(const char *path,\n+\t\t\t     const struct strvec *trailer_args)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (!trailer_args || !trailer_args->nr)\n+\t\treturn 0;\n+\n+\tif (strbuf_read_file(&buf, path, 0) < 0)\n+\t\treturn error_errno(\"could not read '%s'\", path);\n+\n+\tif (amend_strbuf_with_trailers(&buf, trailer_args))\n+\t\tdie(\"failed to append trailers\");\n+\n+\t/* `write_file_buf()` aborts on error internally */\n+\twrite_file_buf(path, buf.buf, buf.len);\n+\tstrbuf_release(&buf);\n+\treturn 0;\n }\ndiff --git a/trailer.h b/trailer.h\nindex 4740549586..b4f28bfd65 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -196,10 +196,22 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n void trailer_iterator_release(struct trailer_iterator *iter);\n \n /*\n- * Augment a file to add trailers to it by running git-interpret-trailers.\n- * This calls run_command() and its return value is the same (i.e. 0 for\n- * success, various non-zero for other errors). See run-command.h.\n+ * Augment a file to add trailers to it (similar to 'git interpret-trailers').\n+ * Returns 0 on success or a non-zero error code on failure.\n  */\n int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);\n \n+/*\n+ * Process trailer lines for a commit message in-memory.\n+ * @opts: trailer processing options (e.g. from parse-options)\n+ * @msg: the input message string\n+ * @new_trailer_head: list of new trailers to add (struct new_trailer_item)\n+ * @out: strbuf to store the resulting message (must be initialized)\n+ *\n+ * Returns 0 on success, <0 on error.\n+ */\n+int trailer_process(const struct process_trailer_options *opts,\n+\t\t    const char *msg,\n+\t\t    struct list_head *new_trailer_head,\n+\t\t    struct strbuf *out);\n #endif /* TRAILER_H */\n-- \n2.51.0\n\n"},{"id":"529353","messageId":"20251022053951.602605-3-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 02/29] trailer: restore interpret_trailers helper","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:22Z","receivedAt":"2025-10-22T05:40:35Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"The change factors the duplicated\nread/process/write logic into a single\ninterpret_trailers() helper(which was removed\nin previous commit) while preserving the\noriginal error handling and output paths for\nboth in-place edits and stdout output.\n\ncmd_interpret_trailers() now reuses the helper\nfor each filename and for the stdin path,\nkeeping the option parsing and safety checks intact.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/interpret-trailers.c | 50 +++++++++++++++++++-----------------\n 1 file changed, 27 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex be0fa83f79..2c8b6fc3b9 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -104,6 +104,30 @@ static void read_input_file(struct strbuf *sb, const char *file)\n \tstrbuf_complete_line(sb);\n }\n \n+static void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       const char *file)\n+{\n+\tstruct strbuf in_buf = STRBUF_INIT;\n+\tstruct strbuf out_buf = STRBUF_INIT;\n+\n+\tread_input_file(&in_buf, file);\n+\tif (trailer_process(opts, in_buf.buf, new_trailer_head, &out_buf) < 0) {\n+\t\tif (file)\n+\t\t\tdie(_(\"failed to process trailers for %s\"), file);\n+\t\telse\n+\t\t\tdie(_(\"failed to process trailers\"));\n+\t}\n+\n+\tif (opts->in_place)\n+\t\twrite_file_buf(file, out_buf.buf, out_buf.len);\n+\telse\n+\t\tfwrite(out_buf.buf, 1, out_buf.len, stdout);\n+\n+\tstrbuf_release(&in_buf);\n+\tstrbuf_release(&out_buf);\n+}\n+\n int cmd_interpret_trailers(int argc,\n \t\t\t   const char **argv,\n \t\t\t   const char *prefix,\n@@ -149,33 +173,13 @@ int cmd_interpret_trailers(int argc,\n \n \tif (argc) {\n \t\tint i;\n-\t\tfor (i = 0; i < argc; i++) {\n-\t\t\tstruct strbuf in_buf = STRBUF_INIT;\n-\t\t\tstruct strbuf out_buf = STRBUF_INIT;\n-\n-\t\t\tread_input_file(&in_buf, argv[i]);\n-\t\t\tif (trailer_process(&opts, in_buf.buf, &trailers, &out_buf) < 0)\n-\t\t\t\tdie(_(\"failed to process trailers for %s\"), argv[i]);\n-\t\t\tif (opts.in_place)\n-\t\t\t\twrite_file_buf(argv[i], out_buf.buf, out_buf.len);\n-\t\t\telse\n-\t\t\t\tfwrite(out_buf.buf, 1, out_buf.len, stdout);\n-\t\t\tstrbuf_release(&in_buf);\n-\t\t\tstrbuf_release(&out_buf);\n-\t\t}\n+\t\tfor (i = 0; i < argc; i++)\n+\t\t\tinterpret_trailers(&opts, &trailers, argv[i]);\n \t} else {\n-\t\tstruct strbuf in_buf = STRBUF_INIT;\n-\t\tstruct strbuf out_buf = STRBUF_INIT;\n-\n \t\tif (opts.in_place)\n \t\t\tdie(_(\"no input file given for in-place editing\"));\n \n-\t\tread_input_file(&in_buf, NULL);\n-\t\tif (trailer_process(&opts, in_buf.buf, &trailers, &out_buf) < 0)\n-\t\t\tdie(_(\"failed to process trailers\"));\n-\t\tfwrite(out_buf.buf, 1, out_buf.len, stdout);\n-\t\tstrbuf_release(&in_buf);\n-\t\tstrbuf_release(&out_buf);\n+\t\tinterpret_trailers(&opts, &trailers, NULL);\n \t}\n \n \tnew_trailers_clear(&trailers);\n-- \n2.51.0\n\n"},{"id":"529354","messageId":"20251022053951.602605-4-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 03/29] trailer: drop --trailer prefix handling in amend helper","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:23Z","receivedAt":"2025-10-22T05:40:46Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Make callers pass plain trailer text instead of recreating\nthe option prefix before invoking interpret-trailers.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/commit.c | 2 +-\n builtin/tag.c    | 3 +--\n trailer.c        | 5 +----\n 3 files changed, 3 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 0243f17d53..67070d6a54 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1719,7 +1719,7 @@ int cmd_commit(int argc,\n \t\tOPT_STRING(0, \"fixup\", &fixup_message, N_(\"[(amend|reword):]commit\"), N_(\"use autosquash formatted message to fixup or amend/reword specified commit\")),\n \t\tOPT_STRING(0, \"squash\", &squash_message, N_(\"commit\"), N_(\"use autosquash formatted message to squash specified commit\")),\n \t\tOPT_BOOL(0, \"reset-author\", &renew_authorship, N_(\"the commit is authored by me now (used with -C/-c/--amend)\")),\n-\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n+\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, parse_opt_strvec),\n \t\tOPT_BOOL('s', \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n \t\tOPT_FILENAME('t', \"template\", &template_file, N_(\"use specified template file\")),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of commit\")),\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f0665af3ac..65c4a0b36b 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -499,8 +499,7 @@ int cmd_tag(int argc,\n \t\tOPT_CALLBACK_F('m', \"message\", &msg, N_(\"message\"),\n \t\t\t       N_(\"tag message\"), PARSE_OPT_NONEG, parse_msg_arg),\n \t\tOPT_FILENAME('F', \"file\", &msgfile, N_(\"read message from file\")),\n-\t\tOPT_PASSTHRU_ARGV(0, \"trailer\", &trailer_args, N_(\"trailer\"),\n-\t\t\t\t  N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG),\n+\t\tOPT_CALLBACK_F(0, \"trailer\", &trailer_args, N_(\"trailer\"), N_(\"add custom trailer(s)\"), PARSE_OPT_NONEG, parse_opt_strvec),\n \t\tOPT_BOOL('e', \"edit\", &edit_flag, N_(\"force edit of tag message\")),\n \t\tOPT_BOOL('s', \"sign\", &opt.sign, N_(\"annotated and GPG-signed tag\")),\n \t\tOPT_CLEANUP(&cleanup_arg),\ndiff --git a/trailer.c b/trailer.c\nindex 2fe49df23a..b7b0029e05 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1235,12 +1235,9 @@ static int amend_strbuf_with_trailers(struct strbuf *buf,\n \topts.no_divider = 1;\n \n \tfor (i = 0; i < trailer_args->nr; i++) {\n-\t\tconst char *arg = trailer_args->v[i];\n-\t\tconst char *text;\n+\t\tconst char *text = trailer_args->v[i];\n \t\tstruct new_trailer_item *item;\n \n-\t\tif (!skip_prefix(arg, \"--trailer=\", &text))\n-\t\t\ttext = arg;\n \t\tif (!*text)\n \t\t\tcontinue;\n \t\titem = xcalloc(1, sizeof(*item));\n-- \n2.51.0\n\n"},{"id":"529355","messageId":"20251022053951.602605-5-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 04/29] trailer: move config_head and arg_head to if storage","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:24Z","receivedAt":"2025-10-22T05:40:56Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nMove LIST_HEAD(config_head) and LIST_HEAD(arg_head) into the\nnon-only_input branch so they are created only when needed.\nNo functional change.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n trailer.c | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex b7b0029e05..9abb5a522a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1265,14 +1265,15 @@ int trailer_process(const struct process_trailer_options *opts,\n {\n \tstruct trailer_block *blk;\n \tLIST_HEAD(orig_head);\n-\tLIST_HEAD(config_head);\n-\tLIST_HEAD(arg_head);\n \tstruct strbuf trailers_sb = STRBUF_INIT;\n \tint had_trailer_before;\n \n \tblk = parse_trailers(opts, msg, &orig_head);\n \thad_trailer_before = !list_empty(&orig_head);\n \tif (!opts->only_input) {\n+\t\tLIST_HEAD(config_head);\n+\t\tLIST_HEAD(arg_head);\n+\n \t\tparse_trailers_from_config(&config_head);\n \t\tparse_trailers_from_command_line_args(&arg_head, new_trailer_head);\n \t\tlist_splice(&config_head, &arg_head);\n-- \n2.51.0\n\n"},{"id":"529356","messageId":"20251022053951.602605-6-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 05/29] trailer: use bool for had_trailer_before","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:25Z","receivedAt":"2025-10-22T05:41:07Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nSwitch had_trailer_before from int to bool\nto match its logical use. No functional change.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n trailer.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex 9abb5a522a..a448380327 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1266,7 +1266,7 @@ int trailer_process(const struct process_trailer_options *opts,\n \tstruct trailer_block *blk;\n \tLIST_HEAD(orig_head);\n \tstruct strbuf trailers_sb = STRBUF_INIT;\n-\tint had_trailer_before;\n+\tbool had_trailer_before;\n \n \tblk = parse_trailers(opts, msg, &orig_head);\n \thad_trailer_before = !list_empty(&orig_head);\n-- \n2.51.0\n\n"},{"id":"529357","messageId":"20251022053951.602605-7-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 06/29] interpret-trailers: buffer stdout output","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:26Z","receivedAt":"2025-10-22T05:41:18Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Simplified the non-in-place path in interpret_trailers\nby writing the existing output strbuf directly to\nstdout without creating a redundant temporary buffer.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/interpret-trailers.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 2c8b6fc3b9..cdf39dbca8 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -122,7 +122,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n \tif (opts->in_place)\n \t\twrite_file_buf(file, out_buf.buf, out_buf.len);\n \telse\n-\t\tfwrite(out_buf.buf, 1, out_buf.len, stdout);\n+\t\tstrbuf_write(&out_buf, stdout);\n \n \tstrbuf_release(&in_buf);\n \tstrbuf_release(&out_buf);\n-- \n2.51.0\n\n"},{"id":"529358","messageId":"20251022053951.602605-8-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 07/29] trailer: mirror interpret-trailers output flow","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:27Z","receivedAt":"2025-10-22T05:41:29Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Drop the early-return branch that mirrored the\nspecial case. Let trailer_process() always\nfollow the same path as interpret-trailers.\nEnsure trailer lists and buffers are freed\nalong the unified exit path.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n trailer.c | 38 +++++++-------------------------------\n 1 file changed, 7 insertions(+), 31 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex a448380327..ac6ac2ac20 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1266,10 +1266,8 @@ int trailer_process(const struct process_trailer_options *opts,\n \tstruct trailer_block *blk;\n \tLIST_HEAD(orig_head);\n \tstruct strbuf trailers_sb = STRBUF_INIT;\n-\tbool had_trailer_before;\n \n \tblk = parse_trailers(opts, msg, &orig_head);\n-\thad_trailer_before = !list_empty(&orig_head);\n \tif (!opts->only_input) {\n \t\tLIST_HEAD(config_head);\n \t\tLIST_HEAD(arg_head);\n@@ -1280,40 +1278,18 @@ int trailer_process(const struct process_trailer_options *opts,\n \t\tprocess_trailers_lists(&orig_head, &arg_head);\n \t}\n \tformat_trailers(opts, &orig_head, &trailers_sb);\n-\tif (!opts->only_trailers && !opts->only_input && !opts->unfold &&\n-\t    !opts->trim_empty && list_empty(&orig_head) &&\n-\t    (list_empty(new_trailer_head) || opts->only_input)) {\n-\t\tsize_t split = trailer_block_start(blk); /* end-of-log-msg */\n-\t\tif (!blank_line_before_trailer_block(blk)) {\n-\t\t\tstrbuf_add(out, msg, split);\n-\t\t\tstrbuf_addch(out, '\\n');\n-\t\t\tstrbuf_addstr(out, msg + split);\n-\t\t} else\n-\t\t\tstrbuf_addstr(out, msg);\n-\n-\t\tstrbuf_release(&trailers_sb);\n-\t\ttrailer_block_release(blk);\n-\t\treturn 0;\n-\t}\n \tif (opts->only_trailers) {\n \t\tstrbuf_addbuf(out, &trailers_sb);\n-\t} else if (had_trailer_before) {\n-\t\tstrbuf_add(out, msg, trailer_block_start(blk));\n-\t\tif (!blank_line_before_trailer_block(blk))\n-\t\t\tstrbuf_addch(out, '\\n');\n-\t\tstrbuf_addbuf(out, &trailers_sb);\n-\t\tstrbuf_add(out, msg + trailer_block_end(blk),\n-\t\t\t   strlen(msg) - trailer_block_end(blk));\n \t} else {\n-\t\tsize_t cpos = trailer_block_start(blk);\n-\t\tstrbuf_add(out, msg, cpos);\n-\t\tif (cpos == 0) /* empty body → just one \\n */\n-\t\t\tstrbuf_addch(out, '\\n');\n-\t\telse if (!blank_line_before_trailer_block(blk))\n-\t\t\tstrbuf_addch(out, '\\n'); /* body without trailing blank */\n+\t\tsize_t block_start = trailer_block_start(blk);\n+\t\tsize_t block_end = trailer_block_end(blk);\n+\t\tbool need_blank_line = !blank_line_before_trailer_block(blk);\n \n+\t\tstrbuf_add(out, msg, block_start);\n+\t\tif (need_blank_line)\n+\t\t\tstrbuf_addch(out, '\\n');\n \t\tstrbuf_addbuf(out, &trailers_sb);\n-\t\tstrbuf_add(out, msg + cpos, strlen(msg) - cpos);\n+\t\tstrbuf_add(out, msg + block_end, strlen(msg) - block_end);\n \t}\n \tstrbuf_release(&trailers_sb);\n \tfree_trailers(&orig_head);\n-- \n2.51.0\n\n"},{"id":"529359","messageId":"20251022053951.602605-9-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 08/29] trailer: handle trailer append failures gently","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:28Z","receivedAt":"2025-10-22T05:41:39Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Added write_file_buf_gently so callers can rewrite\nfiles while surfacing errors instead of aborting.\n\nUpdated amend_file_with_trailers to release buffers\nand propagate trailer and write failures back to the caller,\nbecause amend_file_with_trailers shuold not die.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n trailer.c | 14 ++++++++++----\n wrapper.c | 16 ++++++++++++++++\n wrapper.h |  6 ++++++\n 3 files changed, 32 insertions(+), 4 deletions(-)\n\ndiff --git a/trailer.c b/trailer.c\nindex ac6ac2ac20..1f317f4d37 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -9,6 +9,7 @@\n #include \"commit.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n+#include \"wrapper.h\"\n /*\n  * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>\n  */\n@@ -1308,11 +1309,16 @@ int amend_file_with_trailers(const char *path,\n \tif (strbuf_read_file(&buf, path, 0) < 0)\n \t\treturn error_errno(\"could not read '%s'\", path);\n \n-\tif (amend_strbuf_with_trailers(&buf, trailer_args))\n-\t\tdie(\"failed to append trailers\");\n+\tif (amend_strbuf_with_trailers(&buf, trailer_args)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn error(\"failed to append trailers\");\n+\t}\n+\n+\tif (write_file_buf_gently(path, buf.buf, buf.len)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn -1;\n+\t}\n \n-\t/* `write_file_buf()` aborts on error internally */\n-\twrite_file_buf(path, buf.buf, buf.len);\n \tstrbuf_release(&buf);\n \treturn 0;\n }\ndiff --git a/wrapper.c b/wrapper.c\nindex 3d507d4204..1f12dbb2fa 100644\n--- a/wrapper.c\n+++ b/wrapper.c\n@@ -688,6 +688,22 @@ void write_file_buf(const char *path, const char *buf, size_t len)\n \t\tdie_errno(_(\"could not close '%s'\"), path);\n }\n \n+int write_file_buf_gently(const char *path, const char *buf, size_t len)\n+{\n+\tint fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n+\n+\tif (fd < 0)\n+\t\treturn error_errno(_(\"could not open '%s'\"), path);\n+\tif (write_in_full(fd, buf, len) < 0) {\n+\t\tint ret = error_errno(_(\"could not write to '%s'\"), path);\n+\t\tclose(fd);\n+\t\treturn ret;\n+\t}\n+\tif (close(fd))\n+\t\treturn error_errno(_(\"could not close '%s'\"), path);\n+\treturn 0;\n+}\n+\n void write_file(const char *path, const char *fmt, ...)\n {\n \tva_list params;\ndiff --git a/wrapper.h b/wrapper.h\nindex 44a8597ac3..e5f867b200 100644\n--- a/wrapper.h\n+++ b/wrapper.h\n@@ -56,6 +56,12 @@ static inline ssize_t write_str_in_full(int fd, const char *str)\n  */\n void write_file_buf(const char *path, const char *buf, size_t len);\n \n+/**\n+ * Like write_file_buf(), but report errors instead of exiting. Returns 0 on\n+ * success or a negative value on error after emitting a message.\n+ */\n+int write_file_buf_gently(const char *path, const char *buf, size_t len);\n+\n /**\n  * Like write_file_buf(), but format the contents into a buffer first.\n  * Additionally, write_file() will append a newline if one is not already\n-- \n2.51.0\n\n"},{"id":"529360","messageId":"20251022053951.602605-10-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 09/29] rebase: support --trailer","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:29Z","receivedAt":"2025-10-22T05:41:49Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nImplement a new `--trailer <text>` option for `git rebase`\n(support merge backend only now), which appends arbitrary\ntrailer lines to each rebased commit message.\n\nReject it if the user passes an option that requires the\napply backend (git am) since it lacks message‑filter/trailer\nhook. otherwise we can just use the merge backend.\n\nAutomatically set REBASE_FORCE when any trailer is supplied.\n\nAnd reject invalid input before user edits the interactive file.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n Documentation/git-rebase.adoc |  9 +++-\n builtin/rebase.c              | 89 +++++++++++++++++++++++++++++++++\n sequencer.c                   | 13 +++++\n sequencer.h                   |  4 +-\n t/meson.build                 |  1 +\n t/t3440-rebase-trailer.sh     | 94 +++++++++++++++++++++++++++++++++++\n 6 files changed, 208 insertions(+), 2 deletions(-)\n create mode 100755 t/t3440-rebase-trailer.sh\n\ndiff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc\nindex 005caf6164..4d2fe4be6e 100644\n--- a/Documentation/git-rebase.adoc\n+++ b/Documentation/git-rebase.adoc\n@@ -487,9 +487,16 @@ See also INCOMPATIBLE OPTIONS below.\n \tAdd a `Signed-off-by` trailer to all the rebased commits. Note\n \tthat if `--interactive` is given then only commits marked to be\n \tpicked, edited or reworded will have the trailer added.\n-+\n+\n See also INCOMPATIBLE OPTIONS below.\n \n+--trailer=<trailer>::\n+       Append the given trailer line(s) to every rebased commit\n+       message, processed via linkgit:git-interpret-trailers[1].\n+       When this option is present *rebase automatically implies*\n+       `--force-rebase` so that fast‑forwarded commits are also\n+       rewritten.\n+\n -i::\n --interactive::\n \tMake a list of the commits which are about to be rebased.  Let the\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex c468828189..3db1439b52 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -36,6 +36,7 @@\n #include \"reset.h\"\n #include \"trace2.h\"\n #include \"hook.h\"\n+#include \"trailer.h\"\n \n static char const * const builtin_rebase_usage[] = {\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n@@ -113,6 +114,7 @@ struct rebase_options {\n \tenum action action;\n \tchar *reflog_action;\n \tint signoff;\n+\tstruct strvec trailer_args;\n \tint allow_rerere_autoupdate;\n \tint keep_empty;\n \tint autosquash;\n@@ -143,6 +145,7 @@ struct rebase_options {\n \t\t.flags = REBASE_NO_QUIET, \t\t\\\n \t\t.git_am_opts = STRVEC_INIT,\t\t\\\n \t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n+\t\t.trailer_args = STRVEC_INIT,  \\\n \t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n \t\t.fork_point = -1,\t\t\t\\\n \t\t.reapply_cherry_picks = -1,             \\\n@@ -166,6 +169,7 @@ static void rebase_options_release(struct rebase_options *opts)\n \tfree(opts->strategy);\n \tstring_list_clear(&opts->strategy_opts, 0);\n \tstrbuf_release(&opts->git_format_patch_opt);\n+\tstrvec_clear(&opts->trailer_args);\n }\n \n static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n@@ -177,6 +181,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \tsequencer_init_config(&replay);\n \n \treplay.signoff = opts->signoff;\n+\n+\tfor (size_t i = 0; i < opts->trailer_args.nr; i++)\n+\t\tstrvec_push(&replay.trailer_args, opts->trailer_args.v[i]);\n+\n \treplay.allow_ff = !(opts->flags & REBASE_FORCE);\n \tif (opts->allow_rerere_autoupdate)\n \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n@@ -434,6 +442,8 @@ static int read_basic_state(struct rebase_options *opts)\n \tstruct strbuf head_name = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct object_id oid;\n+\tconst char trailer_state_name[] = \"trailer\";\n+\tconst char *path = state_dir_path(trailer_state_name, opts);\n \n \tif (!read_oneliner(&head_name, state_dir_path(\"head-name\", opts),\n \t\t\t   READ_ONELINER_WARN_MISSING) ||\n@@ -502,11 +512,31 @@ static int read_basic_state(struct rebase_options *opts)\n \n \tstrbuf_release(&buf);\n \n+\tif (strbuf_read_file(&buf, path, 0) >= 0) {\n+\t\tconst char *p = buf.buf, *end = buf.buf + buf.len;\n+\n+\t\twhile (p < end) {\n+\t\t\tchar *nl = memchr(p, '\\n', end - p);\n+\t\t\tif (!nl)\n+\t\t\t\tdie(\"nl shouldn't be NULL\");\n+\t\t\t*nl = '\\0';\n+\n+\t\t\tif (*p)\n+\t\t\t\tstrvec_push(&opts->trailer_args, p);\n+\n+\t\t\tp = nl + 1;\n+\t\t}\n+\t\tstrbuf_release(&buf);\n+\t}\n+\tstrbuf_release(&buf);\n+\n \treturn 0;\n }\n \n static int rebase_write_basic_state(struct rebase_options *opts)\n {\n+\tconst char trailer_state_name[] = \"trailer\";\n+\n \twrite_file(state_dir_path(\"head-name\", opts), \"%s\",\n \t\t   opts->head_name ? opts->head_name : \"detached HEAD\");\n \twrite_file(state_dir_path(\"onto\", opts), \"%s\",\n@@ -528,6 +558,21 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n \tif (opts->signoff)\n \t\twrite_file(state_dir_path(\"signoff\", opts), \"--signoff\");\n \n+\t/*\n+\t * save opts->trailer_args into state_dir/trailer\n+\t */\n+\tif (opts->trailer_args.nr) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tfor (size_t i = 0; i < opts->trailer_args.nr; i++) {\n+\t\t\t\tstrbuf_addstr(&buf, opts->trailer_args.v[i]);\n+\t\t\t\tstrbuf_addch(&buf, '\\n');\n+\t\t}\n+\t\twrite_file(state_dir_path(trailer_state_name, opts),\n+\t\t\t\t   \"%s\", buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n \treturn 0;\n }\n \n@@ -1084,6 +1129,35 @@ static int check_exec_cmd(const char *cmd)\n \treturn 0;\n }\n \n+static int validate_trailer_args_after_config(const struct strvec *cli_args,\n+\t\t\t\t\t      struct strbuf *err)\n+{\n+\tfor (size_t i = 0; i < cli_args->nr; i++) {\n+\t\tconst char *raw = cli_args->v[i];\n+\t\tconst char *txt; // Key[:=]Val\n+\t\tconst char *sep;\n+\n+\t\tif (!skip_prefix(raw, \"--trailer=\", &txt))\n+\t\t\ttxt = raw;\n+\n+\t\tif (!*txt) {\n+\t\t\tstrbuf_addstr(err, _(\"empty --trailer argument\"));\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tsep = strpbrk(txt, \":=\");\n+\n+\t\t/* there must be key bfore seperator */\n+\t\tif (sep && sep == txt) {\n+\t\t\tstrbuf_addf(err,\n+\t\t\t\t    _(\"invalid trailer '%s': missing key before separator\"),\n+\t\t\t\t    txt);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n int cmd_rebase(int argc,\n \t       const char **argv,\n \t       const char *prefix,\n@@ -1132,6 +1206,8 @@ int cmd_rebase(int argc,\n \t\t\t.flags = PARSE_OPT_NOARG,\n \t\t\t.defval = REBASE_DIFFSTAT,\n \t\t},\n+\t\tOPT_STRVEC(0, \"trailer\", &options.trailer_args, N_(\"trailer\"),\n+\t\t\t\tN_(\"add custom trailer(s)\")),\n \t\tOPT_BOOL(0, \"signoff\", &options.signoff,\n \t\t\t N_(\"add a Signed-off-by trailer to each commit\")),\n \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n@@ -1285,6 +1361,16 @@ int cmd_rebase(int argc,\n \t\t\t     builtin_rebase_options,\n \t\t\t     builtin_rebase_usage, 0);\n \n+\tif (options.trailer_args.nr) {\n+\t\tstruct strbuf err = STRBUF_INIT;\n+\n+\t\tif (validate_trailer_args_after_config(&options.trailer_args, &err))\n+\t\t\tdie(\"%s\", err.buf);\n+\n+\t\toptions.flags |= REBASE_FORCE;\n+\t\tstrbuf_release(&err);\n+\t}\n+\n \tif (preserve_merges_selected)\n \t\tdie(_(\"--preserve-merges was replaced by --rebase-merges\\n\"\n \t\t\t\"Note: Your `pull.rebase` configuration may also be set to 'preserve',\\n\"\n@@ -1542,6 +1628,9 @@ int cmd_rebase(int argc,\n \tif (options.root && !options.onto_name)\n \t\timply_merge(&options, \"--root without --onto\");\n \n+\tif (options.trailer_args.nr)\n+\t\timply_merge(&options, \"--trailer\");\n+\n \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 5476d39ba9..5103ae786c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -420,6 +420,7 @@ void replay_opts_release(struct replay_opts *opts)\n \tif (opts->revs)\n \t\trelease_revisions(opts->revs);\n \tfree(opts->revs);\n+\tstrvec_clear(&opts->trailer_args);\n \treplay_ctx_release(ctx);\n \tfree(opts->ctx);\n }\n@@ -2517,6 +2518,18 @@ static int do_pick_commit(struct repository *r,\n \t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n \t\t\toid_to_hex(&commit->object.oid), msg.subject);\n \t} /* else allow == 0 and there's nothing special to do */\n+\n+\tif (!res && opts->trailer_args.nr && !drop_commit) {\n+\t\tconst char *trailer_file =\n+\t\t\tmsg_file ? msg_file : git_path_merge_msg(r);\n+\n+\t\tif (amend_file_with_trailers(trailer_file,\n+\t\t\t\t\t\t&opts->trailer_args)) {\n+\t\t\tres = error(_(\"unable to add trailers to commit message\"));\n+\t\t\tgoto leave;\n+\t\t}\n+\t}\n+\n \tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, reflog_action,\ndiff --git a/sequencer.h b/sequencer.h\nindex 719684c8a9..e21835c5a0 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -44,6 +44,7 @@ struct replay_opts {\n \tint record_origin;\n \tint no_commit;\n \tint signoff;\n+\tstruct strvec trailer_args;\n \tint allow_ff;\n \tint allow_rerere_auto;\n \tint allow_empty;\n@@ -82,8 +83,9 @@ struct replay_opts {\n \tstruct replay_ctx *ctx;\n };\n #define REPLAY_OPTS_INIT {\t\t\t\\\n-\t.edit = -1,\t\t\t\t\\\n \t.action = -1,\t\t\t\t\\\n+\t.edit = -1,\t\t\t\t\\\n+\t.trailer_args = STRVEC_INIT, \\\n \t.xopts = STRVEC_INIT,\t\t\t\\\n \t.ctx = replay_ctx_new(),\t\t\\\n }\ndiff --git a/t/meson.build b/t/meson.build\nindex 401b24e50e..990e8ad4be 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -384,6 +384,7 @@ integration_tests = [\n   't3436-rebase-more-options.sh',\n   't3437-rebase-fixup-options.sh',\n   't3438-rebase-broken-files.sh',\n+  't3440-rebase-trailer.sh',\n   't3500-cherry.sh',\n   't3501-revert-cherry-pick.sh',\n   't3502-cherry-pick-merge.sh',\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nnew file mode 100755\nindex 0000000000..b1c7b03330\n--- /dev/null\n+++ b/t/t3440-rebase-trailer.sh\n@@ -0,0 +1,94 @@\n+#!/bin/sh\n+#\n+\n+test_description='git rebase --trailer integration tests\n+We verify that --trailer works with the merge backend,\n+and that it is rejected early when the apply backend is requested.'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-rebase.sh # test_commit_message, helpers\n+\n+create_expect() {\n+\tcat >\"$1\" <<-EOF\n+\t$2\n+\n+\tReviewed-by: Dev <dev@example.com>\n+\tEOF\n+}\n+\n+test_expect_success 'setup repo with a small history' '\n+\tgit commit --allow-empty -m \"Initial empty commit\" &&\n+\ttest_commit first file a &&\n+\ttest_commit second file &&\n+\tgit checkout -b conflict-branch first &&\n+\ttest_commit file-2 file-2 &&\n+\ttest_commit conflict file &&\n+\ttest_commit third file &&\n+\tident=\"$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\" &&\n+\tcreate_expect initial-signed  \"Initial empty commit\" &&\n+\tcreate_expect first-signed    \"first\"                 &&\n+\tcreate_expect second-signed   \"second\"                &&\n+\tcreate_expect file2-signed    \"file-2\"                &&\n+\tcreate_expect third-signed    \"third\"                 &&\n+\tcreate_expect conflict-signed \"conflict\"\n+'\n+\n+test_expect_success 'apply backend is rejected with --trailer' '\n+\thead_before=$(git rev-parse HEAD) &&\n+\ttest_expect_code 128 \\\n+\tgit rebase --apply --trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n+\t\t\t\tHEAD^ 2>err &&\n+\ttest_grep \"requires the merge backend\" err &&\n+\ttest_cmp_rev HEAD $head_before\n+'\n+\n+test_expect_success 'reject empty --trailer argument' '\n+\tgit reset --hard third &&\n+\ttest_expect_code 128 git rebase -m --trailer \"\" HEAD^ 2>err &&\n+\ttest_grep \"empty --trailer\" err\n+'\n+\n+test_expect_success 'reject trailer with missing key before separator' '\n+\tgit reset --hard third &&\n+\ttest_expect_code 128 git rebase -m --trailer \": no-key\" HEAD^ 2>err &&\n+\ttest_grep \"missing key before separator\" err\n+'\n+\n+test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' '\n+\tgit reset --hard third &&\n+\tgit -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add rebase -m --trailer \"Bug: 123\" --trailer \"Bug: 456\" HEAD~1 &&\n+\tgit cat-file commit HEAD | grep \"^Bug: 456\" &&\n+\tgit cat-file commit HEAD | grep -v \"^Bug: 123\"\n+'\n+\n+test_expect_success 'multiple Signed-off-by trailers all preserved' '\n+\tgit reset --hard third &&\n+\tgit rebase -m \\\n+\t\t--trailer \"Signed-off-by: Dev A <a@ex.com>\" \\\n+\t\t--trailer \"Signed-off-by: Dev B <b@ex.com>\" HEAD~1 &&\n+\tgit cat-file commit HEAD | grep -c \"^Signed-off-by:\" >count &&\n+\ttest \"$(cat count)\" = 2   # two new commits\n+'\n+\n+test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n+\tgit reset --hard third &&\n+\ttest_must_fail git rebase -m \\\n+\t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n+\t\tsecond third &&\n+\tgit checkout --theirs file &&\n+\tgit add file &&\n+\tgit rebase --continue &&\n+\ttest_commit_message HEAD~2 file2-signed\n+'\n+\n+test_expect_success 'rebase --root --trailer updates every commit' '\n+\tgit checkout first &&\n+\tgit rebase --root --keep-empty \\\n+\t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" &&\n+\ttest_commit_message HEAD   first-signed &&\n+\ttest_commit_message HEAD^  initial-signed\n+'\n+test_done\n-- \n2.51.0\n\n"},{"id":"529361","messageId":"20251022053951.602605-11-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 10/29] rebase: inline trailer state paths","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:30Z","receivedAt":"2025-10-22T05:42:01Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Simplified read_basic_state() by dropping the\ntemporary trailer path variables and calling\nstate_dir_path(\"trailer\", opts) directly when\nloading trailer arguments.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/rebase.c | 8 ++------\n 1 file changed, 2 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 3db1439b52..b0f547ef2b 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -442,8 +442,6 @@ static int read_basic_state(struct rebase_options *opts)\n \tstruct strbuf head_name = STRBUF_INIT;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct object_id oid;\n-\tconst char trailer_state_name[] = \"trailer\";\n-\tconst char *path = state_dir_path(trailer_state_name, opts);\n \n \tif (!read_oneliner(&head_name, state_dir_path(\"head-name\", opts),\n \t\t\t   READ_ONELINER_WARN_MISSING) ||\n@@ -512,7 +510,7 @@ static int read_basic_state(struct rebase_options *opts)\n \n \tstrbuf_release(&buf);\n \n-\tif (strbuf_read_file(&buf, path, 0) >= 0) {\n+\tif (strbuf_read_file(&buf, state_dir_path(\"trailer\", opts), 0) >= 0) {\n \t\tconst char *p = buf.buf, *end = buf.buf + buf.len;\n \n \t\twhile (p < end) {\n@@ -535,8 +533,6 @@ static int read_basic_state(struct rebase_options *opts)\n \n static int rebase_write_basic_state(struct rebase_options *opts)\n {\n-\tconst char trailer_state_name[] = \"trailer\";\n-\n \twrite_file(state_dir_path(\"head-name\", opts), \"%s\",\n \t\t   opts->head_name ? opts->head_name : \"detached HEAD\");\n \twrite_file(state_dir_path(\"onto\", opts), \"%s\",\n@@ -568,7 +564,7 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n \t\t\t\tstrbuf_addstr(&buf, opts->trailer_args.v[i]);\n \t\t\t\tstrbuf_addch(&buf, '\\n');\n \t\t}\n-\t\twrite_file(state_dir_path(trailer_state_name, opts),\n+\t\twrite_file(state_dir_path(\"trailer\", opts),\n \t\t\t\t   \"%s\", buf.buf);\n \t\tstrbuf_release(&buf);\n \t}\n-- \n2.51.0\n\n"},{"id":"529362","messageId":"20251022053951.602605-12-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 11/29] rebase: reuse buffer for trailer args","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:31Z","receivedAt":"2025-10-22T05:42:11Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Reset the reusable buffer before reading trailer\narguments in read_basic_state() so the existing\nallocation can be reused.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/rebase.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b0f547ef2b..89ecb84a31 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -508,7 +508,7 @@ static int read_basic_state(struct rebase_options *opts)\n \t\topts->gpg_sign_opt = xstrdup(buf.buf);\n \t}\n \n-\tstrbuf_release(&buf);\n+\tstrbuf_reset(&buf);\n \n \tif (strbuf_read_file(&buf, state_dir_path(\"trailer\", opts), 0) >= 0) {\n \t\tconst char *p = buf.buf, *end = buf.buf + buf.len;\n-- \n2.51.0\n\n"},{"id":"529363","messageId":"20251022053951.602605-13-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 12/29] rebase: drop redundant strbuf_release call","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:32Z","receivedAt":"2025-10-22T05:42:21Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Removed the redundant strbuf_release() call in\nread_basic_state() so the buffer is released once\neven if strbuf_read_file() fails.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/rebase.c | 1 -\n 1 file changed, 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 89ecb84a31..a950005dfc 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -524,7 +524,6 @@ static int read_basic_state(struct rebase_options *opts)\n \n \t\t\tp = nl + 1;\n \t\t}\n-\t\tstrbuf_release(&buf);\n \t}\n \tstrbuf_release(&buf);\n \n-- \n2.51.0\n\n"},{"id":"529364","messageId":"20251022053951.602605-14-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 13/29] rebase: skip stripping of --trailer option prefix","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:33Z","receivedAt":"2025-10-22T05:42:31Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Removed redundant --trailer= prefix stripping in\nvalidate_trailer_args_after_config() since OPT_STRVEC\nalready stores only the argument text.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/rebase.c | 6 +-----\n 1 file changed, 1 insertion(+), 5 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex a950005dfc..3ac1eda61b 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1128,13 +1128,9 @@ static int validate_trailer_args_after_config(const struct strvec *cli_args,\n \t\t\t\t\t      struct strbuf *err)\n {\n \tfor (size_t i = 0; i < cli_args->nr; i++) {\n-\t\tconst char *raw = cli_args->v[i];\n-\t\tconst char *txt; // Key[:=]Val\n+\t\tconst char *txt = cli_args->v[i]; // Key[:=]Val\n \t\tconst char *sep;\n \n-\t\tif (!skip_prefix(raw, \"--trailer=\", &txt))\n-\t\t\ttxt = raw;\n-\n \t\tif (!*txt) {\n \t\t\tstrbuf_addstr(err, _(\"empty --trailer argument\"));\n \t\t\treturn -1;\n-- \n2.51.0\n\n"},{"id":"529365","messageId":"20251022053951.602605-15-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 14/29] rebase: die on invalid trailer args","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:34Z","receivedAt":"2025-10-22T05:42:41Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"This can simplify the error handle in\nvalidate_trailer_args_after_config and\nits caller.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/rebase.c | 26 +++++++-------------------\n 1 file changed, 7 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 3ac1eda61b..872945a897 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1124,29 +1124,22 @@ static int check_exec_cmd(const char *cmd)\n \treturn 0;\n }\n \n-static int validate_trailer_args_after_config(const struct strvec *cli_args,\n-\t\t\t\t\t      struct strbuf *err)\n+static void validate_trailer_args_after_config(const struct strvec *cli_args)\n {\n \tfor (size_t i = 0; i < cli_args->nr; i++) {\n \t\tconst char *txt = cli_args->v[i]; // Key[:=]Val\n \t\tconst char *sep;\n \n-\t\tif (!*txt) {\n-\t\t\tstrbuf_addstr(err, _(\"empty --trailer argument\"));\n-\t\t\treturn -1;\n-\t\t}\n+\t\tif (!*txt)\n+\t\t\tdie(_(\"empty --trailer argument\"));\n \n \t\tsep = strpbrk(txt, \":=\");\n \n \t\t/* there must be key bfore seperator */\n-\t\tif (sep && sep == txt) {\n-\t\t\tstrbuf_addf(err,\n-\t\t\t\t    _(\"invalid trailer '%s': missing key before separator\"),\n-\t\t\t\t    txt);\n-\t\t\treturn -1;\n-\t\t}\n+\t\tif (sep && sep == txt)\n+\t\t\tdie(_(\"invalid trailer '%s': missing key before separator\"),\n+\t\t\t    txt);\n \t}\n-\treturn 0;\n }\n \n int cmd_rebase(int argc,\n@@ -1353,13 +1346,8 @@ int cmd_rebase(int argc,\n \t\t\t     builtin_rebase_usage, 0);\n \n \tif (options.trailer_args.nr) {\n-\t\tstruct strbuf err = STRBUF_INIT;\n-\n-\t\tif (validate_trailer_args_after_config(&options.trailer_args, &err))\n-\t\t\tdie(\"%s\", err.buf);\n-\n+\t\tvalidate_trailer_args_after_config(&options.trailer_args);\n \t\toptions.flags |= REBASE_FORCE;\n-\t\tstrbuf_release(&err);\n \t}\n \n \tif (preserve_merges_selected)\n-- \n2.51.0\n\n"},{"id":"529366","messageId":"20251022053951.602605-16-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 15/29] rebase: validate trailers with configured separators","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:35Z","receivedAt":"2025-10-22T05:42:51Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Moved validate_trailer_args_after_config() into\ntrailer.c so trailer argument validation reuses\nfind_separator() and respects configured separators.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n builtin/rebase.c | 18 ------------------\n trailer.c        | 25 +++++++++++++++++++++++++\n trailer.h        |  2 ++\n 3 files changed, 27 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 872945a897..a88abe08b4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1124,24 +1124,6 @@ static int check_exec_cmd(const char *cmd)\n \treturn 0;\n }\n \n-static void validate_trailer_args_after_config(const struct strvec *cli_args)\n-{\n-\tfor (size_t i = 0; i < cli_args->nr; i++) {\n-\t\tconst char *txt = cli_args->v[i]; // Key[:=]Val\n-\t\tconst char *sep;\n-\n-\t\tif (!*txt)\n-\t\t\tdie(_(\"empty --trailer argument\"));\n-\n-\t\tsep = strpbrk(txt, \":=\");\n-\n-\t\t/* there must be key bfore seperator */\n-\t\tif (sep && sep == txt)\n-\t\t\tdie(_(\"invalid trailer '%s': missing key before separator\"),\n-\t\t\t    txt);\n-\t}\n-}\n-\n int cmd_rebase(int argc,\n \t       const char **argv,\n \t       const char *prefix,\ndiff --git a/trailer.c b/trailer.c\nindex 1f317f4d37..85e42859ca 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -7,6 +7,7 @@\n #include \"string-list.h\"\n #include \"run-command.h\"\n #include \"commit.h\"\n+#include \"strvec.h\"\n #include \"trailer.h\"\n #include \"list.h\"\n #include \"wrapper.h\"\n@@ -773,6 +774,30 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \tfree(cl_separators);\n }\n \n+void validate_trailer_args_after_config(const struct strvec *cli_args)\n+{\n+\tchar *cl_separators;\n+\n+\ttrailer_config_init();\n+\n+\tcl_separators = xstrfmt(\"=%s\", separators);\n+\n+\tfor (size_t i = 0; i < cli_args->nr; i++) {\n+\t\tconst char *txt = cli_args->v[i];\n+\t\tssize_t separator_pos;\n+\n+\t\tif (!*txt)\n+\t\t\tdie(_(\"empty --trailer argument\"));\n+\n+\t\tseparator_pos = find_separator(txt, cl_separators);\n+\t\tif (separator_pos == 0)\n+\t\t\tdie(_(\"invalid trailer '%s': missing key before separator\"),\n+\t\t    txt);\n+\t}\n+\n+\tfree(cl_separators);\n+}\n+\n static const char *next_line(const char *str)\n {\n \tconst char *nl = strchrnul(str, '\\n');\ndiff --git a/trailer.h b/trailer.h\nindex b4f28bfd65..4654ff9c96 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -68,6 +68,8 @@ void parse_trailers_from_config(struct list_head *config_head);\n void parse_trailers_from_command_line_args(struct list_head *arg_head,\n \t\t\t\t\t   struct list_head *new_trailer_head);\n \n+void validate_trailer_args_after_config(const struct strvec *cli_args);\n+\n void process_trailers_lists(struct list_head *head,\n \t\t\t    struct list_head *arg_head);\n \n-- \n2.51.0\n\n"},{"id":"529367","messageId":"20251022053951.602605-17-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 16/29] sequencer: add trailers to message before writing file","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:36Z","receivedAt":"2025-10-22T05:43:01Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Added trailer processing to the in-memory commit message\nwithin do_pick_commit, ensuring fixup/squash commands\nremain untouched before the message is written.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n sequencer.c | 19 ++++++++-----------\n trailer.c   |  4 ++--\n trailer.h   |  3 +++\n 3 files changed, 13 insertions(+), 13 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 5103ae786c..552e629e4f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2444,6 +2444,14 @@ static int do_pick_commit(struct repository *r,\n \tif (opts->signoff && !is_fixup(command))\n \t\tappend_signoff(&ctx->message, 0, 0);\n \n+\tif (opts->trailer_args.nr && !is_fixup(command)) {\n+\t\tif (amend_strbuf_with_trailers(&ctx->message,\n+\t\t\t\t\t       &opts->trailer_args)) {\n+\t\t\tres = error(_(\"unable to add trailers to commit message\"));\n+\t\t\tgoto leave;\n+\t\t}\n+\t}\n+\n \tif (is_rebase_i(opts) && write_author_script(msg.message) < 0)\n \t\tres = -1;\n \telse if (!opts->strategy ||\n@@ -2519,17 +2527,6 @@ static int do_pick_commit(struct repository *r,\n \t\t\toid_to_hex(&commit->object.oid), msg.subject);\n \t} /* else allow == 0 and there's nothing special to do */\n \n-\tif (!res && opts->trailer_args.nr && !drop_commit) {\n-\t\tconst char *trailer_file =\n-\t\t\tmsg_file ? msg_file : git_path_merge_msg(r);\n-\n-\t\tif (amend_file_with_trailers(trailer_file,\n-\t\t\t\t\t\t&opts->trailer_args)) {\n-\t\t\tres = error(_(\"unable to add trailers to commit message\"));\n-\t\t\tgoto leave;\n-\t\t}\n-\t}\n-\n \tif (!opts->no_commit && !drop_commit) {\n \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n \t\t\tres = do_commit(r, msg_file, author, reflog_action,\ndiff --git a/trailer.c b/trailer.c\nindex 85e42859ca..3e96d1624a 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -1250,8 +1250,8 @@ void trailer_iterator_release(struct trailer_iterator *iter)\n \tstrbuf_release(&iter->key);\n }\n \n-static int amend_strbuf_with_trailers(struct strbuf *buf,\n-\t\t\t\t\t\t\t\t\t  const struct strvec *trailer_args)\n+int amend_strbuf_with_trailers(struct strbuf *buf,\n+\t\t\t       const struct strvec *trailer_args)\n {\n \tstruct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;\n \tLIST_HEAD(new_trailer_head);\ndiff --git a/trailer.h b/trailer.h\nindex 4654ff9c96..479bc137cd 100644\n--- a/trailer.h\n+++ b/trailer.h\n@@ -197,6 +197,9 @@ int trailer_iterator_advance(struct trailer_iterator *iter);\n  */\n void trailer_iterator_release(struct trailer_iterator *iter);\n \n+int amend_strbuf_with_trailers(struct strbuf *buf,\n+\t\t\t       const struct strvec *trailer_args);\n+\n /*\n  * Augment a file to add trailers to it (similar to 'git interpret-trailers').\n  * Returns 0 on success or a non-zero error code on failure.\n-- \n2.51.0\n\n"},{"id":"529368","messageId":"20251022053951.602605-18-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 17/29] t3440: create expect files at point of use","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:37Z","receivedAt":"2025-10-22T05:43:11Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Created the expected trailer files within the individual rebase tests\nthat use them, simplifying the shared history setup and avoiding unused\nfixtures.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 12 ++++--------\n 1 file changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex b1c7b03330..a8108f2296 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -26,14 +26,7 @@ test_expect_success 'setup repo with a small history' '\n \tgit checkout -b conflict-branch first &&\n \ttest_commit file-2 file-2 &&\n \ttest_commit conflict file &&\n-\ttest_commit third file &&\n-\tident=\"$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\" &&\n-\tcreate_expect initial-signed  \"Initial empty commit\" &&\n-\tcreate_expect first-signed    \"first\"                 &&\n-\tcreate_expect second-signed   \"second\"                &&\n-\tcreate_expect file2-signed    \"file-2\"                &&\n-\tcreate_expect third-signed    \"third\"                 &&\n-\tcreate_expect conflict-signed \"conflict\"\n+\ttest_commit third file\n '\n \n test_expect_success 'apply backend is rejected with --trailer' '\n@@ -74,6 +67,7 @@ test_expect_success 'multiple Signed-off-by trailers all preserved' '\n '\n \n test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n+\tcreate_expect file2-signed \"file-2\" &&\n \tgit reset --hard third &&\n \ttest_must_fail git rebase -m \\\n \t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n@@ -85,6 +79,8 @@ test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n '\n \n test_expect_success 'rebase --root --trailer updates every commit' '\n+\tcreate_expect initial-signed \"Initial empty commit\" &&\n+\tcreate_expect first-signed \"first\" &&\n \tgit checkout first &&\n \tgit rebase --root --keep-empty \\\n \t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" &&\n-- \n2.51.0\n\n"},{"id":"529369","messageId":"20251022053951.602605-19-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 18/29] t3440: check apply backend error includes option","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:38Z","receivedAt":"2025-10-22T05:43:21Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nUpdated the rebase trailer test to assert that\nthe apply-backend error explicitly includes the\n--trailer option in its message while retaining\nthe existing backend check.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex a8108f2296..6b1c93b4cb 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -34,7 +34,7 @@ test_expect_success 'apply backend is rejected with --trailer' '\n \ttest_expect_code 128 \\\n \tgit rebase --apply --trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n \t\t\t\tHEAD^ 2>err &&\n-\ttest_grep \"requires the merge backend\" err &&\n+\ttest_grep \"fatal: --trailer requires the merge backend\" err &&\n \ttest_cmp_rev HEAD $head_before\n '\n \n-- \n2.51.0\n\n"},{"id":"529370","messageId":"20251022053951.602605-20-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 19/29] t3440: use test_commit_message for trailer checks","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:39Z","receivedAt":"2025-10-22T05:43:35Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Replaced the pipeline-based trailer assertions with\nexplicit expectations verified by test_commit_message,\nensuring the rebase trailer tests catch git command\nfailures reliably.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex 6b1c93b4cb..1571dd2c97 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -52,18 +52,28 @@ test_expect_success 'reject trailer with missing key before separator' '\n \n test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' '\n \tgit reset --hard third &&\n-\tgit -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add rebase -m --trailer \"Bug: 123\" --trailer \"Bug: 456\" HEAD~1 &&\n-\tgit cat-file commit HEAD | grep \"^Bug: 456\" &&\n-\tgit cat-file commit HEAD | grep -v \"^Bug: 123\"\n+\tgit -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add \\\n+\t\trebase -m --trailer \"Bug: 123\" --trailer \"Bug: 456\" HEAD~1 &&\n+\tcat >expect <<-\\EOF &&\n+\tthird\n+\n+\tBug: 456\n+\tEOF\n+\ttest_commit_message HEAD expect\n '\n \n test_expect_success 'multiple Signed-off-by trailers all preserved' '\n \tgit reset --hard third &&\n \tgit rebase -m \\\n-\t\t--trailer \"Signed-off-by: Dev A <a@ex.com>\" \\\n-\t\t--trailer \"Signed-off-by: Dev B <b@ex.com>\" HEAD~1 &&\n-\tgit cat-file commit HEAD | grep -c \"^Signed-off-by:\" >count &&\n-\ttest \"$(cat count)\" = 2   # two new commits\n+\t\t\t--trailer \"Signed-off-by: Dev A <a@ex.com>\" \\\n+\t\t\t--trailer \"Signed-off-by: Dev B <b@ex.com>\" HEAD~1 &&\n+\tcat >expect <<-\\EOF &&\n+\tthird\n+\n+\tSigned-off-by: Dev A <a@ex.com>\n+\tSigned-off-by: Dev B <b@ex.com>\n+\tEOF\n+\ttest_commit_message HEAD expect\n '\n \n test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n-- \n2.51.0\n\n"},{"id":"529371","messageId":"20251022053951.602605-21-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 20/29] t3440: drop redundant resets and pass branch to rebase where needed","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:40Z","receivedAt":"2025-10-22T05:43:46Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nStop hard-resetting to third in these tests. Where the branch matters,\ninvoke git rebase -m ... HEAD~1 third to make the target explicit and\npreserve the original semantics.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 9 ++-------\n 1 file changed, 2 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex 1571dd2c97..504bdd86fc 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -39,21 +39,18 @@ test_expect_success 'apply backend is rejected with --trailer' '\n '\n \n test_expect_success 'reject empty --trailer argument' '\n-\tgit reset --hard third &&\n \ttest_expect_code 128 git rebase -m --trailer \"\" HEAD^ 2>err &&\n \ttest_grep \"empty --trailer\" err\n '\n \n test_expect_success 'reject trailer with missing key before separator' '\n-\tgit reset --hard third &&\n \ttest_expect_code 128 git rebase -m --trailer \": no-key\" HEAD^ 2>err &&\n \ttest_grep \"missing key before separator\" err\n '\n \n test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' '\n-\tgit reset --hard third &&\n \tgit -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add \\\n-\t\trebase -m --trailer \"Bug: 123\" --trailer \"Bug: 456\" HEAD~1 &&\n+\t\trebase -m --trailer \"Bug: 123\" --trailer \"Bug: 456\" HEAD~1 third &&\n \tcat >expect <<-\\EOF &&\n \tthird\n \n@@ -63,10 +60,9 @@ test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last'\n '\n \n test_expect_success 'multiple Signed-off-by trailers all preserved' '\n-\tgit reset --hard third &&\n \tgit rebase -m \\\n \t\t\t--trailer \"Signed-off-by: Dev A <a@ex.com>\" \\\n-\t\t\t--trailer \"Signed-off-by: Dev B <b@ex.com>\" HEAD~1 &&\n+\t\t\t--trailer \"Signed-off-by: Dev B <b@ex.com>\" HEAD~1 third &&\n \tcat >expect <<-\\EOF &&\n \tthird\n \n@@ -78,7 +74,6 @@ test_expect_success 'multiple Signed-off-by trailers all preserved' '\n \n test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n \tcreate_expect file2-signed \"file-2\" &&\n-\tgit reset --hard third &&\n \ttest_must_fail git rebase -m \\\n \t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n \t\tsecond third &&\n-- \n2.51.0\n\n"},{"id":"529372","messageId":"20251022053951.602605-22-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 21/29] t3440: assert trailer on HEAD after conflict rebase","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:41Z","receivedAt":"2025-10-22T05:43:55Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nSwitch the test to check the trailer on HEAD (not HEAD~2) and build the\nexpected message for \"third\", matching the rebased tip after conflicts.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex 504bdd86fc..fbc6f209f1 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -73,14 +73,14 @@ test_expect_success 'multiple Signed-off-by trailers all preserved' '\n '\n \n test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n-\tcreate_expect file2-signed \"file-2\" &&\n+\tcreate_expect third-signed \"third\" &&\n \ttest_must_fail git rebase -m \\\n \t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n \t\tsecond third &&\n \tgit checkout --theirs file &&\n \tgit add file &&\n \tgit rebase --continue &&\n-\ttest_commit_message HEAD~2 file2-signed\n+\ttest_commit_message HEAD third-signed\n '\n \n test_expect_success 'rebase --root --trailer updates every commit' '\n-- \n2.51.0\n\n"},{"id":"529373","messageId":"20251022053951.602605-23-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 22/29] rebase: persist --trailer options across restarts","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:42Z","receivedAt":"2025-10-22T05:44:05Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"t3440 showed rebase -m loses trailers after conflicts.\nEach --continue spawns a fresh replay_opts without trailer_args.\nSerialize them into rebase-merge/trailer so new runs reload them.\nThis keeps the user requested trailers intact when resuming.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n sequencer.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 552e629e4f..c02364cfce 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -209,6 +209,7 @@ static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, \"rebase-merge/reschedul\n static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, \"rebase-merge/no-reschedule-failed-exec\")\n static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, \"rebase-merge/drop_redundant_commits\")\n static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, \"rebase-merge/keep_redundant_commits\")\n+static GIT_PATH_FUNC(rebase_path_trailer, \"rebase-merge/trailer\")\n \n /*\n  * A 'struct replay_ctx' represents the private state of the sequencer.\n@@ -3244,6 +3245,17 @@ static int read_populate_opts(struct replay_opts *opts)\n \n \t\tread_strategy_opts(opts, &buf);\n \t\tstrbuf_reset(&buf);\n+\t\tif (strbuf_read_file(&buf, rebase_path_trailer(), 0) >= 0) {\n+\t\t\tchar *p = buf.buf, *nl;\n+\n+\t\t\twhile ((nl = strchr(p, '\\n'))) {\n+\t\t\t\t*nl = '\\0';\n+\t\t\t\tif (*p)\n+\t\t\t\t\tstrvec_push(&opts->trailer_args, p);\n+\t\t\t\tp = nl + 1;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&buf);\n+\t\t}\n \n \t\tif (read_oneliner(&ctx->current_fixups,\n \t\t\t\t  rebase_path_current_fixups(),\n@@ -3338,6 +3350,14 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,\n \t\twrite_file(rebase_path_reschedule_failed_exec(), \"%s\", \"\");\n \telse\n \t\twrite_file(rebase_path_no_reschedule_failed_exec(), \"%s\", \"\");\n+\tif (opts->trailer_args.nr) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tfor (size_t i = 0; i < opts->trailer_args.nr; i++)\n+\t\t\tstrbuf_addf(&buf, \"%s\\n\", opts->trailer_args.v[i]);\n+\t\twrite_file(rebase_path_trailer(), \"%s\", buf.buf);\n+\t\tstrbuf_release(&buf);\n+\t}\n \n \treturn 0;\n }\n-- \n2.51.0\n\n"},{"id":"529374","messageId":"20251022053951.602605-24-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 23/29] t3440: remove redundant --keep-empty","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:43Z","receivedAt":"2025-10-22T05:44:15Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\n--keep-empty is the default these days so\nwe can drop that.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex fbc6f209f1..4687be3a21 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -87,7 +87,7 @@ test_expect_success 'rebase --root --trailer updates every commit' '\n \tcreate_expect initial-signed \"Initial empty commit\" &&\n \tcreate_expect first-signed \"first\" &&\n \tgit checkout first &&\n-\tgit rebase --root --keep-empty \\\n+\tgit rebase --root \\\n \t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" &&\n \ttest_commit_message HEAD   first-signed &&\n \ttest_commit_message HEAD^  initial-signed\n-- \n2.51.0\n\n"},{"id":"529375","messageId":"20251022053951.602605-25-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 24/29] t3440: use helper for trailer checks","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:44Z","receivedAt":"2025-10-22T05:44:25Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Introduce expect_trailer_msg() to wrap test_commit_message\nand dedupe the trailer via REVIEWED_BY_TRAILER.\nDrop create_expect and temp files. In the conflict case,\nassert on HEAD (rebased \"third\") instead of HEAD~2. Update\n--apply rejection and --root tests to use the helper.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 23 +++++++++++------------\n 1 file changed, 11 insertions(+), 12 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex 4687be3a21..4b0b0ee2d3 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -11,11 +11,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-rebase.sh # test_commit_message, helpers\n \n-create_expect() {\n-\tcat >\"$1\" <<-EOF\n+REVIEWED_BY_TRAILER=\"Reviewed-by: Dev <dev@example.com>\"\n+\n+expect_trailer_msg() {\n+\ttest_commit_message \"$1\" <<-EOF\n \t$2\n \n-\tReviewed-by: Dev <dev@example.com>\n+\t${3:-$REVIEWED_BY_TRAILER}\n \tEOF\n }\n \n@@ -32,7 +34,7 @@ test_expect_success 'setup repo with a small history' '\n test_expect_success 'apply backend is rejected with --trailer' '\n \thead_before=$(git rev-parse HEAD) &&\n \ttest_expect_code 128 \\\n-\tgit rebase --apply --trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n+\tgit rebase --apply --trailer \"$REVIEWED_BY_TRAILER\" \\\n \t\t\t\tHEAD^ 2>err &&\n \ttest_grep \"fatal: --trailer requires the merge backend\" err &&\n \ttest_cmp_rev HEAD $head_before\n@@ -73,23 +75,20 @@ test_expect_success 'multiple Signed-off-by trailers all preserved' '\n '\n \n test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n-\tcreate_expect third-signed \"third\" &&\n \ttest_must_fail git rebase -m \\\n-\t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n+\t\t--trailer \"$REVIEWED_BY_TRAILER\" \\\n \t\tsecond third &&\n \tgit checkout --theirs file &&\n \tgit add file &&\n \tgit rebase --continue &&\n-\ttest_commit_message HEAD third-signed\n+\texpect_trailer_msg HEAD \"third\"\n '\n \n test_expect_success 'rebase --root --trailer updates every commit' '\n-\tcreate_expect initial-signed \"Initial empty commit\" &&\n-\tcreate_expect first-signed \"first\" &&\n \tgit checkout first &&\n \tgit rebase --root \\\n-\t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" &&\n-\ttest_commit_message HEAD   first-signed &&\n-\ttest_commit_message HEAD^  initial-signed\n+\t\t--trailer \"$REVIEWED_BY_TRAILER\" &&\n+\texpect_trailer_msg HEAD  \"first\" &&\n+\texpect_trailer_msg HEAD^ \"Initial empty commit\"\n '\n test_done\n-- \n2.51.0\n\n"},{"id":"529376","messageId":"20251022053951.602605-26-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 25/29] t3440: test --trailer without values","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:45Z","receivedAt":"2025-10-22T05:44:35Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Add a regression test to ensure git\nrebase --trailer accepts trailers without\nvalues while preserving the separator’s\ntrailing space in the recorded message.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex 4b0b0ee2d3..bed6955001 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -50,6 +50,16 @@ test_expect_success 'reject trailer with missing key before separator' '\n \ttest_grep \"missing key before separator\" err\n '\n \n+test_expect_success 'allow trailer with missing value after separator' '\n+\tgit rebase -m --trailer \"Acked-by:\" HEAD~1 third &&\n+\tsed -e \"s/_/ /g\" <<-\\EOF >expect &&\n+\tthird\n+\n+\tAcked-by:_\n+\tEOF\n+\ttest_commit_message HEAD expect\n+'\n+\n test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' '\n \tgit -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add \\\n \t\trebase -m --trailer \"Bug: 123\" --trailer \"Bug: 456\" HEAD~1 third &&\n-- \n2.51.0\n\n"},{"id":"529377","messageId":"20251022053951.602605-27-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 26/29] t3440: convert ex.com to example.com","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:46Z","receivedAt":"2025-10-22T05:44:45Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"From: Li Chen <chenl311@chinatelecom.cn>\n\nLets use example.com here rather than some\nrandom domain that might actually exist.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex bed6955001..bea98d08c6 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -73,13 +73,13 @@ test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last'\n \n test_expect_success 'multiple Signed-off-by trailers all preserved' '\n \tgit rebase -m \\\n-\t\t\t--trailer \"Signed-off-by: Dev A <a@ex.com>\" \\\n-\t\t\t--trailer \"Signed-off-by: Dev B <b@ex.com>\" HEAD~1 third &&\n+\t\t\t--trailer \"Signed-off-by: Dev A <a@example.com>\" \\\n+\t\t\t--trailer \"Signed-off-by: Dev B <b@example.com>\" HEAD~1 third &&\n \tcat >expect <<-\\EOF &&\n \tthird\n \n-\tSigned-off-by: Dev A <a@ex.com>\n-\tSigned-off-by: Dev B <b@ex.com>\n+\tSigned-off-by: Dev A <a@example.com>\n+\tSigned-off-by: Dev B <b@example.com>\n \tEOF\n \ttest_commit_message HEAD expect\n '\n-- \n2.51.0\n\n"},{"id":"529378","messageId":"20251022053951.602605-28-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 27/29] t3440: ensure trailers persist after rebase continue","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:47Z","receivedAt":"2025-10-22T05:44:55Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Rebuilt the conflict test branch, added a fourth commit,\nand asserted that both the conflicted and subsequent commits\nreceive the --trailer data in t/t3440-rebase-trailer.sh.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 9 ++++++---\n 1 file changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex bea98d08c6..35d2054716 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -85,13 +85,16 @@ test_expect_success 'multiple Signed-off-by trailers all preserved' '\n '\n \n test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n+\tgit checkout -B conflict-branch third &&\n+\ttest_commit fourth file &&\n \ttest_must_fail git rebase -m \\\n-\t\t--trailer \"$REVIEWED_BY_TRAILER\" \\\n-\t\tsecond third &&\n+\t\t\t--trailer \"$REVIEWED_BY_TRAILER\" \\\n+\t\t\tsecond &&\n \tgit checkout --theirs file &&\n \tgit add file &&\n \tgit rebase --continue &&\n-\texpect_trailer_msg HEAD \"third\"\n+\texpect_trailer_msg HEAD \"fourth\" &&\n+\texpect_trailer_msg HEAD^ \"third\"\n '\n \n test_expect_success 'rebase --root --trailer updates every commit' '\n-- \n2.51.0\n\n"},{"id":"529379","messageId":"20251022053951.602605-29-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 28/29] t3440: exercise trailer config mapping","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:48Z","receivedAt":"2025-10-22T05:45:05Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Updated the rebase --root --trailer test\nto exercise trailer.review.key configuration\nand the --trailer= CLI form that uses an\nequals separator, ensuring we still add the\nexpected Reviewed-by trailer.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n t/t3440-rebase-trailer.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex 35d2054716..d697bf558b 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -99,8 +99,8 @@ test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n \n test_expect_success 'rebase --root --trailer updates every commit' '\n \tgit checkout first &&\n-\tgit rebase --root \\\n-\t\t--trailer \"$REVIEWED_BY_TRAILER\" &&\n+\tgit -c trailer.review.key=Reviewed-by rebase --root \\\n+\t\t--trailer=review=\"Dev <dev@example.com>\" &&\n \texpect_trailer_msg HEAD  \"first\" &&\n \texpect_trailer_msg HEAD^ \"Initial empty commit\"\n '\n-- \n2.51.0\n\n"},{"id":"529380","messageId":"20251022053951.602605-30-me@linux.beauty","threadId":"64367","inReplyTo":"20251022053951.602605-1-me@linux.beauty","subject":"[PATCH v5 29/29] sequencer: honor --trailer with fixup -C","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-10-22T05:39:49Z","receivedAt":"2025-10-22T05:45:15Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Add an interactive rebase test that exercises\ntodo lists containing fixup and fixup -C commands,\nand teach append_squash_message() to append trailers\nwhen replacing the commit message.\n\nSigned-off-by: Li Chen <chenl311@chinatelecom.cn>\n---\n sequencer.c               |  4 ++++\n t/t3440-rebase-trailer.sh | 27 +++++++++++++++++++++++++++\n 2 files changed, 31 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex c02364cfce..fbf35cb474 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2027,6 +2027,10 @@ static int append_squash_message(struct strbuf *buf, const char *body,\n \t\tif (opts->signoff)\n \t\t\tappend_signoff(buf, 0, 0);\n \n+\t\tif (opts->trailer_args.nr &&\n+\t\t\tamend_strbuf_with_trailers(buf, &opts->trailer_args))\n+\t\t\treturn error(_(\"unable to add trailers to commit message\"));\n+\n \t\tif ((command == TODO_FIXUP) &&\n \t\t    (flag & TODO_REPLACE_FIXUP_MSG) &&\n \t\t    (file_exists(rebase_path_fixup_msg()) ||\ndiff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\nindex d697bf558b..d0e0434664 100755\n--- a/t/t3440-rebase-trailer.sh\n+++ b/t/t3440-rebase-trailer.sh\n@@ -97,6 +97,33 @@ test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n \texpect_trailer_msg HEAD^ \"third\"\n '\n \n+test_expect_success '--trailer handles fixup commands in todo list' '\n+\tgit checkout -B fixup-trailer HEAD &&\n+\ttest_commit fixup-base base &&\n+\ttest_commit fixup-second second &&\n+\tfirst_short=$(git rev-parse --short fixup-base) &&\n+\tsecond_short=$(git rev-parse --short fixup-second) &&\n+\tcat >todo <<EOF &&\n+pick $first_short fixup-base\n+fixup $second_short fixup-second\n+EOF\n+\t(\n+\t\tset_replace_editor todo &&\n+\t\tgit rebase -i --trailer \"$REVIEWED_BY_TRAILER\" HEAD~2\n+\t) &&\n+\texpect_trailer_msg HEAD \"fixup-base\" &&\n+\tgit reset --hard fixup-second &&\n+\tcat >todo <<EOF &&\n+pick $first_short fixup-base\n+fixup -C $second_short fixup-second\n+EOF\n+\t(\n+\t\tset_replace_editor todo &&\n+\t\tgit rebase -i --trailer \"$REVIEWED_BY_TRAILER\" HEAD~2\n+\t) &&\n+\texpect_trailer_msg HEAD \"fixup-second\"\n+'\n+\n test_expect_success 'rebase --root --trailer updates every commit' '\n \tgit checkout first &&\n \tgit -c trailer.review.key=Reviewed-by rebase --root \\\n-- \n2.51.0\n\n"},{"id":"529501","messageId":"7d12b046-365f-441c-af8e-8a39d61efbbd@gmail.com","threadId":"64367","inReplyTo":"20251022053951.602605-2-me@linux.beauty","subject":"Re: [PATCH v5 01/29] trailer: append trailers in-process and drop the fork to `interpret-trailers`","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-10-23T13:21:28Z","receivedAt":"2025-10-23T13:21:35Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Li\n\nOn 22/10/2025 06:39, Li Chen wrote:\n> From: Li Chen <chenl311@chinatelecom.cn>\n> \n> Route all trailer insertion through trailer_process() and make\n> builtin/interpret-trailers just do file I/O before calling into it.\n> amend_file_with_trailers() now shares the same code path.\n> \n> This removes the fork/exec and tempfile juggling, cutting overhead and\n> simplifying error handling. No functional change. It also\n> centralizes logic to prepare for follow-up rebase --trailer patch.\n> \n> Signed-off-by: Li Chen <chenl311@chinatelecom.cn>\n\nWhen I review v3 of this series I said\n\n>> As I said above reusing the existing code as you have done here is a\n>> much better approach. However it would be much easier to review if\n>> the code movement was separated from the refactoring. I'm also\n>> struggling to see the benefit of a lot of the refactoring - I was\n>> expecting the conversion to use an strubf would essentially look like\n>> fwrite() being replaced with strbuf_add() and fprintf() being\n>> replaced with strbuf_addf() etc. rather than reworking the logic.\n\nUnfortunately this version has the same code changes as v3 that make it\nare virtually impossible to verify if the behavior is changed or not.\nThe diff below shows what I was hoping to see as the first step. It\nrefactors interpret_trailers() in place to factor out the code that\nprocesses the trailers into a separate function. That makes it easy to\nsee that fwrite() is replaced with strbuf_add() etc. and so verify that\nthe behavior is unchanged. If you view the diff with \"--color-moved\"\nyou'll see that virtually all of the code in the new\ninterpret_trailers() function is moved from the old one which in turn\nmakes it easy to verify that there is no change in behavior. I would\nexpect the next patch in the series to move the new function for\nprocessing trailers into trailer.c and the patch after that to refactor\namend_file_with_trailers() to add and use amend_strbuf_with_trailers()\nand stop forking \"git interpret-trailers\". Then builtin/rebase.c can be\nmodified to add support for trailers using amend_strbuf_with_trailers().\n\nThanks\n\nPhillip\n\n---- 8< ----\ndiff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\nindex 41b0750e5af..4c90580ffff 100644\n--- a/builtin/interpret-trailers.c\n+++ b/builtin/interpret-trailers.c\n@@ -136,32 +136,21 @@ static void read_input_file(struct strbuf *sb, const char *file)\n  \tstrbuf_complete_line(sb);\n  }\n  \n-static void interpret_trailers(const struct process_trailer_options *opts,\n-\t\t\t       struct list_head *new_trailer_head,\n-\t\t\t       const char *file)\n+static void process_trailers(const struct process_trailer_options *opts,\n+\t\t\t     struct list_head *new_trailer_head,\n+\t\t\t     struct strbuf *sb, struct strbuf *out)\n  {\n  \tLIST_HEAD(head);\n-\tstruct strbuf sb = STRBUF_INIT;\n-\tstruct strbuf trailer_block_sb = STRBUF_INIT;\n  \tstruct trailer_block *trailer_block;\n-\tFILE *outfile = stdout;\n-\n-\ttrailer_config_init();\n-\n-\tread_input_file(&sb, file);\n-\n-\tif (opts->in_place)\n-\t\toutfile = create_in_place_tempfile(file);\n-\n-\ttrailer_block = parse_trailers(opts, sb.buf, &head);\n+\n+\ttrailer_block = parse_trailers(opts, sb->buf, &head);\n  \n  \t/* Print the lines before the trailer block */\n  \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);\n+\t\tstrbuf_add(out, sb->buf, trailer_block_start(trailer_block));\n  \n  \tif (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n-\t\tfprintf(outfile, \"\\n\");\n-\n+\t\tstrbuf_addch(out, '\\n');\n  \n  \tif (!opts->only_input) {\n  \t\tLIST_HEAD(config_head);\n@@ -173,22 +162,40 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n  \t}\n  \n  \t/* Print trailer block. */\n-\tformat_trailers(opts, &head, &trailer_block_sb);\n+\tformat_trailers(opts, &head, out);\n  \tfree_trailers(&head);\n-\tfwrite(trailer_block_sb.buf, 1, trailer_block_sb.len, outfile);\n-\tstrbuf_release(&trailer_block_sb);\n  \n  \t/* Print the lines after the trailer block as is. */\n  \tif (!opts->only_trailers)\n-\t\tfwrite(sb.buf + trailer_block_end(trailer_block), 1,\n-\t\t       sb.len - trailer_block_end(trailer_block), outfile);\n+\t\tstrbuf_add(out, sb->buf + trailer_block_end(trailer_block),\n+\t\t\t   sb->len - trailer_block_end(trailer_block));\n  \ttrailer_block_release(trailer_block);\n-\n+}\n+\n+static void interpret_trailers(const struct process_trailer_options *opts,\n+\t\t\t       struct list_head *new_trailer_head,\n+\t\t\t       const char *file)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf out = STRBUF_INIT;\n+\tFILE *outfile = stdout;\n+\n+\ttrailer_config_init();\n+\n+\tread_input_file(&sb, file);\n+\n+\tif (opts->in_place)\n+\t\toutfile = create_in_place_tempfile(file);\n+\n+\tprocess_trailers(opts, new_trailer_head, &sb, &out);\n+\n+\tfwrite(out.buf, out.len, 1, outfile);\n  \tif (opts->in_place)\n  \t\tif (rename_tempfile(&trailers_tempfile, file))\n  \t\t\tdie_errno(_(\"could not rename temporary file to %s\"), file);\n  \n  \tstrbuf_release(&sb);\n+\tstrbuf_release(&out);\n  }\n  \n  int cmd_interpret_trailers(int argc,\n"},{"id":"529502","messageId":"66ff7ada-b584-4a0b-99af-9e7d234b75ed@gmail.com","threadId":"64367","inReplyTo":"20251022053951.602605-10-me@linux.beauty","subject":"Re: [PATCH v5 09/29] rebase: support --trailer","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-10-23T13:21:47Z","receivedAt":"2025-10-23T13:21:53Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Chen\n\nThe code here seems to be largely unchanged from v3. I see there are \nsome later patches in response to my review. They should be squashed \ninto this patch as Kristoffer has previously explained.\n\nThanks\n\nPhillip\n\nOn 22/10/2025 06:39, Li Chen wrote:\n> From: Li Chen <chenl311@chinatelecom.cn>\n> \n> Implement a new `--trailer <text>` option for `git rebase`\n> (support merge backend only now), which appends arbitrary\n> trailer lines to each rebased commit message.\n> \n> Reject it if the user passes an option that requires the\n> apply backend (git am) since it lacks message‑filter/trailer\n> hook. otherwise we can just use the merge backend.\n> \n> Automatically set REBASE_FORCE when any trailer is supplied.\n> \n> And reject invalid input before user edits the interactive file.\n> \n> Signed-off-by: Li Chen <chenl311@chinatelecom.cn>\n> ---\n>   Documentation/git-rebase.adoc |  9 +++-\n>   builtin/rebase.c              | 89 +++++++++++++++++++++++++++++++++\n>   sequencer.c                   | 13 +++++\n>   sequencer.h                   |  4 +-\n>   t/meson.build                 |  1 +\n>   t/t3440-rebase-trailer.sh     | 94 +++++++++++++++++++++++++++++++++++\n>   6 files changed, 208 insertions(+), 2 deletions(-)\n>   create mode 100755 t/t3440-rebase-trailer.sh\n> \n> diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc\n> index 005caf6164..4d2fe4be6e 100644\n> --- a/Documentation/git-rebase.adoc\n> +++ b/Documentation/git-rebase.adoc\n> @@ -487,9 +487,16 @@ See also INCOMPATIBLE OPTIONS below.\n>   \tAdd a `Signed-off-by` trailer to all the rebased commits. Note\n>   \tthat if `--interactive` is given then only commits marked to be\n>   \tpicked, edited or reworded will have the trailer added.\n> -+\n> +\n>   See also INCOMPATIBLE OPTIONS below.\n>   \n> +--trailer=<trailer>::\n> +       Append the given trailer line(s) to every rebased commit\n> +       message, processed via linkgit:git-interpret-trailers[1].\n> +       When this option is present *rebase automatically implies*\n> +       `--force-rebase` so that fast‑forwarded commits are also\n> +       rewritten.\n> +\n>   -i::\n>   --interactive::\n>   \tMake a list of the commits which are about to be rebased.  Let the\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index c468828189..3db1439b52 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -36,6 +36,7 @@\n>   #include \"reset.h\"\n>   #include \"trace2.h\"\n>   #include \"hook.h\"\n> +#include \"trailer.h\"\n>   \n>   static char const * const builtin_rebase_usage[] = {\n>   \tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n> @@ -113,6 +114,7 @@ struct rebase_options {\n>   \tenum action action;\n>   \tchar *reflog_action;\n>   \tint signoff;\n> +\tstruct strvec trailer_args;\n>   \tint allow_rerere_autoupdate;\n>   \tint keep_empty;\n>   \tint autosquash;\n> @@ -143,6 +145,7 @@ struct rebase_options {\n>   \t\t.flags = REBASE_NO_QUIET, \t\t\\\n>   \t\t.git_am_opts = STRVEC_INIT,\t\t\\\n>   \t\t.exec = STRING_LIST_INIT_NODUP,\t\t\\\n> +\t\t.trailer_args = STRVEC_INIT,  \\\n>   \t\t.git_format_patch_opt = STRBUF_INIT,\t\\\n>   \t\t.fork_point = -1,\t\t\t\\\n>   \t\t.reapply_cherry_picks = -1,             \\\n> @@ -166,6 +169,7 @@ static void rebase_options_release(struct rebase_options *opts)\n>   \tfree(opts->strategy);\n>   \tstring_list_clear(&opts->strategy_opts, 0);\n>   \tstrbuf_release(&opts->git_format_patch_opt);\n> +\tstrvec_clear(&opts->trailer_args);\n>   }\n>   \n>   static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n> @@ -177,6 +181,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n>   \tsequencer_init_config(&replay);\n>   \n>   \treplay.signoff = opts->signoff;\n> +\n> +\tfor (size_t i = 0; i < opts->trailer_args.nr; i++)\n> +\t\tstrvec_push(&replay.trailer_args, opts->trailer_args.v[i]);\n> +\n>   \treplay.allow_ff = !(opts->flags & REBASE_FORCE);\n>   \tif (opts->allow_rerere_autoupdate)\n>   \t\treplay.allow_rerere_auto = opts->allow_rerere_autoupdate;\n> @@ -434,6 +442,8 @@ static int read_basic_state(struct rebase_options *opts)\n>   \tstruct strbuf head_name = STRBUF_INIT;\n>   \tstruct strbuf buf = STRBUF_INIT;\n>   \tstruct object_id oid;\n> +\tconst char trailer_state_name[] = \"trailer\";\n> +\tconst char *path = state_dir_path(trailer_state_name, opts);\n>   \n>   \tif (!read_oneliner(&head_name, state_dir_path(\"head-name\", opts),\n>   \t\t\t   READ_ONELINER_WARN_MISSING) ||\n> @@ -502,11 +512,31 @@ static int read_basic_state(struct rebase_options *opts)\n>   \n>   \tstrbuf_release(&buf);\n>   \n> +\tif (strbuf_read_file(&buf, path, 0) >= 0) {\n> +\t\tconst char *p = buf.buf, *end = buf.buf + buf.len;\n> +\n> +\t\twhile (p < end) {\n> +\t\t\tchar *nl = memchr(p, '\\n', end - p);\n> +\t\t\tif (!nl)\n> +\t\t\t\tdie(\"nl shouldn't be NULL\");\n> +\t\t\t*nl = '\\0';\n> +\n> +\t\t\tif (*p)\n> +\t\t\t\tstrvec_push(&opts->trailer_args, p);\n> +\n> +\t\t\tp = nl + 1;\n> +\t\t}\n> +\t\tstrbuf_release(&buf);\n> +\t}\n> +\tstrbuf_release(&buf);\n> +\n>   \treturn 0;\n>   }\n>   \n>   static int rebase_write_basic_state(struct rebase_options *opts)\n>   {\n> +\tconst char trailer_state_name[] = \"trailer\";\n> +\n>   \twrite_file(state_dir_path(\"head-name\", opts), \"%s\",\n>   \t\t   opts->head_name ? opts->head_name : \"detached HEAD\");\n>   \twrite_file(state_dir_path(\"onto\", opts), \"%s\",\n> @@ -528,6 +558,21 @@ static int rebase_write_basic_state(struct rebase_options *opts)\n>   \tif (opts->signoff)\n>   \t\twrite_file(state_dir_path(\"signoff\", opts), \"--signoff\");\n>   \n> +\t/*\n> +\t * save opts->trailer_args into state_dir/trailer\n> +\t */\n> +\tif (opts->trailer_args.nr) {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\n> +\t\tfor (size_t i = 0; i < opts->trailer_args.nr; i++) {\n> +\t\t\t\tstrbuf_addstr(&buf, opts->trailer_args.v[i]);\n> +\t\t\t\tstrbuf_addch(&buf, '\\n');\n> +\t\t}\n> +\t\twrite_file(state_dir_path(trailer_state_name, opts),\n> +\t\t\t\t   \"%s\", buf.buf);\n> +\t\tstrbuf_release(&buf);\n> +\t}\n> +\n>   \treturn 0;\n>   }\n>   \n> @@ -1084,6 +1129,35 @@ static int check_exec_cmd(const char *cmd)\n>   \treturn 0;\n>   }\n>   \n> +static int validate_trailer_args_after_config(const struct strvec *cli_args,\n> +\t\t\t\t\t      struct strbuf *err)\n> +{\n> +\tfor (size_t i = 0; i < cli_args->nr; i++) {\n> +\t\tconst char *raw = cli_args->v[i];\n> +\t\tconst char *txt; // Key[:=]Val\n> +\t\tconst char *sep;\n> +\n> +\t\tif (!skip_prefix(raw, \"--trailer=\", &txt))\n> +\t\t\ttxt = raw;\n> +\n> +\t\tif (!*txt) {\n> +\t\t\tstrbuf_addstr(err, _(\"empty --trailer argument\"));\n> +\t\t\treturn -1;\n> +\t\t}\n> +\n> +\t\tsep = strpbrk(txt, \":=\");\n> +\n> +\t\t/* there must be key bfore seperator */\n> +\t\tif (sep && sep == txt) {\n> +\t\t\tstrbuf_addf(err,\n> +\t\t\t\t    _(\"invalid trailer '%s': missing key before separator\"),\n> +\t\t\t\t    txt);\n> +\t\t\treturn -1;\n> +\t\t}\n> +\t}\n> +\treturn 0;\n> +}\n> +\n>   int cmd_rebase(int argc,\n>   \t       const char **argv,\n>   \t       const char *prefix,\n> @@ -1132,6 +1206,8 @@ int cmd_rebase(int argc,\n>   \t\t\t.flags = PARSE_OPT_NOARG,\n>   \t\t\t.defval = REBASE_DIFFSTAT,\n>   \t\t},\n> +\t\tOPT_STRVEC(0, \"trailer\", &options.trailer_args, N_(\"trailer\"),\n> +\t\t\t\tN_(\"add custom trailer(s)\")),\n>   \t\tOPT_BOOL(0, \"signoff\", &options.signoff,\n>   \t\t\t N_(\"add a Signed-off-by trailer to each commit\")),\n>   \t\tOPT_BOOL(0, \"committer-date-is-author-date\",\n> @@ -1285,6 +1361,16 @@ int cmd_rebase(int argc,\n>   \t\t\t     builtin_rebase_options,\n>   \t\t\t     builtin_rebase_usage, 0);\n>   \n> +\tif (options.trailer_args.nr) {\n> +\t\tstruct strbuf err = STRBUF_INIT;\n> +\n> +\t\tif (validate_trailer_args_after_config(&options.trailer_args, &err))\n> +\t\t\tdie(\"%s\", err.buf);\n> +\n> +\t\toptions.flags |= REBASE_FORCE;\n> +\t\tstrbuf_release(&err);\n> +\t}\n> +\n>   \tif (preserve_merges_selected)\n>   \t\tdie(_(\"--preserve-merges was replaced by --rebase-merges\\n\"\n>   \t\t\t\"Note: Your `pull.rebase` configuration may also be set to 'preserve',\\n\"\n> @@ -1542,6 +1628,9 @@ int cmd_rebase(int argc,\n>   \tif (options.root && !options.onto_name)\n>   \t\timply_merge(&options, \"--root without --onto\");\n>   \n> +\tif (options.trailer_args.nr)\n> +\t\timply_merge(&options, \"--trailer\");\n> +\n>   \tif (isatty(2) && options.flags & REBASE_NO_QUIET)\n>   \t\tstrbuf_addstr(&options.git_format_patch_opt, \" --progress\");\n>   \n> diff --git a/sequencer.c b/sequencer.c\n> index 5476d39ba9..5103ae786c 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -420,6 +420,7 @@ void replay_opts_release(struct replay_opts *opts)\n>   \tif (opts->revs)\n>   \t\trelease_revisions(opts->revs);\n>   \tfree(opts->revs);\n> +\tstrvec_clear(&opts->trailer_args);\n>   \treplay_ctx_release(ctx);\n>   \tfree(opts->ctx);\n>   }\n> @@ -2517,6 +2518,18 @@ static int do_pick_commit(struct repository *r,\n>   \t\t\t_(\"dropping %s %s -- patch contents already upstream\\n\"),\n>   \t\t\toid_to_hex(&commit->object.oid), msg.subject);\n>   \t} /* else allow == 0 and there's nothing special to do */\n> +\n> +\tif (!res && opts->trailer_args.nr && !drop_commit) {\n> +\t\tconst char *trailer_file =\n> +\t\t\tmsg_file ? msg_file : git_path_merge_msg(r);\n> +\n> +\t\tif (amend_file_with_trailers(trailer_file,\n> +\t\t\t\t\t\t&opts->trailer_args)) {\n> +\t\t\tres = error(_(\"unable to add trailers to commit message\"));\n> +\t\t\tgoto leave;\n> +\t\t}\n> +\t}\n> +\n>   \tif (!opts->no_commit && !drop_commit) {\n>   \t\tif (author || command == TODO_REVERT || (flags & AMEND_MSG))\n>   \t\t\tres = do_commit(r, msg_file, author, reflog_action,\n> diff --git a/sequencer.h b/sequencer.h\n> index 719684c8a9..e21835c5a0 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -44,6 +44,7 @@ struct replay_opts {\n>   \tint record_origin;\n>   \tint no_commit;\n>   \tint signoff;\n> +\tstruct strvec trailer_args;\n>   \tint allow_ff;\n>   \tint allow_rerere_auto;\n>   \tint allow_empty;\n> @@ -82,8 +83,9 @@ struct replay_opts {\n>   \tstruct replay_ctx *ctx;\n>   };\n>   #define REPLAY_OPTS_INIT {\t\t\t\\\n> -\t.edit = -1,\t\t\t\t\\\n>   \t.action = -1,\t\t\t\t\\\n> +\t.edit = -1,\t\t\t\t\\\n> +\t.trailer_args = STRVEC_INIT, \\\n>   \t.xopts = STRVEC_INIT,\t\t\t\\\n>   \t.ctx = replay_ctx_new(),\t\t\\\n>   }\n> diff --git a/t/meson.build b/t/meson.build\n> index 401b24e50e..990e8ad4be 100644\n> --- a/t/meson.build\n> +++ b/t/meson.build\n> @@ -384,6 +384,7 @@ integration_tests = [\n>     't3436-rebase-more-options.sh',\n>     't3437-rebase-fixup-options.sh',\n>     't3438-rebase-broken-files.sh',\n> +  't3440-rebase-trailer.sh',\n>     't3500-cherry.sh',\n>     't3501-revert-cherry-pick.sh',\n>     't3502-cherry-pick-merge.sh',\n> diff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh\n> new file mode 100755\n> index 0000000000..b1c7b03330\n> --- /dev/null\n> +++ b/t/t3440-rebase-trailer.sh\n> @@ -0,0 +1,94 @@\n> +#!/bin/sh\n> +#\n> +\n> +test_description='git rebase --trailer integration tests\n> +We verify that --trailer works with the merge backend,\n> +and that it is rejected early when the apply backend is requested.'\n> +\n> +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n> +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n> +\n> +. ./test-lib.sh\n> +. \"$TEST_DIRECTORY\"/lib-rebase.sh # test_commit_message, helpers\n> +\n> +create_expect() {\n> +\tcat >\"$1\" <<-EOF\n> +\t$2\n> +\n> +\tReviewed-by: Dev <dev@example.com>\n> +\tEOF\n> +}\n> +\n> +test_expect_success 'setup repo with a small history' '\n> +\tgit commit --allow-empty -m \"Initial empty commit\" &&\n> +\ttest_commit first file a &&\n> +\ttest_commit second file &&\n> +\tgit checkout -b conflict-branch first &&\n> +\ttest_commit file-2 file-2 &&\n> +\ttest_commit conflict file &&\n> +\ttest_commit third file &&\n> +\tident=\"$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\" &&\n> +\tcreate_expect initial-signed  \"Initial empty commit\" &&\n> +\tcreate_expect first-signed    \"first\"                 &&\n> +\tcreate_expect second-signed   \"second\"                &&\n> +\tcreate_expect file2-signed    \"file-2\"                &&\n> +\tcreate_expect third-signed    \"third\"                 &&\n> +\tcreate_expect conflict-signed \"conflict\"\n> +'\n> +\n> +test_expect_success 'apply backend is rejected with --trailer' '\n> +\thead_before=$(git rev-parse HEAD) &&\n> +\ttest_expect_code 128 \\\n> +\tgit rebase --apply --trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n> +\t\t\t\tHEAD^ 2>err &&\n> +\ttest_grep \"requires the merge backend\" err &&\n> +\ttest_cmp_rev HEAD $head_before\n> +'\n> +\n> +test_expect_success 'reject empty --trailer argument' '\n> +\tgit reset --hard third &&\n> +\ttest_expect_code 128 git rebase -m --trailer \"\" HEAD^ 2>err &&\n> +\ttest_grep \"empty --trailer\" err\n> +'\n> +\n> +test_expect_success 'reject trailer with missing key before separator' '\n> +\tgit reset --hard third &&\n> +\ttest_expect_code 128 git rebase -m --trailer \": no-key\" HEAD^ 2>err &&\n> +\ttest_grep \"missing key before separator\" err\n> +'\n> +\n> +test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' '\n> +\tgit reset --hard third &&\n> +\tgit -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add rebase -m --trailer \"Bug: 123\" --trailer \"Bug: 456\" HEAD~1 &&\n> +\tgit cat-file commit HEAD | grep \"^Bug: 456\" &&\n> +\tgit cat-file commit HEAD | grep -v \"^Bug: 123\"\n> +'\n> +\n> +test_expect_success 'multiple Signed-off-by trailers all preserved' '\n> +\tgit reset --hard third &&\n> +\tgit rebase -m \\\n> +\t\t--trailer \"Signed-off-by: Dev A <a@ex.com>\" \\\n> +\t\t--trailer \"Signed-off-by: Dev B <b@ex.com>\" HEAD~1 &&\n> +\tgit cat-file commit HEAD | grep -c \"^Signed-off-by:\" >count &&\n> +\ttest \"$(cat count)\" = 2   # two new commits\n> +'\n> +\n> +test_expect_success 'rebase -m --trailer adds trailer after conflicts' '\n> +\tgit reset --hard third &&\n> +\ttest_must_fail git rebase -m \\\n> +\t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" \\\n> +\t\tsecond third &&\n> +\tgit checkout --theirs file &&\n> +\tgit add file &&\n> +\tgit rebase --continue &&\n> +\ttest_commit_message HEAD~2 file2-signed\n> +'\n> +\n> +test_expect_success 'rebase --root --trailer updates every commit' '\n> +\tgit checkout first &&\n> +\tgit rebase --root --keep-empty \\\n> +\t\t--trailer \"Reviewed-by: Dev <dev@example.com>\" &&\n> +\ttest_commit_message HEAD   first-signed &&\n> +\ttest_commit_message HEAD^  initial-signed\n> +'\n> +test_done\n\n"},{"id":"530187","messageId":"19a4eb739bb.71f7f93b817785.4090964595844940221@linux.beauty","threadId":"64367","inReplyTo":"7d12b046-365f-441c-af8e-8a39d61efbbd@gmail.com","subject":"Re: [PATCH v5 01/29] trailer: append trailers in-process and drop the fork to `interpret-trailers`","fromName":"Li Chen","fromEmail":"me@linux.beauty","sentAt":"2025-11-04T11:53:42Z","receivedAt":"2025-11-04T11:53:55Z","isPatch":true,"sender":{"key":"me@linux.beauty","avatar":"https://avatars.githubusercontent.com/u/37442588?v=4"},"body":"Hi Phillip,\n\n ---- On Thu, 23 Oct 2025 21:21:28 +0800  Phillip Wood <phillip.wood123@gmail.com> wrote --- \n > Hi Li\n > \n > On 22/10/2025 06:39, Li Chen wrote:\n > > From: Li Chen <chenl311@chinatelecom.cn>\n > > \n > > Route all trailer insertion through trailer_process() and make\n > > builtin/interpret-trailers just do file I/O before calling into it.\n > > amend_file_with_trailers() now shares the same code path.\n > > \n > > This removes the fork/exec and tempfile juggling, cutting overhead and\n > > simplifying error handling. No functional change. It also\n > > centralizes logic to prepare for follow-up rebase --trailer patch.\n > > \n > > Signed-off-by: Li Chen <chenl311@chinatelecom.cn>\n > \n > When I review v3 of this series I said\n > \n > >> As I said above reusing the existing code as you have done here is a\n > >> much better approach. However it would be much easier to review if\n > >> the code movement was separated from the refactoring. I'm also\n > >> struggling to see the benefit of a lot of the refactoring - I was\n > >> expecting the conversion to use an strubf would essentially look like\n > >> fwrite() being replaced with strbuf_add() and fprintf() being\n > >> replaced with strbuf_addf() etc. rather than reworking the logic.\n > \n > Unfortunately this version has the same code changes as v3 that make it\n > are virtually impossible to verify if the behavior is changed or not.\n > The diff below shows what I was hoping to see as the first step. It\n > refactors interpret_trailers() in place to factor out the code that\n > processes the trailers into a separate function. That makes it easy to\n > see that fwrite() is replaced with strbuf_add() etc. and so verify that\n > the behavior is unchanged. If you view the diff with \"--color-moved\"\n > you'll see that virtually all of the code in the new\n > interpret_trailers() function is moved from the old one which in turn\n > makes it easy to verify that there is no change in behavior. I would\n > expect the next patch in the series to move the new function for\n > processing trailers into trailer.c and the patch after that to refactor\n > amend_file_with_trailers() to add and use amend_strbuf_with_trailers()\n > and stop forking \"git interpret-trailers\". Then builtin/rebase.c can be\n > modified to add support for trailers using amend_strbuf_with_trailers().\n > \n > Thanks\n > \n > Phillip\n > \n > ---- 8< ----\n > diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c\n > index 41b0750e5af..4c90580ffff 100644\n > --- a/builtin/interpret-trailers.c\n > +++ b/builtin/interpret-trailers.c\n > @@ -136,32 +136,21 @@ static void read_input_file(struct strbuf *sb, const char *file)\n >       strbuf_complete_line(sb);\n >   }\n >   \n > -static void interpret_trailers(const struct process_trailer_options *opts,\n > -                   struct list_head *new_trailer_head,\n > -                   const char *file)\n > +static void process_trailers(const struct process_trailer_options *opts,\n > +                 struct list_head *new_trailer_head,\n > +                 struct strbuf *sb, struct strbuf *out)\n >   {\n >       LIST_HEAD(head);\n > -    struct strbuf sb = STRBUF_INIT;\n > -    struct strbuf trailer_block_sb = STRBUF_INIT;\n >       struct trailer_block *trailer_block;\n > -    FILE *outfile = stdout;\n > -\n > -    trailer_config_init();\n > -\n > -    read_input_file(&sb, file);\n > -\n > -    if (opts->in_place)\n > -        outfile = create_in_place_tempfile(file);\n > -\n > -    trailer_block = parse_trailers(opts, sb.buf, &head);\n > +\n > +    trailer_block = parse_trailers(opts, sb->buf, &head);\n >   \n >       /* Print the lines before the trailer block */\n >       if (!opts->only_trailers)\n > -        fwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);\n > +        strbuf_add(out, sb->buf, trailer_block_start(trailer_block));\n >   \n >       if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))\n > -        fprintf(outfile, \"\\n\");\n > -\n > +        strbuf_addch(out, '\\n');\n >   \n >       if (!opts->only_input) {\n >           LIST_HEAD(config_head);\n > @@ -173,22 +162,40 @@ static void interpret_trailers(const struct process_trailer_options *opts,\n >       }\n >   \n >       /* Print trailer block. */\n > -    format_trailers(opts, &head, &trailer_block_sb);\n > +    format_trailers(opts, &head, out);\n >       free_trailers(&head);\n > -    fwrite(trailer_block_sb.buf, 1, trailer_block_sb.len, outfile);\n > -    strbuf_release(&trailer_block_sb);\n >   \n >       /* Print the lines after the trailer block as is. */\n >       if (!opts->only_trailers)\n > -        fwrite(sb.buf + trailer_block_end(trailer_block), 1,\n > -               sb.len - trailer_block_end(trailer_block), outfile);\n > +        strbuf_add(out, sb->buf + trailer_block_end(trailer_block),\n > +               sb->len - trailer_block_end(trailer_block));\n >       trailer_block_release(trailer_block);\n > -\n > +}\n > +\n > +static void interpret_trailers(const struct process_trailer_options *opts,\n > +                   struct list_head *new_trailer_head,\n > +                   const char *file)\n > +{\n > +    struct strbuf sb = STRBUF_INIT;\n > +    struct strbuf out = STRBUF_INIT;\n > +    FILE *outfile = stdout;\n > +\n > +    trailer_config_init();\n > +\n > +    read_input_file(&sb, file);\n > +\n > +    if (opts->in_place)\n > +        outfile = create_in_place_tempfile(file);\n > +\n > +    process_trailers(opts, new_trailer_head, &sb, &out);\n > +\n > +    fwrite(out.buf, out.len, 1, outfile);\n >       if (opts->in_place)\n >           if (rename_tempfile(&trailers_tempfile, file))\n >               die_errno(_(\"could not rename temporary file to %s\"), file);\n >   \n >       strbuf_release(&sb);\n > +    strbuf_release(&out);\n >   }\n >   \n >   int cmd_interpret_trailers(int argc,\n > \n\nThank you for your suggestion and kindness. I will re-implement the first patch based on your change and\navoid the die in interpret_trailers (as suggested in v3) and replacing the fwrite with strbuf_write (also suggested in v3).\n\nAnd then add patches as you said above.\n\nRegards,\n\nLi​\n\n"}]}