trailer.con commit trailer: process command line trailer arguments (f0a90b4)
   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 void free_trailer_item(struct trailer_item *item)
  73{
  74        free(item->conf.name);
  75        free(item->conf.key);
  76        free(item->conf.command);
  77        free((char *)item->token);
  78        free((char *)item->value);
  79        free(item);
  80}
  81
  82static void update_last(struct trailer_item **last)
  83{
  84        if (*last)
  85                while ((*last)->next != NULL)
  86                        *last = (*last)->next;
  87}
  88
  89static void update_first(struct trailer_item **first)
  90{
  91        if (*first)
  92                while ((*first)->previous != NULL)
  93                        *first = (*first)->previous;
  94}
  95
  96static void add_arg_to_input_list(struct trailer_item *on_tok,
  97                                  struct trailer_item *arg_tok,
  98                                  struct trailer_item **first,
  99                                  struct trailer_item **last)
 100{
 101        if (after_or_end(arg_tok->conf.where)) {
 102                arg_tok->next = on_tok->next;
 103                on_tok->next = arg_tok;
 104                arg_tok->previous = on_tok;
 105                if (arg_tok->next)
 106                        arg_tok->next->previous = arg_tok;
 107                update_last(last);
 108        } else {
 109                arg_tok->previous = on_tok->previous;
 110                on_tok->previous = arg_tok;
 111                arg_tok->next = on_tok;
 112                if (arg_tok->previous)
 113                        arg_tok->previous->next = arg_tok;
 114                update_first(first);
 115        }
 116}
 117
 118static int check_if_different(struct trailer_item *in_tok,
 119                              struct trailer_item *arg_tok,
 120                              int check_all)
 121{
 122        enum action_where where = arg_tok->conf.where;
 123        do {
 124                if (!in_tok)
 125                        return 1;
 126                if (same_trailer(in_tok, arg_tok))
 127                        return 0;
 128                /*
 129                 * if we want to add a trailer after another one,
 130                 * we have to check those before this one
 131                 */
 132                in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
 133        } while (check_all);
 134        return 1;
 135}
 136
 137static void remove_from_list(struct trailer_item *item,
 138                             struct trailer_item **first,
 139                             struct trailer_item **last)
 140{
 141        struct trailer_item *next = item->next;
 142        struct trailer_item *previous = item->previous;
 143
 144        if (next) {
 145                item->next->previous = previous;
 146                item->next = NULL;
 147        } else if (last)
 148                *last = previous;
 149
 150        if (previous) {
 151                item->previous->next = next;
 152                item->previous = NULL;
 153        } else if (first)
 154                *first = next;
 155}
 156
 157static struct trailer_item *remove_first(struct trailer_item **first)
 158{
 159        struct trailer_item *item = *first;
 160        *first = item->next;
 161        if (item->next) {
 162                item->next->previous = NULL;
 163                item->next = NULL;
 164        }
 165        return item;
 166}
 167
 168static void apply_arg_if_exists(struct trailer_item *in_tok,
 169                                struct trailer_item *arg_tok,
 170                                struct trailer_item *on_tok,
 171                                struct trailer_item **in_tok_first,
 172                                struct trailer_item **in_tok_last)
 173{
 174        switch (arg_tok->conf.if_exists) {
 175        case EXISTS_DO_NOTHING:
 176                free_trailer_item(arg_tok);
 177                break;
 178        case EXISTS_REPLACE:
 179                add_arg_to_input_list(on_tok, arg_tok,
 180                                      in_tok_first, in_tok_last);
 181                remove_from_list(in_tok, in_tok_first, in_tok_last);
 182                free_trailer_item(in_tok);
 183                break;
 184        case EXISTS_ADD:
 185                add_arg_to_input_list(on_tok, arg_tok,
 186                                      in_tok_first, in_tok_last);
 187                break;
 188        case EXISTS_ADD_IF_DIFFERENT:
 189                if (check_if_different(in_tok, arg_tok, 1))
 190                        add_arg_to_input_list(on_tok, arg_tok,
 191                                              in_tok_first, in_tok_last);
 192                else
 193                        free_trailer_item(arg_tok);
 194                break;
 195        case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
 196                if (check_if_different(on_tok, arg_tok, 0))
 197                        add_arg_to_input_list(on_tok, arg_tok,
 198                                              in_tok_first, in_tok_last);
 199                else
 200                        free_trailer_item(arg_tok);
 201                break;
 202        }
 203}
 204
 205static void apply_arg_if_missing(struct trailer_item **in_tok_first,
 206                                 struct trailer_item **in_tok_last,
 207                                 struct trailer_item *arg_tok)
 208{
 209        struct trailer_item **in_tok;
 210        enum action_where where;
 211
 212        switch (arg_tok->conf.if_missing) {
 213        case MISSING_DO_NOTHING:
 214                free_trailer_item(arg_tok);
 215                break;
 216        case MISSING_ADD:
 217                where = arg_tok->conf.where;
 218                in_tok = after_or_end(where) ? in_tok_last : in_tok_first;
 219                if (*in_tok) {
 220                        add_arg_to_input_list(*in_tok, arg_tok,
 221                                              in_tok_first, in_tok_last);
 222                } else {
 223                        *in_tok_first = arg_tok;
 224                        *in_tok_last = arg_tok;
 225                }
 226                break;
 227        }
 228}
 229
 230static int find_same_and_apply_arg(struct trailer_item **in_tok_first,
 231                                   struct trailer_item **in_tok_last,
 232                                   struct trailer_item *arg_tok)
 233{
 234        struct trailer_item *in_tok;
 235        struct trailer_item *on_tok;
 236        struct trailer_item *following_tok;
 237
 238        enum action_where where = arg_tok->conf.where;
 239        int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);
 240        int backwards = after_or_end(where);
 241        struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;
 242
 243        for (in_tok = start_tok; in_tok; in_tok = following_tok) {
 244                following_tok = backwards ? in_tok->previous : in_tok->next;
 245                if (!same_token(in_tok, arg_tok))
 246                        continue;
 247                on_tok = middle ? in_tok : start_tok;
 248                apply_arg_if_exists(in_tok, arg_tok, on_tok,
 249                                    in_tok_first, in_tok_last);
 250                return 1;
 251        }
 252        return 0;
 253}
 254
 255static void process_trailers_lists(struct trailer_item **in_tok_first,
 256                                   struct trailer_item **in_tok_last,
 257                                   struct trailer_item **arg_tok_first)
 258{
 259        struct trailer_item *arg_tok;
 260        struct trailer_item *next_arg;
 261
 262        if (!*arg_tok_first)
 263                return;
 264
 265        for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
 266                int applied = 0;
 267
 268                next_arg = arg_tok->next;
 269                remove_from_list(arg_tok, arg_tok_first, NULL);
 270
 271                applied = find_same_and_apply_arg(in_tok_first,
 272                                                  in_tok_last,
 273                                                  arg_tok);
 274
 275                if (!applied)
 276                        apply_arg_if_missing(in_tok_first,
 277                                             in_tok_last,
 278                                             arg_tok);
 279        }
 280}
 281
 282static int set_where(struct conf_info *item, const char *value)
 283{
 284        if (!strcasecmp("after", value))
 285                item->where = WHERE_AFTER;
 286        else if (!strcasecmp("before", value))
 287                item->where = WHERE_BEFORE;
 288        else if (!strcasecmp("end", value))
 289                item->where = WHERE_END;
 290        else if (!strcasecmp("start", value))
 291                item->where = WHERE_START;
 292        else
 293                return -1;
 294        return 0;
 295}
 296
 297static int set_if_exists(struct conf_info *item, const char *value)
 298{
 299        if (!strcasecmp("addIfDifferent", value))
 300                item->if_exists = EXISTS_ADD_IF_DIFFERENT;
 301        else if (!strcasecmp("addIfDifferentNeighbor", value))
 302                item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
 303        else if (!strcasecmp("add", value))
 304                item->if_exists = EXISTS_ADD;
 305        else if (!strcasecmp("replace", value))
 306                item->if_exists = EXISTS_REPLACE;
 307        else if (!strcasecmp("doNothing", value))
 308                item->if_exists = EXISTS_DO_NOTHING;
 309        else
 310                return -1;
 311        return 0;
 312}
 313
 314static int set_if_missing(struct conf_info *item, const char *value)
 315{
 316        if (!strcasecmp("doNothing", value))
 317                item->if_missing = MISSING_DO_NOTHING;
 318        else if (!strcasecmp("add", value))
 319                item->if_missing = MISSING_ADD;
 320        else
 321                return -1;
 322        return 0;
 323}
 324
 325static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
 326{
 327        *dst = *src;
 328        if (src->name)
 329                dst->name = xstrdup(src->name);
 330        if (src->key)
 331                dst->key = xstrdup(src->key);
 332        if (src->command)
 333                dst->command = xstrdup(src->command);
 334}
 335
 336static struct trailer_item *get_conf_item(const char *name)
 337{
 338        struct trailer_item *item;
 339        struct trailer_item *previous;
 340
 341        /* Look up item with same name */
 342        for (previous = NULL, item = first_conf_item;
 343             item;
 344             previous = item, item = item->next) {
 345                if (!strcasecmp(item->conf.name, name))
 346                        return item;
 347        }
 348
 349        /* Item does not already exists, create it */
 350        item = xcalloc(sizeof(struct trailer_item), 1);
 351        duplicate_conf(&item->conf, &default_conf_info);
 352        item->conf.name = xstrdup(name);
 353
 354        if (!previous)
 355                first_conf_item = item;
 356        else {
 357                previous->next = item;
 358                item->previous = previous;
 359        }
 360
 361        return item;
 362}
 363
 364enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
 365                         TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
 366
 367static struct {
 368        const char *name;
 369        enum trailer_info_type type;
 370} trailer_config_items[] = {
 371        { "key", TRAILER_KEY },
 372        { "command", TRAILER_COMMAND },
 373        { "where", TRAILER_WHERE },
 374        { "ifexists", TRAILER_IF_EXISTS },
 375        { "ifmissing", TRAILER_IF_MISSING }
 376};
 377
 378static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
 379{
 380        const char *trailer_item, *variable_name;
 381
 382        if (!skip_prefix(conf_key, "trailer.", &trailer_item))
 383                return 0;
 384
 385        variable_name = strrchr(trailer_item, '.');
 386        if (!variable_name) {
 387                if (!strcmp(trailer_item, "where")) {
 388                        if (set_where(&default_conf_info, value) < 0)
 389                                warning(_("unknown value '%s' for key '%s'"),
 390                                        value, conf_key);
 391                } else if (!strcmp(trailer_item, "ifexists")) {
 392                        if (set_if_exists(&default_conf_info, value) < 0)
 393                                warning(_("unknown value '%s' for key '%s'"),
 394                                        value, conf_key);
 395                } else if (!strcmp(trailer_item, "ifmissing")) {
 396                        if (set_if_missing(&default_conf_info, value) < 0)
 397                                warning(_("unknown value '%s' for key '%s'"),
 398                                        value, conf_key);
 399                } else if (!strcmp(trailer_item, "separators")) {
 400                        separators = xstrdup(value);
 401                }
 402        }
 403        return 0;
 404}
 405
 406static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 407{
 408        const char *trailer_item, *variable_name;
 409        struct trailer_item *item;
 410        struct conf_info *conf;
 411        char *name = NULL;
 412        enum trailer_info_type type;
 413        int i;
 414
 415        if (!skip_prefix(conf_key, "trailer.", &trailer_item))
 416                return 0;
 417
 418        variable_name = strrchr(trailer_item, '.');
 419        if (!variable_name)
 420                return 0;
 421
 422        variable_name++;
 423        for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
 424                if (strcmp(trailer_config_items[i].name, variable_name))
 425                        continue;
 426                name = xstrndup(trailer_item,  variable_name - trailer_item - 1);
 427                type = trailer_config_items[i].type;
 428                break;
 429        }
 430
 431        if (!name)
 432                return 0;
 433
 434        item = get_conf_item(name);
 435        conf = &item->conf;
 436        free(name);
 437
 438        switch (type) {
 439        case TRAILER_KEY:
 440                if (conf->key)
 441                        warning(_("more than one %s"), conf_key);
 442                conf->key = xstrdup(value);
 443                break;
 444        case TRAILER_COMMAND:
 445                if (conf->command)
 446                        warning(_("more than one %s"), conf_key);
 447                conf->command = xstrdup(value);
 448                break;
 449        case TRAILER_WHERE:
 450                if (set_where(conf, value))
 451                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 452                break;
 453        case TRAILER_IF_EXISTS:
 454                if (set_if_exists(conf, value))
 455                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 456                break;
 457        case TRAILER_IF_MISSING:
 458                if (set_if_missing(conf, value))
 459                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 460                break;
 461        default:
 462                die("internal bug in trailer.c");
 463        }
 464        return 0;
 465}
 466
 467static int parse_trailer(struct strbuf *tok, struct strbuf *val, const char *trailer)
 468{
 469        size_t len;
 470        struct strbuf seps = STRBUF_INIT;
 471        strbuf_addstr(&seps, separators);
 472        strbuf_addch(&seps, '=');
 473        len = strcspn(trailer, seps.buf);
 474        strbuf_release(&seps);
 475        if (len == 0)
 476                return error(_("empty trailer token in trailer '%s'"), trailer);
 477        if (len < strlen(trailer)) {
 478                strbuf_add(tok, trailer, len);
 479                strbuf_trim(tok);
 480                strbuf_addstr(val, trailer + len + 1);
 481                strbuf_trim(val);
 482        } else {
 483                strbuf_addstr(tok, trailer);
 484                strbuf_trim(tok);
 485        }
 486        return 0;
 487}
 488
 489static const char *token_from_item(struct trailer_item *item, char *tok)
 490{
 491        if (item->conf.key)
 492                return item->conf.key;
 493        if (tok)
 494                return tok;
 495        return item->conf.name;
 496}
 497
 498static struct trailer_item *new_trailer_item(struct trailer_item *conf_item,
 499                                             char *tok, char *val)
 500{
 501        struct trailer_item *new = xcalloc(sizeof(*new), 1);
 502        new->value = val;
 503
 504        if (conf_item) {
 505                duplicate_conf(&new->conf, &conf_item->conf);
 506                new->token = xstrdup(token_from_item(conf_item, tok));
 507                free(tok);
 508        } else {
 509                duplicate_conf(&new->conf, &default_conf_info);
 510                new->token = tok;
 511        }
 512
 513        return new;
 514}
 515
 516static int token_matches_item(const char *tok, struct trailer_item *item, int tok_len)
 517{
 518        if (!strncasecmp(tok, item->conf.name, tok_len))
 519                return 1;
 520        return item->conf.key ? !strncasecmp(tok, item->conf.key, tok_len) : 0;
 521}
 522
 523static struct trailer_item *create_trailer_item(const char *string)
 524{
 525        struct strbuf tok = STRBUF_INIT;
 526        struct strbuf val = STRBUF_INIT;
 527        struct trailer_item *item;
 528        int tok_len;
 529
 530        if (parse_trailer(&tok, &val, string))
 531                return NULL;
 532
 533        tok_len = token_len_without_separator(tok.buf, tok.len);
 534
 535        /* Lookup if the token matches something in the config */
 536        for (item = first_conf_item; item; item = item->next) {
 537                if (token_matches_item(tok.buf, item, tok_len))
 538                        return new_trailer_item(item,
 539                                                strbuf_detach(&tok, NULL),
 540                                                strbuf_detach(&val, NULL));
 541        }
 542
 543        return new_trailer_item(NULL,
 544                                strbuf_detach(&tok, NULL),
 545                                strbuf_detach(&val, NULL));
 546}
 547
 548static void add_trailer_item(struct trailer_item **first,
 549                             struct trailer_item **last,
 550                             struct trailer_item *new)
 551{
 552        if (!new)
 553                return;
 554        if (!*last) {
 555                *first = new;
 556                *last = new;
 557        } else {
 558                (*last)->next = new;
 559                new->previous = *last;
 560                *last = new;
 561        }
 562}
 563
 564static struct trailer_item *process_command_line_args(struct string_list *trailers)
 565{
 566        struct trailer_item *arg_tok_first = NULL;
 567        struct trailer_item *arg_tok_last = NULL;
 568        struct string_list_item *tr;
 569
 570        for_each_string_list_item(tr, trailers) {
 571                struct trailer_item *new = create_trailer_item(tr->string);
 572                add_trailer_item(&arg_tok_first, &arg_tok_last, new);
 573        }
 574
 575        return arg_tok_first;
 576}