git
/
trailer.c
1229 строк · 31.4 Кб
1#define USE_THE_REPOSITORY_VARIABLE2
3#include "git-compat-util.h"4#include "config.h"5#include "environment.h"6#include "gettext.h"7#include "string-list.h"8#include "run-command.h"9#include "commit.h"10#include "trailer.h"11#include "list.h"12/*
13* Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
14*/
15
16struct trailer_info {17/*18* True if there is a blank line before the location pointed to by
19* trailer_block_start.
20*/
21int blank_line_before_trailer;22
23/*24* Offsets to the trailer block start and end positions in the input
25* string. If no trailer block is found, these are both set to the
26* "true" end of the input (find_end_of_log_message()).
27*/
28size_t trailer_block_start, trailer_block_end;29
30/*31* Array of trailers found.
32*/
33char **trailers;34size_t trailer_nr;35};36
37struct conf_info {38char *name;39char *key;40char *command;41char *cmd;42enum trailer_where where;43enum trailer_if_exists if_exists;44enum trailer_if_missing if_missing;45};46
47static struct conf_info default_conf_info;48
49struct trailer_item {50struct list_head list;51/*52* If this is not a trailer line, the line is stored in value
53* (excluding the terminating newline) and token is NULL.
54*/
55char *token;56char *value;57};58
59struct arg_item {60struct list_head list;61char *token;62char *value;63struct conf_info conf;64};65
66static LIST_HEAD(conf_head);67
68static const char *separators = ":";69
70static int configured;71
72#define TRAILER_ARG_STRING "$ARG"73
74static const char *git_generated_prefixes[] = {75"Signed-off-by: ",76"(cherry picked from commit ",77NULL78};79
80/* Iterate over the elements of the list. */
81#define list_for_each_dir(pos, head, is_reverse) \82for (pos = is_reverse ? (head)->prev : (head)->next; \83pos != (head); \84pos = is_reverse ? pos->prev : pos->next)85
86static int after_or_end(enum trailer_where where)87{
88return (where == WHERE_AFTER) || (where == WHERE_END);89}
90
91/*
92* Return the length of the string not including any final
93* punctuation. E.g., the input "Signed-off-by:" would return
94* 13, stripping the trailing punctuation but retaining
95* internal punctuation.
96*/
97static size_t token_len_without_separator(const char *token, size_t len)98{
99while (len > 0 && !isalnum(token[len - 1]))100len--;101return len;102}
103
104static int same_token(struct trailer_item *a, struct arg_item *b)105{
106size_t a_len, b_len, min_len;107
108if (!a->token)109return 0;110
111a_len = token_len_without_separator(a->token, strlen(a->token));112b_len = token_len_without_separator(b->token, strlen(b->token));113min_len = (a_len > b_len) ? b_len : a_len;114
115return !strncasecmp(a->token, b->token, min_len);116}
117
118static int same_value(struct trailer_item *a, struct arg_item *b)119{
120return !strcasecmp(a->value, b->value);121}
122
123static int same_trailer(struct trailer_item *a, struct arg_item *b)124{
125return same_token(a, b) && same_value(a, b);126}
127
128static inline int is_blank_line(const char *str)129{
130const char *s = str;131while (*s && *s != '\n' && isspace(*s))132s++;133return !*s || *s == '\n';134}
135
136static inline void strbuf_replace(struct strbuf *sb, const char *a, const char *b)137{
138const char *ptr = strstr(sb->buf, a);139if (ptr)140strbuf_splice(sb, ptr - sb->buf, strlen(a), b, strlen(b));141}
142
143static void free_trailer_item(struct trailer_item *item)144{
145free(item->token);146free(item->value);147free(item);148}
149
150static void free_arg_item(struct arg_item *item)151{
152free(item->conf.name);153free(item->conf.key);154free(item->conf.command);155free(item->conf.cmd);156free(item->token);157free(item->value);158free(item);159}
160
161static char last_non_space_char(const char *s)162{
163int i;164for (i = strlen(s) - 1; i >= 0; i--)165if (!isspace(s[i]))166return s[i];167return '\0';168}
169
170static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok)171{
172struct trailer_item *new_item = xcalloc(1, sizeof(*new_item));173new_item->token = arg_tok->token;174new_item->value = arg_tok->value;175arg_tok->token = arg_tok->value = NULL;176free_arg_item(arg_tok);177return new_item;178}
179
180static void add_arg_to_input_list(struct trailer_item *on_tok,181struct arg_item *arg_tok)182{
183int aoe = after_or_end(arg_tok->conf.where);184struct trailer_item *to_add = trailer_from_arg(arg_tok);185if (aoe)186list_add(&to_add->list, &on_tok->list);187else188list_add_tail(&to_add->list, &on_tok->list);189}
190
191static int check_if_different(struct trailer_item *in_tok,192struct arg_item *arg_tok,193int check_all,194struct list_head *head)195{
196enum trailer_where where = arg_tok->conf.where;197struct list_head *next_head;198do {199if (same_trailer(in_tok, arg_tok))200return 0;201/*202* if we want to add a trailer after another one,
203* we have to check those before this one
204*/
205next_head = after_or_end(where) ? in_tok->list.prev206: in_tok->list.next;207if (next_head == head)208break;209in_tok = list_entry(next_head, struct trailer_item, list);210} while (check_all);211return 1;212}
213
214static char *apply_command(struct conf_info *conf, const char *arg)215{
216struct strbuf cmd = STRBUF_INIT;217struct strbuf buf = STRBUF_INIT;218struct child_process cp = CHILD_PROCESS_INIT;219char *result;220
221if (conf->cmd) {222strbuf_addstr(&cmd, conf->cmd);223strvec_push(&cp.args, cmd.buf);224if (arg)225strvec_push(&cp.args, arg);226} else if (conf->command) {227strbuf_addstr(&cmd, conf->command);228if (arg)229strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);230strvec_push(&cp.args, cmd.buf);231}232strvec_pushv(&cp.env, (const char **)local_repo_env);233cp.no_stdin = 1;234cp.use_shell = 1;235
236if (capture_command(&cp, &buf, 1024)) {237error(_("running trailer command '%s' failed"), cmd.buf);238strbuf_release(&buf);239result = xstrdup("");240} else {241strbuf_trim(&buf);242result = strbuf_detach(&buf, NULL);243}244
245strbuf_release(&cmd);246return result;247}
248
249static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)250{
251if (arg_tok->conf.command || arg_tok->conf.cmd) {252const char *arg;253if (arg_tok->value && arg_tok->value[0]) {254arg = arg_tok->value;255} else {256if (in_tok && in_tok->value)257arg = xstrdup(in_tok->value);258else259arg = xstrdup("");260}261arg_tok->value = apply_command(&arg_tok->conf, arg);262free((char *)arg);263}264}
265
266static void apply_arg_if_exists(struct trailer_item *in_tok,267struct arg_item *arg_tok,268struct trailer_item *on_tok,269struct list_head *head)270{
271switch (arg_tok->conf.if_exists) {272case EXISTS_DO_NOTHING:273free_arg_item(arg_tok);274break;275case EXISTS_REPLACE:276apply_item_command(in_tok, arg_tok);277add_arg_to_input_list(on_tok, arg_tok);278list_del(&in_tok->list);279free_trailer_item(in_tok);280break;281case EXISTS_ADD:282apply_item_command(in_tok, arg_tok);283add_arg_to_input_list(on_tok, arg_tok);284break;285case EXISTS_ADD_IF_DIFFERENT:286apply_item_command(in_tok, arg_tok);287if (check_if_different(in_tok, arg_tok, 1, head))288add_arg_to_input_list(on_tok, arg_tok);289else290free_arg_item(arg_tok);291break;292case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:293apply_item_command(in_tok, arg_tok);294if (check_if_different(on_tok, arg_tok, 0, head))295add_arg_to_input_list(on_tok, arg_tok);296else297free_arg_item(arg_tok);298break;299default:300BUG("trailer.c: unhandled value %d",301arg_tok->conf.if_exists);302}303}
304
305static void apply_arg_if_missing(struct list_head *head,306struct arg_item *arg_tok)307{
308enum trailer_where where;309struct trailer_item *to_add;310
311switch (arg_tok->conf.if_missing) {312case MISSING_DO_NOTHING:313free_arg_item(arg_tok);314break;315case MISSING_ADD:316where = arg_tok->conf.where;317apply_item_command(NULL, arg_tok);318to_add = trailer_from_arg(arg_tok);319if (after_or_end(where))320list_add_tail(&to_add->list, head);321else322list_add(&to_add->list, head);323break;324default:325BUG("trailer.c: unhandled value %d",326arg_tok->conf.if_missing);327}328}
329
330static int find_same_and_apply_arg(struct list_head *head,331struct arg_item *arg_tok)332{
333struct list_head *pos;334struct trailer_item *in_tok;335struct trailer_item *on_tok;336
337enum trailer_where where = arg_tok->conf.where;338int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);339int backwards = after_or_end(where);340struct trailer_item *start_tok;341
342if (list_empty(head))343return 0;344
345start_tok = list_entry(backwards ? head->prev : head->next,346struct trailer_item,347list);348
349list_for_each_dir(pos, head, backwards) {350in_tok = list_entry(pos, struct trailer_item, list);351if (!same_token(in_tok, arg_tok))352continue;353on_tok = middle ? in_tok : start_tok;354apply_arg_if_exists(in_tok, arg_tok, on_tok, head);355return 1;356}357return 0;358}
359
360void process_trailers_lists(struct list_head *head,361struct list_head *arg_head)362{
363struct list_head *pos, *p;364struct arg_item *arg_tok;365
366list_for_each_safe(pos, p, arg_head) {367int applied = 0;368arg_tok = list_entry(pos, struct arg_item, list);369
370list_del(pos);371
372applied = find_same_and_apply_arg(head, arg_tok);373
374if (!applied)375apply_arg_if_missing(head, arg_tok);376}377}
378
379int trailer_set_where(enum trailer_where *item, const char *value)380{
381if (!value)382*item = WHERE_DEFAULT;383else if (!strcasecmp("after", value))384*item = WHERE_AFTER;385else if (!strcasecmp("before", value))386*item = WHERE_BEFORE;387else if (!strcasecmp("end", value))388*item = WHERE_END;389else if (!strcasecmp("start", value))390*item = WHERE_START;391else392return -1;393return 0;394}
395
396int trailer_set_if_exists(enum trailer_if_exists *item, const char *value)397{
398if (!value)399*item = EXISTS_DEFAULT;400else if (!strcasecmp("addIfDifferent", value))401*item = EXISTS_ADD_IF_DIFFERENT;402else if (!strcasecmp("addIfDifferentNeighbor", value))403*item = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;404else if (!strcasecmp("add", value))405*item = EXISTS_ADD;406else if (!strcasecmp("replace", value))407*item = EXISTS_REPLACE;408else if (!strcasecmp("doNothing", value))409*item = EXISTS_DO_NOTHING;410else411return -1;412return 0;413}
414
415int trailer_set_if_missing(enum trailer_if_missing *item, const char *value)416{
417if (!value)418*item = MISSING_DEFAULT;419else if (!strcasecmp("doNothing", value))420*item = MISSING_DO_NOTHING;421else if (!strcasecmp("add", value))422*item = MISSING_ADD;423else424return -1;425return 0;426}
427
428static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)429{
430*dst = *src;431dst->name = xstrdup_or_null(src->name);432dst->key = xstrdup_or_null(src->key);433dst->command = xstrdup_or_null(src->command);434dst->cmd = xstrdup_or_null(src->cmd);435}
436
437static struct arg_item *get_conf_item(const char *name)438{
439struct list_head *pos;440struct arg_item *item;441
442/* Look up item with same name */443list_for_each(pos, &conf_head) {444item = list_entry(pos, struct arg_item, list);445if (!strcasecmp(item->conf.name, name))446return item;447}448
449/* Item does not already exists, create it */450CALLOC_ARRAY(item, 1);451duplicate_conf(&item->conf, &default_conf_info);452item->conf.name = xstrdup(name);453
454list_add_tail(&item->list, &conf_head);455
456return item;457}
458
459enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,460TRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };461
462static struct {463const char *name;464enum trailer_info_type type;465} trailer_config_items[] = {466{ "key", TRAILER_KEY },467{ "command", TRAILER_COMMAND },468{ "cmd", TRAILER_CMD },469{ "where", TRAILER_WHERE },470{ "ifexists", TRAILER_IF_EXISTS },471{ "ifmissing", TRAILER_IF_MISSING }472};473
474static int git_trailer_default_config(const char *conf_key, const char *value,475const struct config_context *ctx UNUSED,476void *cb UNUSED)477{
478const char *trailer_item, *variable_name;479
480if (!skip_prefix(conf_key, "trailer.", &trailer_item))481return 0;482
483variable_name = strrchr(trailer_item, '.');484if (!variable_name) {485if (!strcmp(trailer_item, "where")) {486if (trailer_set_where(&default_conf_info.where,487value) < 0)488warning(_("unknown value '%s' for key '%s'"),489value, conf_key);490} else if (!strcmp(trailer_item, "ifexists")) {491if (trailer_set_if_exists(&default_conf_info.if_exists,492value) < 0)493warning(_("unknown value '%s' for key '%s'"),494value, conf_key);495} else if (!strcmp(trailer_item, "ifmissing")) {496if (trailer_set_if_missing(&default_conf_info.if_missing,497value) < 0)498warning(_("unknown value '%s' for key '%s'"),499value, conf_key);500} else if (!strcmp(trailer_item, "separators")) {501if (!value)502return config_error_nonbool(conf_key);503separators = xstrdup(value);504}505}506return 0;507}
508
509static int git_trailer_config(const char *conf_key, const char *value,510const struct config_context *ctx UNUSED,511void *cb UNUSED)512{
513const char *trailer_item, *variable_name;514struct arg_item *item;515struct conf_info *conf;516char *name = NULL;517enum trailer_info_type type;518int i;519
520if (!skip_prefix(conf_key, "trailer.", &trailer_item))521return 0;522
523variable_name = strrchr(trailer_item, '.');524if (!variable_name)525return 0;526
527variable_name++;528for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {529if (strcmp(trailer_config_items[i].name, variable_name))530continue;531name = xstrndup(trailer_item, variable_name - trailer_item - 1);532type = trailer_config_items[i].type;533break;534}535
536if (!name)537return 0;538
539item = get_conf_item(name);540conf = &item->conf;541free(name);542
543switch (type) {544case TRAILER_KEY:545if (conf->key)546warning(_("more than one %s"), conf_key);547if (!value)548return config_error_nonbool(conf_key);549conf->key = xstrdup(value);550break;551case TRAILER_COMMAND:552if (conf->command)553warning(_("more than one %s"), conf_key);554if (!value)555return config_error_nonbool(conf_key);556conf->command = xstrdup(value);557break;558case TRAILER_CMD:559if (conf->cmd)560warning(_("more than one %s"), conf_key);561if (!value)562return config_error_nonbool(conf_key);563conf->cmd = xstrdup(value);564break;565case TRAILER_WHERE:566if (trailer_set_where(&conf->where, value))567warning(_("unknown value '%s' for key '%s'"), value, conf_key);568break;569case TRAILER_IF_EXISTS:570if (trailer_set_if_exists(&conf->if_exists, value))571warning(_("unknown value '%s' for key '%s'"), value, conf_key);572break;573case TRAILER_IF_MISSING:574if (trailer_set_if_missing(&conf->if_missing, value))575warning(_("unknown value '%s' for key '%s'"), value, conf_key);576break;577default:578BUG("trailer.c: unhandled type %d", type);579}580return 0;581}
582
583void trailer_config_init(void)584{
585if (configured)586return;587
588/* Default config must be setup first */589default_conf_info.where = WHERE_END;590default_conf_info.if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;591default_conf_info.if_missing = MISSING_ADD;592git_config(git_trailer_default_config, NULL);593git_config(git_trailer_config, NULL);594configured = 1;595}
596
597static const char *token_from_item(struct arg_item *item, char *tok)598{
599if (item->conf.key)600return item->conf.key;601if (tok)602return tok;603return item->conf.name;604}
605
606static int token_matches_item(const char *tok, struct arg_item *item, size_t tok_len)607{
608if (!strncasecmp(tok, item->conf.name, tok_len))609return 1;610return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;611}
612
613/*
614* If the given line is of the form
615* "<token><optional whitespace><separator>..." or "<separator>...", return the
616* location of the separator. Otherwise, return -1. The optional whitespace
617* is allowed there primarily to allow things like "Bug #43" where <token> is
618* "Bug" and <separator> is "#".
619*
620* The separator-starts-line case (in which this function returns 0) is
621* distinguished from the non-well-formed-line case (in which this function
622* returns -1) because some callers of this function need such a distinction.
623*/
624static ssize_t find_separator(const char *line, const char *separators)625{
626int whitespace_found = 0;627const char *c;628for (c = line; *c; c++) {629if (strchr(separators, *c))630return c - line;631if (!whitespace_found && (isalnum(*c) || *c == '-'))632continue;633if (c != line && (*c == ' ' || *c == '\t')) {634whitespace_found = 1;635continue;636}637break;638}639return -1;640}
641
642/*
643* Obtain the token, value, and conf from the given trailer.
644*
645* separator_pos must not be 0, since the token cannot be an empty string.
646*
647* If separator_pos is -1, interpret the whole trailer as a token.
648*/
649static void parse_trailer(struct strbuf *tok, struct strbuf *val,650const struct conf_info **conf, const char *trailer,651ssize_t separator_pos)652{
653struct arg_item *item;654size_t tok_len;655struct list_head *pos;656
657if (separator_pos != -1) {658strbuf_add(tok, trailer, separator_pos);659strbuf_trim(tok);660strbuf_addstr(val, trailer + separator_pos + 1);661strbuf_trim(val);662} else {663strbuf_addstr(tok, trailer);664strbuf_trim(tok);665}666
667/* Lookup if the token matches something in the config */668tok_len = token_len_without_separator(tok->buf, tok->len);669if (conf)670*conf = &default_conf_info;671list_for_each(pos, &conf_head) {672item = list_entry(pos, struct arg_item, list);673if (token_matches_item(tok->buf, item, tok_len)) {674char *tok_buf = strbuf_detach(tok, NULL);675if (conf)676*conf = &item->conf;677strbuf_addstr(tok, token_from_item(item, tok_buf));678free(tok_buf);679break;680}681}682}
683
684static struct trailer_item *add_trailer_item(struct list_head *head, char *tok,685char *val)686{
687struct trailer_item *new_item = xcalloc(1, sizeof(*new_item));688new_item->token = tok;689new_item->value = val;690list_add_tail(&new_item->list, head);691return new_item;692}
693
694static void add_arg_item(struct list_head *arg_head, char *tok, char *val,695const struct conf_info *conf,696const struct new_trailer_item *new_trailer_item)697{
698struct arg_item *new_item = xcalloc(1, sizeof(*new_item));699new_item->token = tok;700new_item->value = val;701duplicate_conf(&new_item->conf, conf);702if (new_trailer_item) {703if (new_trailer_item->where != WHERE_DEFAULT)704new_item->conf.where = new_trailer_item->where;705if (new_trailer_item->if_exists != EXISTS_DEFAULT)706new_item->conf.if_exists = new_trailer_item->if_exists;707if (new_trailer_item->if_missing != MISSING_DEFAULT)708new_item->conf.if_missing = new_trailer_item->if_missing;709}710list_add_tail(&new_item->list, arg_head);711}
712
713void parse_trailers_from_config(struct list_head *config_head)714{
715struct arg_item *item;716struct list_head *pos;717
718/* Add an arg item for each configured trailer with a command */719list_for_each(pos, &conf_head) {720item = list_entry(pos, struct arg_item, list);721if (item->conf.command)722add_arg_item(config_head,723xstrdup(token_from_item(item, NULL)),724xstrdup(""),725&item->conf, NULL);726}727}
728
729void parse_trailers_from_command_line_args(struct list_head *arg_head,730struct list_head *new_trailer_head)731{
732struct strbuf tok = STRBUF_INIT;733struct strbuf val = STRBUF_INIT;734const struct conf_info *conf;735struct list_head *pos;736
737/*738* In command-line arguments, '=' is accepted (in addition to the
739* separators that are defined).
740*/
741char *cl_separators = xstrfmt("=%s", separators);742
743/* Add an arg item for each trailer on the command line */744list_for_each(pos, new_trailer_head) {745struct new_trailer_item *tr =746list_entry(pos, struct new_trailer_item, list);747ssize_t separator_pos = find_separator(tr->text, cl_separators);748
749if (separator_pos == 0) {750struct strbuf sb = STRBUF_INIT;751strbuf_addstr(&sb, tr->text);752strbuf_trim(&sb);753error(_("empty trailer token in trailer '%.*s'"),754(int) sb.len, sb.buf);755strbuf_release(&sb);756} else {757parse_trailer(&tok, &val, &conf, tr->text,758separator_pos);759add_arg_item(arg_head,760strbuf_detach(&tok, NULL),761strbuf_detach(&val, NULL),762conf, tr);763}764}765
766free(cl_separators);767}
768
769static const char *next_line(const char *str)770{
771const char *nl = strchrnul(str, '\n');772return nl + !!*nl;773}
774
775/*
776* Return the position of the start of the last line. If len is 0, return -1.
777*/
778static ssize_t last_line(const char *buf, size_t len)779{
780ssize_t i;781if (len == 0)782return -1;783if (len == 1)784return 0;785/*786* Skip the last character (in addition to the null terminator),
787* because if the last character is a newline, it is considered as part
788* of the last line anyway.
789*/
790i = len - 2;791
792for (; i >= 0; i--) {793if (buf[i] == '\n')794return i + 1;795}796return 0;797}
798
799/*
800* Find the end of the log message as an offset from the start of the input
801* (where callers of this function are interested in looking for a trailers
802* block in the same input). We have to consider two categories of content that
803* can come at the end of the input which we want to ignore (because they don't
804* belong in the log message):
805*
806* (1) the "patch part" which begins with a "---" divider and has patch
807* information (like the output of git-format-patch), and
808*
809* (2) any trailing comment lines, blank lines like in the output of "git
810* commit -v", or stuff below the "cut" (scissor) line.
811*
812* As a formula, the situation looks like this:
813*
814* INPUT = LOG MESSAGE + IGNORED
815*
816* where IGNORED can be either of the two categories described above. It may be
817* that there is nothing to ignore. Now it may be the case that the LOG MESSAGE
818* contains a trailer block, but that's not the concern of this function.
819*/
820static size_t find_end_of_log_message(const char *input, int no_divider)821{
822size_t end;823const char *s;824
825/* Assume the naive end of the input is already what we want. */826end = strlen(input);827
828/* Optionally skip over any patch part ("---" line and below). */829if (!no_divider) {830for (s = input; *s; s = next_line(s)) {831const char *v;832
833if (skip_prefix(s, "---", &v) && isspace(*v)) {834end = s - input;835break;836}837}838}839
840/* Skip over other ignorable bits. */841return end - ignored_log_message_bytes(input, end);842}
843
844/*
845* Return the position of the first trailer line or len if there are no
846* trailers.
847*/
848static size_t find_trailer_block_start(const char *buf, size_t len)849{
850const char *s;851ssize_t end_of_title, l;852int only_spaces = 1;853int recognized_prefix = 0, trailer_lines = 0, non_trailer_lines = 0;854/*855* Number of possible continuation lines encountered. This will be
856* reset to 0 if we encounter a trailer (since those lines are to be
857* considered continuations of that trailer), and added to
858* non_trailer_lines if we encounter a non-trailer (since those lines
859* are to be considered non-trailers).
860*/
861int possible_continuation_lines = 0;862
863/* The first paragraph is the title and cannot be trailers */864for (s = buf; s < buf + len; s = next_line(s)) {865if (starts_with_mem(s, buf + len - s, comment_line_str))866continue;867if (is_blank_line(s))868break;869}870end_of_title = s - buf;871
872/*873* Get the start of the trailers by looking starting from the end for a
874* blank line before a set of non-blank lines that (i) are all
875* trailers, or (ii) contains at least one Git-generated trailer and
876* consists of at least 25% trailers.
877*/
878for (l = last_line(buf, len);879l >= end_of_title;880l = last_line(buf, l)) {881const char *bol = buf + l;882const char **p;883ssize_t separator_pos;884
885if (starts_with_mem(bol, buf + len - bol, comment_line_str)) {886non_trailer_lines += possible_continuation_lines;887possible_continuation_lines = 0;888continue;889}890if (is_blank_line(bol)) {891if (only_spaces)892continue;893non_trailer_lines += possible_continuation_lines;894if (recognized_prefix &&895trailer_lines * 3 >= non_trailer_lines)896return next_line(bol) - buf;897else if (trailer_lines && !non_trailer_lines)898return next_line(bol) - buf;899return len;900}901only_spaces = 0;902
903for (p = git_generated_prefixes; *p; p++) {904if (starts_with(bol, *p)) {905trailer_lines++;906possible_continuation_lines = 0;907recognized_prefix = 1;908goto continue_outer_loop;909}910}911
912separator_pos = find_separator(bol, separators);913if (separator_pos >= 1 && !isspace(bol[0])) {914struct list_head *pos;915
916trailer_lines++;917possible_continuation_lines = 0;918if (recognized_prefix)919continue;920list_for_each(pos, &conf_head) {921struct arg_item *item;922item = list_entry(pos, struct arg_item, list);923if (token_matches_item(bol, item,924separator_pos)) {925recognized_prefix = 1;926break;927}928}929} else if (isspace(bol[0]))930possible_continuation_lines++;931else {932non_trailer_lines++;933non_trailer_lines += possible_continuation_lines;934possible_continuation_lines = 0;935}936continue_outer_loop:937;938}939
940return len;941}
942
943static int ends_with_blank_line(const char *buf, size_t len)944{
945ssize_t ll = last_line(buf, len);946if (ll < 0)947return 0;948return is_blank_line(buf + ll);949}
950
951static void unfold_value(struct strbuf *val)952{
953struct strbuf out = STRBUF_INIT;954size_t i;955
956strbuf_grow(&out, val->len);957i = 0;958while (i < val->len) {959char c = val->buf[i++];960if (c == '\n') {961/* Collapse continuation down to a single space. */962while (i < val->len && isspace(val->buf[i]))963i++;964strbuf_addch(&out, ' ');965} else {966strbuf_addch(&out, c);967}968}969
970/* Empty lines may have left us with whitespace cruft at the edges */971strbuf_trim(&out);972
973/* output goes back to val as if we modified it in-place */974strbuf_swap(&out, val);975strbuf_release(&out);976}
977
978static struct trailer_info *trailer_info_new(void)979{
980struct trailer_info *info = xcalloc(1, sizeof(*info));981return info;982}
983
984static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,985const char *str)986{
987struct trailer_info *info = trailer_info_new();988size_t end_of_log_message = 0, trailer_block_start = 0;989struct strbuf **trailer_lines, **ptr;990char **trailer_strings = NULL;991size_t nr = 0, alloc = 0;992char **last = NULL;993
994trailer_config_init();995
996end_of_log_message = find_end_of_log_message(str, opts->no_divider);997trailer_block_start = find_trailer_block_start(str, end_of_log_message);998
999trailer_lines = strbuf_split_buf(str + trailer_block_start,1000end_of_log_message - trailer_block_start,1001'\n',10020);1003for (ptr = trailer_lines; *ptr; ptr++) {1004if (last && isspace((*ptr)->buf[0])) {1005struct strbuf sb = STRBUF_INIT;1006strbuf_attach(&sb, *last, strlen(*last), strlen(*last));1007strbuf_addbuf(&sb, *ptr);1008*last = strbuf_detach(&sb, NULL);1009continue;1010}1011ALLOC_GROW(trailer_strings, nr + 1, alloc);1012trailer_strings[nr] = strbuf_detach(*ptr, NULL);1013last = find_separator(trailer_strings[nr], separators) >= 11014? &trailer_strings[nr]1015: NULL;1016nr++;1017}1018strbuf_list_free(trailer_lines);1019
1020info->blank_line_before_trailer = ends_with_blank_line(str,1021trailer_block_start);1022info->trailer_block_start = trailer_block_start;1023info->trailer_block_end = end_of_log_message;1024info->trailers = trailer_strings;1025info->trailer_nr = nr;1026
1027return info;1028}
1029
1030/*
1031* Parse trailers in "str", populating the trailer info and "trailer_objects"
1032* linked list structure.
1033*/
1034struct trailer_info *parse_trailers(const struct process_trailer_options *opts,1035const char *str,1036struct list_head *trailer_objects)1037{
1038struct trailer_info *info;1039struct strbuf tok = STRBUF_INIT;1040struct strbuf val = STRBUF_INIT;1041size_t i;1042
1043info = trailer_info_get(opts, str);1044
1045for (i = 0; i < info->trailer_nr; i++) {1046int separator_pos;1047char *trailer = info->trailers[i];1048if (starts_with(trailer, comment_line_str))1049continue;1050separator_pos = find_separator(trailer, separators);1051if (separator_pos >= 1) {1052parse_trailer(&tok, &val, NULL, trailer,1053separator_pos);1054if (opts->unfold)1055unfold_value(&val);1056add_trailer_item(trailer_objects,1057strbuf_detach(&tok, NULL),1058strbuf_detach(&val, NULL));1059} else if (!opts->only_trailers) {1060strbuf_addstr(&val, trailer);1061strbuf_strip_suffix(&val, "\n");1062add_trailer_item(trailer_objects,1063NULL,1064strbuf_detach(&val, NULL));1065}1066}1067
1068return info;1069}
1070
1071void free_trailers(struct list_head *trailers)1072{
1073struct list_head *pos, *p;1074list_for_each_safe(pos, p, trailers) {1075list_del(pos);1076free_trailer_item(list_entry(pos, struct trailer_item, list));1077}1078}
1079
1080size_t trailer_block_start(struct trailer_info *info)1081{
1082return info->trailer_block_start;1083}
1084
1085size_t trailer_block_end(struct trailer_info *info)1086{
1087return info->trailer_block_end;1088}
1089
1090int blank_line_before_trailer_block(struct trailer_info *info)1091{
1092return info->blank_line_before_trailer;1093}
1094
1095void trailer_info_release(struct trailer_info *info)1096{
1097size_t i;1098for (i = 0; i < info->trailer_nr; i++)1099free(info->trailers[i]);1100free(info->trailers);1101free(info);1102}
1103
1104void format_trailers(const struct process_trailer_options *opts,1105struct list_head *trailers,1106struct strbuf *out)1107{
1108size_t origlen = out->len;1109struct list_head *pos;1110struct trailer_item *item;1111
1112list_for_each(pos, trailers) {1113item = list_entry(pos, struct trailer_item, list);1114if (item->token) {1115struct strbuf tok = STRBUF_INIT;1116struct strbuf val = STRBUF_INIT;1117strbuf_addstr(&tok, item->token);1118strbuf_addstr(&val, item->value);1119
1120/*1121* Skip key/value pairs where the value was empty. This
1122* can happen from trailers specified without a
1123* separator, like `--trailer "Reviewed-by"` (no
1124* corresponding value).
1125*/
1126if (opts->trim_empty && !strlen(item->value))1127continue;1128
1129if (!opts->filter || opts->filter(&tok, opts->filter_data)) {1130if (opts->separator && out->len != origlen)1131strbuf_addbuf(out, opts->separator);1132if (!opts->value_only)1133strbuf_addbuf(out, &tok);1134if (!opts->key_only && !opts->value_only) {1135if (opts->key_value_separator)1136strbuf_addbuf(out, opts->key_value_separator);1137else {1138char c = last_non_space_char(tok.buf);1139if (c && !strchr(separators, c))1140strbuf_addf(out, "%c ", separators[0]);1141}1142}1143if (!opts->key_only)1144strbuf_addbuf(out, &val);1145if (!opts->separator)1146strbuf_addch(out, '\n');1147}1148strbuf_release(&tok);1149strbuf_release(&val);1150
1151} else if (!opts->only_trailers) {1152if (opts->separator && out->len != origlen) {1153strbuf_addbuf(out, opts->separator);1154}1155strbuf_addstr(out, item->value);1156if (opts->separator)1157strbuf_rtrim(out);1158else1159strbuf_addch(out, '\n');1160}1161}1162}
1163
1164void format_trailers_from_commit(const struct process_trailer_options *opts,1165const char *msg,1166struct strbuf *out)1167{
1168LIST_HEAD(trailer_objects);1169struct trailer_info *info = parse_trailers(opts, msg, &trailer_objects);1170
1171/* If we want the whole block untouched, we can take the fast path. */1172if (!opts->only_trailers && !opts->unfold && !opts->filter &&1173!opts->separator && !opts->key_only && !opts->value_only &&1174!opts->key_value_separator) {1175strbuf_add(out, msg + info->trailer_block_start,1176info->trailer_block_end - info->trailer_block_start);1177} else1178format_trailers(opts, &trailer_objects, out);1179
1180free_trailers(&trailer_objects);1181trailer_info_release(info);1182}
1183
1184void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)1185{
1186struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;1187strbuf_init(&iter->key, 0);1188strbuf_init(&iter->val, 0);1189opts.no_divider = 1;1190iter->internal.info = trailer_info_get(&opts, msg);1191iter->internal.cur = 0;1192}
1193
1194int trailer_iterator_advance(struct trailer_iterator *iter)1195{
1196if (iter->internal.cur < iter->internal.info->trailer_nr) {1197char *line = iter->internal.info->trailers[iter->internal.cur++];1198int separator_pos = find_separator(line, separators);1199
1200iter->raw = line;1201strbuf_reset(&iter->key);1202strbuf_reset(&iter->val);1203parse_trailer(&iter->key, &iter->val, NULL,1204line, separator_pos);1205/* Always unfold values during iteration. */1206unfold_value(&iter->val);1207return 1;1208}1209return 0;1210}
1211
1212void trailer_iterator_release(struct trailer_iterator *iter)1213{
1214trailer_info_release(iter->internal.info);1215strbuf_release(&iter->val);1216strbuf_release(&iter->key);1217}
1218
1219int amend_file_with_trailers(const char *path, const struct strvec *trailer_args)1220{
1221struct child_process run_trailer = CHILD_PROCESS_INIT;1222
1223run_trailer.git_cmd = 1;1224strvec_pushl(&run_trailer.args, "interpret-trailers",1225"--in-place", "--no-divider",1226path, NULL);1227strvec_pushv(&run_trailer.args, trailer_args->v);1228return run_command(&run_trailer);1229}
1230