Skip to content

Commit 5a45004

Browse files
committed
Merge branch 'lc/rebase-trailer' into seen
* lc/rebase-trailer: rebase: support --trailer trailer: append trailers in-process and drop the fork to `interpret-trailers`
2 parents 93f57d4 + ba50274 commit 5a45004

File tree

9 files changed

+375
-102
lines changed

9 files changed

+375
-102
lines changed

Documentation/git-rebase.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,13 @@ See also INCOMPATIBLE OPTIONS below.
521521
that if `--interactive` is given then only commits marked to be
522522
picked, edited or reworded will have the trailer added.
523523
+
524+
--trailer <trailer>::
525+
Append the given trailer line(s) to every rebased commit
526+
message, processed via linkgit:git-interpret-trailers[1].
527+
When this option is present *rebase automatically enables*
528+
`--force-rebase` so that fast‑forwarded commits are also
529+
rewritten.
530+
524531
See also INCOMPATIBLE OPTIONS below.
525532

526533
-i::

builtin/interpret-trailers.c

Lines changed: 27 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include "gettext.h"
1111
#include "parse-options.h"
1212
#include "string-list.h"
13-
#include "tempfile.h"
1413
#include "trailer.h"
1514
#include "config.h"
1615

@@ -85,6 +84,7 @@ static int parse_opt_parse(const struct option *opt, const char *arg,
8584
int unset)
8685
{
8786
struct process_trailer_options *v = opt->value;
87+
8888
v->only_trailers = 1;
8989
v->only_input = 1;
9090
v->unfold = 1;
@@ -93,37 +93,6 @@ static int parse_opt_parse(const struct option *opt, const char *arg,
9393
return 0;
9494
}
9595

96-
static struct tempfile *trailers_tempfile;
97-
98-
static FILE *create_in_place_tempfile(const char *file)
99-
{
100-
struct stat st;
101-
struct strbuf filename_template = STRBUF_INIT;
102-
const char *tail;
103-
FILE *outfile;
104-
105-
if (stat(file, &st))
106-
die_errno(_("could not stat %s"), file);
107-
if (!S_ISREG(st.st_mode))
108-
die(_("file %s is not a regular file"), file);
109-
if (!(st.st_mode & S_IWUSR))
110-
die(_("file %s is not writable by user"), file);
111-
112-
/* Create temporary file in the same directory as the original */
113-
tail = strrchr(file, '/');
114-
if (tail)
115-
strbuf_add(&filename_template, file, tail - file + 1);
116-
strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX");
117-
118-
trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);
119-
strbuf_release(&filename_template);
120-
outfile = fdopen_tempfile(trailers_tempfile, "w");
121-
if (!outfile)
122-
die_errno(_("could not open temporary file"));
123-
124-
return outfile;
125-
}
126-
12796
static void read_input_file(struct strbuf *sb, const char *file)
12897
{
12998
if (file) {
@@ -136,61 +105,6 @@ static void read_input_file(struct strbuf *sb, const char *file)
136105
strbuf_complete_line(sb);
137106
}
138107

139-
static void interpret_trailers(const struct process_trailer_options *opts,
140-
struct list_head *new_trailer_head,
141-
const char *file)
142-
{
143-
LIST_HEAD(head);
144-
struct strbuf sb = STRBUF_INIT;
145-
struct strbuf trailer_block_sb = STRBUF_INIT;
146-
struct trailer_block *trailer_block;
147-
FILE *outfile = stdout;
148-
149-
trailer_config_init();
150-
151-
read_input_file(&sb, file);
152-
153-
if (opts->in_place)
154-
outfile = create_in_place_tempfile(file);
155-
156-
trailer_block = parse_trailers(opts, sb.buf, &head);
157-
158-
/* Print the lines before the trailer block */
159-
if (!opts->only_trailers)
160-
fwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);
161-
162-
if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))
163-
fprintf(outfile, "\n");
164-
165-
166-
if (!opts->only_input) {
167-
LIST_HEAD(config_head);
168-
LIST_HEAD(arg_head);
169-
parse_trailers_from_config(&config_head);
170-
parse_trailers_from_command_line_args(&arg_head, new_trailer_head);
171-
list_splice(&config_head, &arg_head);
172-
process_trailers_lists(&head, &arg_head);
173-
}
174-
175-
/* Print trailer block. */
176-
format_trailers(opts, &head, &trailer_block_sb);
177-
free_trailers(&head);
178-
fwrite(trailer_block_sb.buf, 1, trailer_block_sb.len, outfile);
179-
strbuf_release(&trailer_block_sb);
180-
181-
/* Print the lines after the trailer block as is. */
182-
if (!opts->only_trailers)
183-
fwrite(sb.buf + trailer_block_end(trailer_block), 1,
184-
sb.len - trailer_block_end(trailer_block), outfile);
185-
trailer_block_release(trailer_block);
186-
187-
if (opts->in_place)
188-
if (rename_tempfile(&trailers_tempfile, file))
189-
die_errno(_("could not rename temporary file to %s"), file);
190-
191-
strbuf_release(&sb);
192-
}
193-
194108
int cmd_interpret_trailers(int argc,
195109
const char **argv,
196110
const char *prefix,
@@ -232,14 +146,37 @@ int cmd_interpret_trailers(int argc,
232146
git_interpret_trailers_usage,
233147
options);
234148

149+
trailer_config_init();
150+
235151
if (argc) {
236152
int i;
237-
for (i = 0; i < argc; i++)
238-
interpret_trailers(&opts, &trailers, argv[i]);
153+
for (i = 0; i < argc; i++) {
154+
struct strbuf in_buf = STRBUF_INIT;
155+
struct strbuf out_buf = STRBUF_INIT;
156+
157+
read_input_file(&in_buf, argv[i]);
158+
if (trailer_process(&opts, in_buf.buf, &trailers, &out_buf) < 0)
159+
die(_("failed to process trailers for %s"), argv[i]);
160+
if (opts.in_place)
161+
write_file_buf(argv[i], out_buf.buf, out_buf.len);
162+
else
163+
fwrite(out_buf.buf, 1, out_buf.len, stdout);
164+
strbuf_release(&in_buf);
165+
strbuf_release(&out_buf);
166+
}
239167
} else {
168+
struct strbuf in_buf = STRBUF_INIT;
169+
struct strbuf out_buf = STRBUF_INIT;
170+
240171
if (opts.in_place)
241172
die(_("no input file given for in-place editing"));
242-
interpret_trailers(&opts, &trailers, NULL);
173+
174+
read_input_file(&in_buf, NULL);
175+
if (trailer_process(&opts, in_buf.buf, &trailers, &out_buf) < 0)
176+
die(_("failed to process trailers"));
177+
fwrite(out_buf.buf, 1, out_buf.len, stdout);
178+
strbuf_release(&in_buf);
179+
strbuf_release(&out_buf);
243180
}
244181

245182
new_trailers_clear(&trailers);

builtin/rebase.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#include "reset.h"
3737
#include "trace2.h"
3838
#include "hook.h"
39+
#include "trailer.h"
40+
#include "parse-options.h"
3941

4042
static char const * const builtin_rebase_usage[] = {
4143
N_("git rebase [-i] [options] [--exec <cmd>] "
@@ -113,6 +115,7 @@ struct rebase_options {
113115
enum action action;
114116
char *reflog_action;
115117
int signoff;
118+
struct strvec trailer_args;
116119
int allow_rerere_autoupdate;
117120
int keep_empty;
118121
int autosquash;
@@ -143,6 +146,7 @@ struct rebase_options {
143146
.flags = REBASE_NO_QUIET, \
144147
.git_am_opts = STRVEC_INIT, \
145148
.exec = STRING_LIST_INIT_NODUP, \
149+
.trailer_args = STRVEC_INIT, \
146150
.git_format_patch_opt = STRBUF_INIT, \
147151
.fork_point = -1, \
148152
.reapply_cherry_picks = -1, \
@@ -166,6 +170,7 @@ static void rebase_options_release(struct rebase_options *opts)
166170
free(opts->strategy);
167171
string_list_clear(&opts->strategy_opts, 0);
168172
strbuf_release(&opts->git_format_patch_opt);
173+
strvec_clear(&opts->trailer_args);
169174
}
170175

171176
static struct replay_opts get_replay_opts(const struct rebase_options *opts)
@@ -177,6 +182,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
177182
sequencer_init_config(&replay);
178183

179184
replay.signoff = opts->signoff;
185+
186+
for (size_t i = 0; i < opts->trailer_args.nr; i++)
187+
strvec_push(&replay.trailer_args, opts->trailer_args.v[i]);
188+
180189
replay.allow_ff = !(opts->flags & REBASE_FORCE);
181190
if (opts->allow_rerere_autoupdate)
182191
replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
@@ -435,6 +444,8 @@ static int read_basic_state(struct rebase_options *opts)
435444
struct strbuf head_name = STRBUF_INIT;
436445
struct strbuf buf = STRBUF_INIT;
437446
struct object_id oid;
447+
const char trailer_state_name[] = "trailer";
448+
const char *path = state_dir_path(trailer_state_name, opts);
438449

439450
if (!read_oneliner(&head_name, state_dir_path("head-name", opts),
440451
READ_ONELINER_WARN_MISSING) ||
@@ -503,11 +514,31 @@ static int read_basic_state(struct rebase_options *opts)
503514

504515
strbuf_release(&buf);
505516

517+
if (strbuf_read_file(&buf, path, 0) >= 0) {
518+
const char *p = buf.buf, *end = buf.buf + buf.len;
519+
520+
while (p < end) {
521+
char *nl = memchr(p, '\n', end - p);
522+
if (!nl)
523+
die("nl shouldn't be NULL");
524+
*nl = '\0';
525+
526+
if (*p)
527+
strvec_push(&opts->trailer_args, p);
528+
529+
p = nl + 1;
530+
}
531+
strbuf_release(&buf);
532+
}
533+
strbuf_release(&buf);
534+
506535
return 0;
507536
}
508537

509538
static int rebase_write_basic_state(struct rebase_options *opts)
510539
{
540+
const char trailer_state_name[] = "trailer";
541+
511542
write_file(state_dir_path("head-name", opts), "%s",
512543
opts->head_name ? opts->head_name : "detached HEAD");
513544
write_file(state_dir_path("onto", opts), "%s",
@@ -529,6 +560,22 @@ static int rebase_write_basic_state(struct rebase_options *opts)
529560
if (opts->signoff)
530561
write_file(state_dir_path("signoff", opts), "--signoff");
531562

563+
/*
564+
* save opts->trailer_args into state_dir/trailer
565+
*/
566+
if (opts->trailer_args.nr) {
567+
struct strbuf buf = STRBUF_INIT;
568+
size_t i;
569+
570+
for (i = 0; i < opts->trailer_args.nr; i++) {
571+
strbuf_addstr(&buf, opts->trailer_args.v[i]);
572+
strbuf_addch(&buf, '\n');
573+
}
574+
write_file(state_dir_path(trailer_state_name, opts),
575+
"%s", buf.buf);
576+
strbuf_release(&buf);
577+
}
578+
532579
return 0;
533580
}
534581

@@ -1085,6 +1132,37 @@ static int check_exec_cmd(const char *cmd)
10851132
return 0;
10861133
}
10871134

1135+
static int validate_trailer_args_after_config(const struct strvec *cli_args,
1136+
struct strbuf *err)
1137+
{
1138+
size_t i;
1139+
1140+
for (i = 0; i < cli_args->nr; i++) {
1141+
const char *raw = cli_args->v[i];
1142+
const char *txt; // Key[:=]Val
1143+
const char *sep;
1144+
1145+
if (!skip_prefix(raw, "--trailer=", &txt))
1146+
txt = raw;
1147+
1148+
if (!*txt) {
1149+
strbuf_addstr(err, _("empty --trailer argument"));
1150+
return -1;
1151+
}
1152+
1153+
sep = strpbrk(txt, ":=");
1154+
1155+
/* there must be key bfore seperator */
1156+
if (sep && sep == txt) {
1157+
strbuf_addf(err,
1158+
_("invalid trailer '%s': missing key before separator"),
1159+
txt);
1160+
return -1;
1161+
}
1162+
}
1163+
return 0;
1164+
}
1165+
10881166
int cmd_rebase(int argc,
10891167
const char **argv,
10901168
const char *prefix,
@@ -1133,6 +1211,7 @@ int cmd_rebase(int argc,
11331211
.flags = PARSE_OPT_NOARG,
11341212
.defval = REBASE_DIFFSTAT,
11351213
},
1214+
OPT_STRVEC(0, "trailer", &options.trailer_args, N_("trailer"), N_("add custom trailer(s)")),
11361215
OPT_BOOL(0, "signoff", &options.signoff,
11371216
N_("add a Signed-off-by trailer to each commit")),
11381217
OPT_BOOL(0, "committer-date-is-author-date",
@@ -1286,6 +1365,17 @@ int cmd_rebase(int argc,
12861365
builtin_rebase_options,
12871366
builtin_rebase_usage, 0);
12881367

1368+
/* if add --trailer,force rebase */
1369+
if (options.trailer_args.nr) {
1370+
struct strbuf err = STRBUF_INIT;
1371+
1372+
if (validate_trailer_args_after_config(&options.trailer_args, &err))
1373+
die("%s", err.buf);
1374+
1375+
options.flags |= REBASE_FORCE;
1376+
strbuf_release(&err);
1377+
}
1378+
12891379
if (preserve_merges_selected)
12901380
die(_("--preserve-merges was replaced by --rebase-merges\n"
12911381
"Note: Your `pull.rebase` configuration may also be set to 'preserve',\n"
@@ -1543,6 +1633,14 @@ int cmd_rebase(int argc,
15431633
if (options.root && !options.onto_name)
15441634
imply_merge(&options, "--root without --onto");
15451635

1636+
/*
1637+
* The apply‑based backend (git am) cannot append trailers because
1638+
* it lacks a message‑filter facility. Reject early, before any
1639+
* state (index, HEAD, etc.) is modified.
1640+
*/
1641+
if (options.trailer_args.nr)
1642+
imply_merge(&options, "--trailer");
1643+
15461644
if (isatty(2) && options.flags & REBASE_NO_QUIET)
15471645
strbuf_addstr(&options.git_format_patch_opt, " --progress");
15481646

sequencer.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ void replay_opts_release(struct replay_opts *opts)
422422
free(opts->revs);
423423
replay_ctx_release(ctx);
424424
free(opts->ctx);
425+
strvec_clear(&opts->trailer_args);
425426
}
426427

427428
int sequencer_remove_state(struct replay_opts *opts)
@@ -2529,6 +2530,18 @@ static int do_pick_commit(struct repository *r,
25292530
_("dropping %s %s -- patch contents already upstream\n"),
25302531
oid_to_hex(&commit->object.oid), msg.subject);
25312532
} /* else allow == 0 and there's nothing special to do */
2533+
2534+
if (!res && opts->trailer_args.nr && !drop_commit) {
2535+
const char *trailer_file =
2536+
msg_file ? msg_file : git_path_merge_msg(r);
2537+
2538+
if (amend_file_with_trailers(trailer_file,
2539+
&opts->trailer_args)) {
2540+
res = error(_("unable to add trailers to commit message"));
2541+
goto leave;
2542+
}
2543+
}
2544+
25322545
if (!opts->no_commit && !drop_commit) {
25332546
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
25342547
res = do_commit(r, msg_file, author, reflog_action,

0 commit comments

Comments
 (0)