trailer.con commit trailer: parse trailers from file or stdin (2013d85)
   1#include "cache.h"
   2#include "string-list.h"
   3/*
   4 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
   5 */
   6
   7enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START };
   8enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT,
   9                        EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING };
  10enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
  11
  12struct conf_info {
  13        char *name;
  14        char *key;
  15        char *command;
  16        enum action_where where;
  17        enum action_if_exists if_exists;
  18        enum action_if_missing if_missing;
  19};
  20
  21static struct conf_info default_conf_info;
  22
  23struct trailer_item {
  24        struct trailer_item *previous;
  25        struct trailer_item *next;
  26        const char *token;
  27        const char *value;
  28        struct conf_info conf;
  29};
  30
  31static struct trailer_item *first_conf_item;
  32
  33static char *separators = ":";
  34
  35static int after_or_end(enum action_where where)
  36{
  37        return (where == WHERE_AFTER) || (where == WHERE_END);
  38}
  39
  40/*
  41 * Return the length of the string not including any final
  42 * punctuation. E.g., the input "Signed-off-by:" would return
  43 * 13, stripping the trailing punctuation but retaining
  44 * internal punctuation.
  45 */
  46static size_t token_len_without_separator(const char *token, size_t len)
  47{
  48        while (len > 0 && !isalnum(token[len - 1]))
  49                len--;
  50        return len;
  51}
  52
  53static int same_token(struct trailer_item *a, struct trailer_item *b)
  54{
  55        size_t a_len = token_len_without_separator(a->token, strlen(a->token));
  56        size_t b_len = token_len_without_separator(b->token, strlen(b->token));
  57        size_t min_len = (a_len > b_len) ? b_len : a_len;
  58
  59        return !strncasecmp(a->token, b->token, min_len);
  60}
  61
  62static int same_value(struct trailer_item *a, struct trailer_item *b)
  63{
  64        return !strcasecmp(a->value, b->value);
  65}
  66
  67static int same_trailer(struct trailer_item *a, struct trailer_item *b)
  68{
  69        return same_token(a, b) && same_value(a, b);
  70}
  71
  72static inline int contains_only_spaces(const char *str)
  73{
  74        const char *s = str;
  75        while (*s && isspace(*s))
  76                s++;
  77        return !*s;
  78}
  79
  80static void free_trailer_item(struct trailer_item *item)
  81{
  82        free(item->conf.name);
  83        free(item->conf.key);
  84        free(item->conf.command);
  85        free((char *)item->token);
  86        free((char *)item->value);
  87        free(item);
  88}
  89
  90static void update_last(struct trailer_item **last)
  91{
  92        if (*last)
  93                while ((*last)->next != NULL)
  94                        *last = (*last)->next;
  95}
  96
  97static void update_first(struct trailer_item **first)
  98{
  99        if (*first)
 100                while ((*first)->previous != NULL)
 101                        *first = (*first)->previous;
 102}
 103
 104static void add_arg_to_input_list(struct trailer_item *on_tok,
 105                                  struct trailer_item *arg_tok,
 106                                  struct trailer_item **first,
 107                                  struct trailer_item **last)
 108{
 109        if (after_or_end(arg_tok->conf.where)) {
 110                arg_tok->next = on_tok->next;
 111                on_tok->next = arg_tok;
 112                arg_tok->previous = on_tok;
 113                if (arg_tok->next)
 114                        arg_tok->next->previous = arg_tok;
 115                update_last(last);
 116        } else {
 117                arg_tok->previous = on_tok->previous;
 118                on_tok->previous = arg_tok;
 119                arg_tok->next = on_tok;
 120                if (arg_tok->previous)
 121                        arg_tok->previous->next = arg_tok;
 122                update_first(first);
 123        }
 124}
 125
 126static int check_if_different(struct trailer_item *in_tok,
 127                              struct trailer_item *arg_tok,
 128                              int check_all)
 129{
 130        enum action_where where = arg_tok->conf.where;
 131        do {
 132                if (!in_tok)
 133                        return 1;
 134                if (same_trailer(in_tok, arg_tok))
 135                        return 0;
 136                /*
 137                 * if we want to add a trailer after another one,
 138                 * we have to check those before this one
 139                 */
 140                in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
 141        } while (check_all);
 142        return 1;
 143}
 144
 145static void remove_from_list(struct trailer_item *item,
 146                             struct trailer_item **first,
 147                             struct trailer_item **last)
 148{
 149        struct trailer_item *next = item->next;
 150        struct trailer_item *previous = item->previous;
 151
 152        if (next) {
 153                item->next->previous = previous;
 154                item->next = NULL;
 155        } else if (last)
 156                *last = previous;
 157
 158        if (previous) {
 159                item->previous->next = next;
 160                item->previous = NULL;
 161        } else if (first)
 162                *first = next;
 163}
 164
 165static struct trailer_item *remove_first(struct trailer_item **first)
 166{
 167        struct trailer_item *item = *first;
 168        *first = item->next;
 169        if (item->next) {
 170                item->next->previous = NULL;
 171                item->next = NULL;
 172        }
 173        return item;
 174}
 175
 176static void apply_arg_if_exists(struct trailer_item *in_tok,
 177                                struct trailer_item *arg_tok,
 178                                struct trailer_item *on_tok,
 179                                struct trailer_item **in_tok_first,
 180                                struct trailer_item **in_tok_last)
 181{
 182        switch (arg_tok->conf.if_exists) {
 183        case EXISTS_DO_NOTHING:
 184                free_trailer_item(arg_tok);
 185                break;
 186        case EXISTS_REPLACE:
 187                add_arg_to_input_list(on_tok, arg_tok,
 188                                      in_tok_first, in_tok_last);
 189                remove_from_list(in_tok, in_tok_first, in_tok_last);
 190                free_trailer_item(in_tok);
 191                break;
 192        case EXISTS_ADD:
 193                add_arg_to_input_list(on_tok, arg_tok,
 194                                      in_tok_first, in_tok_last);
 195                break;
 196        case EXISTS_ADD_IF_DIFFERENT:
 197                if (check_if_different(in_tok, arg_tok, 1))
 198                        add_arg_to_input_list(on_tok, arg_tok,
 199                                              in_tok_first, in_tok_last);
 200                else
 201                        free_trailer_item(arg_tok);
 202                break;
 203        case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
 204                if (check_if_different(on_tok, arg_tok, 0))
 205                        add_arg_to_input_list(on_tok, arg_tok,
 206                                              in_tok_first, in_tok_last);
 207                else
 208                        free_trailer_item(arg_tok);
 209                break;
 210        }
 211}
 212
 213static void apply_arg_if_missing(struct trailer_item **in_tok_first,
 214                                 struct trailer_item **in_tok_last,
 215                                 struct trailer_item *arg_tok)
 216{
 217        struct trailer_item **in_tok;
 218        enum action_where where;
 219
 220        switch (arg_tok->conf.if_missing) {
 221        case MISSING_DO_NOTHING:
 222                free_trailer_item(arg_tok);
 223                break;
 224        case MISSING_ADD:
 225                where = arg_tok->conf.where;
 226                in_tok = after_or_end(where) ? in_tok_last : in_tok_first;
 227                if (*in_tok) {
 228                        add_arg_to_input_list(*in_tok, arg_tok,
 229                                              in_tok_first, in_tok_last);
 230                } else {
 231                        *in_tok_first = arg_tok;
 232                        *in_tok_last = arg_tok;
 233                }
 234                break;
 235        }
 236}
 237
 238static int find_same_and_apply_arg(struct trailer_item **in_tok_first,
 239                                   struct trailer_item **in_tok_last,
 240                                   struct trailer_item *arg_tok)
 241{
 242        struct trailer_item *in_tok;
 243        struct trailer_item *on_tok;
 244        struct trailer_item *following_tok;
 245
 246        enum action_where where = arg_tok->conf.where;
 247        int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);
 248        int backwards = after_or_end(where);
 249        struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;
 250
 251        for (in_tok = start_tok; in_tok; in_tok = following_tok) {
 252                following_tok = backwards ? in_tok->previous : in_tok->next;
 253                if (!same_token(in_tok, arg_tok))
 254                        continue;
 255                on_tok = middle ? in_tok : start_tok;
 256                apply_arg_if_exists(in_tok, arg_tok, on_tok,
 257                                    in_tok_first, in_tok_last);
 258                return 1;
 259        }
 260        return 0;
 261}
 262
 263static void process_trailers_lists(struct trailer_item **in_tok_first,
 264                                   struct trailer_item **in_tok_last,
 265                                   struct trailer_item **arg_tok_first)
 266{
 267        struct trailer_item *arg_tok;
 268        struct trailer_item *next_arg;
 269
 270        if (!*arg_tok_first)
 271                return;
 272
 273        for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
 274                int applied = 0;
 275
 276                next_arg = arg_tok->next;
 277                remove_from_list(arg_tok, arg_tok_first, NULL);
 278
 279                applied = find_same_and_apply_arg(in_tok_first,
 280                                                  in_tok_last,
 281                                                  arg_tok);
 282
 283                if (!applied)
 284                        apply_arg_if_missing(in_tok_first,
 285                                             in_tok_last,
 286                                             arg_tok);
 287        }
 288}
 289
 290static int set_where(struct conf_info *item, const char *value)
 291{
 292        if (!strcasecmp("after", value))
 293                item->where = WHERE_AFTER;
 294        else if (!strcasecmp("before", value))
 295                item->where = WHERE_BEFORE;
 296        else if (!strcasecmp("end", value))
 297                item->where = WHERE_END;
 298        else if (!strcasecmp("start", value))
 299                item->where = WHERE_START;
 300        else
 301                return -1;
 302        return 0;
 303}
 304
 305static int set_if_exists(struct conf_info *item, const char *value)
 306{
 307        if (!strcasecmp("addIfDifferent", value))
 308                item->if_exists = EXISTS_ADD_IF_DIFFERENT;
 309        else if (!strcasecmp("addIfDifferentNeighbor", value))
 310                item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
 311        else if (!strcasecmp("add", value))
 312                item->if_exists = EXISTS_ADD;
 313        else if (!strcasecmp("replace", value))
 314                item->if_exists = EXISTS_REPLACE;
 315        else if (!strcasecmp("doNothing", value))
 316                item->if_exists = EXISTS_DO_NOTHING;
 317        else
 318                return -1;
 319        return 0;
 320}
 321
 322static int set_if_missing(struct conf_info *item, const char *value)
 323{
 324        if (!strcasecmp("doNothing", value))
 325                item->if_missing = MISSING_DO_NOTHING;
 326        else if (!strcasecmp("add", value))
 327                item->if_missing = MISSING_ADD;
 328        else
 329                return -1;
 330        return 0;
 331}
 332
 333static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
 334{
 335        *dst = *src;
 336        if (src->name)
 337                dst->name = xstrdup(src->name);
 338        if (src->key)
 339                dst->key = xstrdup(src->key);
 340        if (src->command)
 341                dst->command = xstrdup(src->command);
 342}
 343
 344static struct trailer_item *get_conf_item(const char *name)
 345{
 346        struct trailer_item *item;
 347        struct trailer_item *previous;
 348
 349        /* Look up item with same name */
 350        for (previous = NULL, item = first_conf_item;
 351             item;
 352             previous = item, item = item->next) {
 353                if (!strcasecmp(item->conf.name, name))
 354                        return item;
 355        }
 356
 357        /* Item does not already exists, create it */
 358        item = xcalloc(sizeof(struct trailer_item), 1);
 359        duplicate_conf(&item->conf, &default_conf_info);
 360        item->conf.name = xstrdup(name);
 361
 362        if (!previous)
 363                first_conf_item = item;
 364        else {
 365                previous->next = item;
 366                item->previous = previous;
 367        }
 368
 369        return item;
 370}
 371
 372enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
 373                         TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
 374
 375static struct {
 376        const char *name;
 377        enum trailer_info_type type;
 378} trailer_config_items[] = {
 379        { "key", TRAILER_KEY },
 380        { "command", TRAILER_COMMAND },
 381        { "where", TRAILER_WHERE },
 382        { "ifexists", TRAILER_IF_EXISTS },
 383        { "ifmissing", TRAILER_IF_MISSING }
 384};
 385
 386static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
 387{
 388        const char *trailer_item, *variable_name;
 389
 390        if (!skip_prefix(conf_key, "trailer.", &trailer_item))
 391                return 0;
 392
 393        variable_name = strrchr(trailer_item, '.');
 394        if (!variable_name) {
 395                if (!strcmp(trailer_item, "where")) {
 396                        if (set_where(&default_conf_info, value) < 0)
 397                                warning(_("unknown value '%s' for key '%s'"),
 398                                        value, conf_key);
 399                } else if (!strcmp(trailer_item, "ifexists")) {
 400                        if (set_if_exists(&default_conf_info, value) < 0)
 401                                warning(_("unknown value '%s' for key '%s'"),
 402                                        value, conf_key);
 403                } else if (!strcmp(trailer_item, "ifmissing")) {
 404                        if (set_if_missing(&default_conf_info, value) < 0)
 405                                warning(_("unknown value '%s' for key '%s'"),
 406                                        value, conf_key);
 407                } else if (!strcmp(trailer_item, "separators")) {
 408                        separators = xstrdup(value);
 409                }
 410        }
 411        return 0;
 412}
 413
 414static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 415{
 416        const char *trailer_item, *variable_name;
 417        struct trailer_item *item;
 418        struct conf_info *conf;
 419        char *name = NULL;
 420        enum trailer_info_type type;
 421        int i;
 422
 423        if (!skip_prefix(conf_key, "trailer.", &trailer_item))
 424                return 0;
 425
 426        variable_name = strrchr(trailer_item, '.');
 427        if (!variable_name)
 428                return 0;
 429
 430        variable_name++;
 431        for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
 432                if (strcmp(trailer_config_items[i].name, variable_name))
 433                        continue;
 434                name = xstrndup(trailer_item,  variable_name - trailer_item - 1);
 435                type = trailer_config_items[i].type;
 436                break;
 437        }
 438
 439        if (!name)
 440                return 0;
 441
 442        item = get_conf_item(name);
 443        conf = &item->conf;
 444        free(name);
 445
 446        switch (type) {
 447        case TRAILER_KEY:
 448                if (conf->key)
 449                        warning(_("more than one %s"), conf_key);
 450                conf->key = xstrdup(value);
 451                break;
 452        case TRAILER_COMMAND:
 453                if (conf->command)
 454                        warning(_("more than one %s"), conf_key);
 455                conf->command = xstrdup(value);
 456                break;
 457        case TRAILER_WHERE:
 458                if (set_where(conf, value))
 459                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 460                break;
 461        case TRAILER_IF_EXISTS:
 462                if (set_if_exists(conf, value))
 463                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 464                break;
 465        case TRAILER_IF_MISSING:
 466                if (set_if_missing(conf, value))
 467                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 468                break;
 469        default:
 470                die("internal bug in trailer.c");
 471        }
 472        return 0;
 473}
 474
 475static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)
 476{
 477        size_t len;
 478        struct strbuf seps = STRBUF_INIT;
 479        strbuf_addstr(&seps, separators);
 480        strbuf_addch(&seps, '=');
 481        len = strcspn(trailer, seps.buf);
 482        strbuf_release(&seps);
 483        if (len == 0)
 484                return error(_("empty trailer token in trailer '%s'"), trailer);
 485        if (len < strlen(trailer)) {
 486                strbuf_add(tok, trailer, len);
 487                strbuf_trim(tok);
 488                strbuf_addstr(val, trailer + len + 1);
 489                strbuf_trim(val);
 490        } else {
 491                strbuf_addstr(tok, trailer);
 492                strbuf_trim(tok);
 493        }
 494        return 0;
 495}
 496
 497static const char *token_from_item(struct trailer_item *item, char *tok)
 498{
 499        if (item->conf.key)
 500                return item->conf.key;
 501        if (tok)
 502                return tok;
 503        return item->conf.name;
 504}
 505
 506static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
 507                                             char *tok, char *val)
 508{
 509        struct trailer_item *new = xcalloc(sizeof(*new), 1);
 510        new->value = val;
 511
 512        if (conf_item) {
 513                duplicate_conf(&new->conf, &conf_item->conf);
 514                new->token = xstrdup(token_from_item(conf_item, tok));
 515                free(tok);
 516        } else {
 517                duplicate_conf(&new->conf, &default_conf_info);
 518                new->token = tok;
 519        }
 520
 521        return new;
 522}
 523
 524static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)
 525{
 526        if (!strncasecmp(tok, item->conf.name, tok_len))
 527                return 1;
 528        return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;
 529}
 530
 531static struct trailer_item *create_trailer_item(const char *string)
 532{
 533        struct strbuf tok = STRBUF_INIT;
 534        struct strbuf val = STRBUF_INIT;
 535        struct trailer_item *item;
 536        int tok_len;
 537
 538        if (parse_trailer(&tok, &val, string))
 539                return NULL;
 540
 541        tok_len = token_len_without_separator(tok.buf, tok.len);
 542
 543        /* Lookup if the token matches something in the config */
 544        for (item = first_conf_item; item; item = item->next) {
 545                if (token_matches_item(tok.buf, item, tok_len))
 546                        return new_trailer_item(item,
 547                                                strbuf_detach(&tok, NULL),
 548                                                strbuf_detach(&val, NULL));
 549        }
 550
 551        return new_trailer_item(NULL,
 552                                strbuf_detach(&tok, NULL),
 553                                strbuf_detach(&val, NULL));
 554}
 555
 556static void add_trailer_item(struct trailer_item **first,
 557                             struct trailer_item **last,
 558                             struct trailer_item *new)
 559{
 560        if (!new)
 561                return;
 562        if (!*last) {
 563                *first = new;
 564                *last = new;
 565        } else {
 566                (*last)->next = new;
 567                new->previous = *last;
 568                *last = new;
 569        }
 570}
 571
 572static struct trailer_item *process_command_line_args(struct string_list *trailers)
 573{
 574        struct trailer_item *arg_tok_first = NULL;
 575        struct trailer_item *arg_tok_last = NULL;
 576        struct string_list_item *tr;
 577
 578        for_each_string_list_item(tr, trailers) {
 579                struct trailer_item *new = create_trailer_item(tr->string);
 580                add_trailer_item(&arg_tok_first, &arg_tok_last, new);
 581        }
 582
 583        return arg_tok_first;
 584}
 585
 586static struct strbuf **read_input_file(const char *file)
 587{
 588        struct strbuf **lines;
 589        struct strbuf sb = STRBUF_INIT;
 590
 591        if (file) {
 592                if (strbuf_read_file(&sb, file, 0) < 0)
 593                        die_errno(_("could not read input file '%s'"), file);
 594        } else {
 595                if (strbuf_read(&sb, fileno(stdin), 0) < 0)
 596                        die_errno(_("could not read from stdin"));
 597        }
 598
 599        lines = strbuf_split(&sb, '\n');
 600
 601        strbuf_release(&sb);
 602
 603        return lines;
 604}
 605
 606/*
 607 * Return the (0 based) index of the start of the patch or the line
 608 * count if there is no patch in the message.
 609 */
 610static int find_patch_start(struct strbuf **lines, int count)
 611{
 612        int i;
 613
 614        /* Get the start of the patch part if any */
 615        for (i = 0; i < count; i++) {
 616                if (starts_with(lines[i]->buf, "---"))
 617                        return i;
 618        }
 619
 620        return count;
 621}
 622
 623/*
 624 * Return the (0 based) index of the first trailer line or count if
 625 * there are no trailers. Trailers are searched only in the lines from
 626 * index (count - 1) down to index 0.
 627 */
 628static int find_trailer_start(struct strbuf **lines, int count)
 629{
 630        int start, only_spaces = 1;
 631
 632        /*
 633         * Get the start of the trailers by looking starting from the end
 634         * for a line with only spaces before lines with one separator.
 635         */
 636        for (start = count - 1; start >= 0; start--) {
 637                if (lines[start]->buf[0] == comment_line_char)
 638                        continue;
 639                if (contains_only_spaces(lines[start]->buf)) {
 640                        if (only_spaces)
 641                                continue;
 642                        return start + 1;
 643                }
 644                if (strcspn(lines[start]->buf, separators) < lines[start]->len) {
 645                        if (only_spaces)
 646                                only_spaces = 0;
 647                        continue;
 648                }
 649                return count;
 650        }
 651
 652        return only_spaces ? count : 0;
 653}
 654
 655static int has_blank_line_before(struct strbuf **lines, int start)
 656{
 657        for (;start >= 0; start--) {
 658                if (lines[start]->buf[0] == comment_line_char)
 659                        continue;
 660                return contains_only_spaces(lines[start]->buf);
 661        }
 662        return 0;
 663}
 664
 665static void print_lines(struct strbuf **lines, int start, int end)
 666{
 667        int i;
 668        for (i = start; lines[i] && i < end; i++)
 669                printf("%s", lines[i]->buf);
 670}
 671
 672static int process_input_file(struct strbuf **lines,
 673                              struct trailer_item **in_tok_first,
 674                              struct trailer_item **in_tok_last)
 675{
 676        int count = 0;
 677        int patch_start, trailer_start, i;
 678
 679        /* Get the line count */
 680        while (lines[count])
 681                count++;
 682
 683        patch_start = find_patch_start(lines, count);
 684        trailer_start = find_trailer_start(lines, patch_start);
 685
 686        /* Print lines before the trailers as is */
 687        print_lines(lines, 0, trailer_start);
 688
 689        if (!has_blank_line_before(lines, trailer_start - 1))
 690                printf("\n");
 691
 692        /* Parse trailer lines */
 693        for (i = trailer_start; i < patch_start; i++) {
 694                struct trailer_item *new = create_trailer_item(lines[i]->buf);
 695                add_trailer_item(in_tok_first, in_tok_last, new);
 696        }
 697
 698        return patch_start;
 699}