trailer.con commit trailer: read and process config information (46a0613)
   1#include "cache.h"
   2/*
   3 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
   4 */
   5
   6enum action_where { WHERE_END, WHERE_AFTER, WHERE_BEFORE, WHERE_START };
   7enum action_if_exists { EXISTS_ADD_IF_DIFFERENT_NEIGHBOR, EXISTS_ADD_IF_DIFFERENT,
   8                        EXISTS_ADD, EXISTS_REPLACE, EXISTS_DO_NOTHING };
   9enum action_if_missing { MISSING_ADD, MISSING_DO_NOTHING };
  10
  11struct conf_info {
  12        char *name;
  13        char *key;
  14        char *command;
  15        enum action_where where;
  16        enum action_if_exists if_exists;
  17        enum action_if_missing if_missing;
  18};
  19
  20static struct conf_info default_conf_info;
  21
  22struct trailer_item {
  23        struct trailer_item *previous;
  24        struct trailer_item *next;
  25        const char *token;
  26        const char *value;
  27        struct conf_info conf;
  28};
  29
  30static struct trailer_item *first_conf_item;
  31
  32static char *separators = ":";
  33
  34static int after_or_end(enum action_where where)
  35{
  36        return (where == WHERE_AFTER) || (where == WHERE_END);
  37}
  38
  39/*
  40 * Return the length of the string not including any final
  41 * punctuation. E.g., the input "Signed-off-by:" would return
  42 * 13, stripping the trailing punctuation but retaining
  43 * internal punctuation.
  44 */
  45static size_t token_len_without_separator(const char *token, size_t len)
  46{
  47        while (len > 0 && !isalnum(token[len - 1]))
  48                len--;
  49        return len;
  50}
  51
  52static int same_token(struct trailer_item *a, struct trailer_item *b)
  53{
  54        size_t a_len = token_len_without_separator(a->token, strlen(a->token));
  55        size_t b_len = token_len_without_separator(b->token, strlen(b->token));
  56        size_t min_len = (a_len > b_len) ? b_len : a_len;
  57
  58        return !strncasecmp(a->token, b->token, min_len);
  59}
  60
  61static int same_value(struct trailer_item *a, struct trailer_item *b)
  62{
  63        return !strcasecmp(a->value, b->value);
  64}
  65
  66static int same_trailer(struct trailer_item *a, struct trailer_item *b)
  67{
  68        return same_token(a, b) && same_value(a, b);
  69}
  70
  71static void free_trailer_item(struct trailer_item *item)
  72{
  73        free(item->conf.name);
  74        free(item->conf.key);
  75        free(item->conf.command);
  76        free((char *)item->token);
  77        free((char *)item->value);
  78        free(item);
  79}
  80
  81static void update_last(struct trailer_item **last)
  82{
  83        if (*last)
  84                while ((*last)->next != NULL)
  85                        *last = (*last)->next;
  86}
  87
  88static void update_first(struct trailer_item **first)
  89{
  90        if (*first)
  91                while ((*first)->previous != NULL)
  92                        *first = (*first)->previous;
  93}
  94
  95static void add_arg_to_input_list(struct trailer_item *on_tok,
  96                                  struct trailer_item *arg_tok,
  97                                  struct trailer_item **first,
  98                                  struct trailer_item **last)
  99{
 100        if (after_or_end(arg_tok->conf.where)) {
 101                arg_tok->next = on_tok->next;
 102                on_tok->next = arg_tok;
 103                arg_tok->previous = on_tok;
 104                if (arg_tok->next)
 105                        arg_tok->next->previous = arg_tok;
 106                update_last(last);
 107        } else {
 108                arg_tok->previous = on_tok->previous;
 109                on_tok->previous = arg_tok;
 110                arg_tok->next = on_tok;
 111                if (arg_tok->previous)
 112                        arg_tok->previous->next = arg_tok;
 113                update_first(first);
 114        }
 115}
 116
 117static int check_if_different(struct trailer_item *in_tok,
 118                              struct trailer_item *arg_tok,
 119                              int check_all)
 120{
 121        enum action_where where = arg_tok->conf.where;
 122        do {
 123                if (!in_tok)
 124                        return 1;
 125                if (same_trailer(in_tok, arg_tok))
 126                        return 0;
 127                /*
 128                 * if we want to add a trailer after another one,
 129                 * we have to check those before this one
 130                 */
 131                in_tok = after_or_end(where) ? in_tok->previous : in_tok->next;
 132        } while (check_all);
 133        return 1;
 134}
 135
 136static void remove_from_list(struct trailer_item *item,
 137                             struct trailer_item **first,
 138                             struct trailer_item **last)
 139{
 140        struct trailer_item *next = item->next;
 141        struct trailer_item *previous = item->previous;
 142
 143        if (next) {
 144                item->next->previous = previous;
 145                item->next = NULL;
 146        } else if (last)
 147                *last = previous;
 148
 149        if (previous) {
 150                item->previous->next = next;
 151                item->previous = NULL;
 152        } else if (first)
 153                *first = next;
 154}
 155
 156static struct trailer_item *remove_first(struct trailer_item **first)
 157{
 158        struct trailer_item *item = *first;
 159        *first = item->next;
 160        if (item->next) {
 161                item->next->previous = NULL;
 162                item->next = NULL;
 163        }
 164        return item;
 165}
 166
 167static void apply_arg_if_exists(struct trailer_item *in_tok,
 168                                struct trailer_item *arg_tok,
 169                                struct trailer_item *on_tok,
 170                                struct trailer_item **in_tok_first,
 171                                struct trailer_item **in_tok_last)
 172{
 173        switch (arg_tok->conf.if_exists) {
 174        case EXISTS_DO_NOTHING:
 175                free_trailer_item(arg_tok);
 176                break;
 177        case EXISTS_REPLACE:
 178                add_arg_to_input_list(on_tok, arg_tok,
 179                                      in_tok_first, in_tok_last);
 180                remove_from_list(in_tok, in_tok_first, in_tok_last);
 181                free_trailer_item(in_tok);
 182                break;
 183        case EXISTS_ADD:
 184                add_arg_to_input_list(on_tok, arg_tok,
 185                                      in_tok_first, in_tok_last);
 186                break;
 187        case EXISTS_ADD_IF_DIFFERENT:
 188                if (check_if_different(in_tok, arg_tok, 1))
 189                        add_arg_to_input_list(on_tok, arg_tok,
 190                                              in_tok_first, in_tok_last);
 191                else
 192                        free_trailer_item(arg_tok);
 193                break;
 194        case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
 195                if (check_if_different(on_tok, arg_tok, 0))
 196                        add_arg_to_input_list(on_tok, arg_tok,
 197                                              in_tok_first, in_tok_last);
 198                else
 199                        free_trailer_item(arg_tok);
 200                break;
 201        }
 202}
 203
 204static void apply_arg_if_missing(struct trailer_item **in_tok_first,
 205                                 struct trailer_item **in_tok_last,
 206                                 struct trailer_item *arg_tok)
 207{
 208        struct trailer_item **in_tok;
 209        enum action_where where;
 210
 211        switch (arg_tok->conf.if_missing) {
 212        case MISSING_DO_NOTHING:
 213                free_trailer_item(arg_tok);
 214                break;
 215        case MISSING_ADD:
 216                where = arg_tok->conf.where;
 217                in_tok = after_or_end(where) ? in_tok_last : in_tok_first;
 218                if (*in_tok) {
 219                        add_arg_to_input_list(*in_tok, arg_tok,
 220                                              in_tok_first, in_tok_last);
 221                } else {
 222                        *in_tok_first = arg_tok;
 223                        *in_tok_last = arg_tok;
 224                }
 225                break;
 226        }
 227}
 228
 229static int find_same_and_apply_arg(struct trailer_item **in_tok_first,
 230                                   struct trailer_item **in_tok_last,
 231                                   struct trailer_item *arg_tok)
 232{
 233        struct trailer_item *in_tok;
 234        struct trailer_item *on_tok;
 235        struct trailer_item *following_tok;
 236
 237        enum action_where where = arg_tok->conf.where;
 238        int middle = (where == WHERE_AFTER) || (where == WHERE_BEFORE);
 239        int backwards = after_or_end(where);
 240        struct trailer_item *start_tok = backwards ? *in_tok_last : *in_tok_first;
 241
 242        for (in_tok = start_tok; in_tok; in_tok = following_tok) {
 243                following_tok = backwards ? in_tok->previous : in_tok->next;
 244                if (!same_token(in_tok, arg_tok))
 245                        continue;
 246                on_tok = middle ? in_tok : start_tok;
 247                apply_arg_if_exists(in_tok, arg_tok, on_tok,
 248                                    in_tok_first, in_tok_last);
 249                return 1;
 250        }
 251        return 0;
 252}
 253
 254static void process_trailers_lists(struct trailer_item **in_tok_first,
 255                                   struct trailer_item **in_tok_last,
 256                                   struct trailer_item **arg_tok_first)
 257{
 258        struct trailer_item *arg_tok;
 259        struct trailer_item *next_arg;
 260
 261        if (!*arg_tok_first)
 262                return;
 263
 264        for (arg_tok = *arg_tok_first; arg_tok; arg_tok = next_arg) {
 265                int applied = 0;
 266
 267                next_arg = arg_tok->next;
 268                remove_from_list(arg_tok, arg_tok_first, NULL);
 269
 270                applied = find_same_and_apply_arg(in_tok_first,
 271                                                  in_tok_last,
 272                                                  arg_tok);
 273
 274                if (!applied)
 275                        apply_arg_if_missing(in_tok_first,
 276                                             in_tok_last,
 277                                             arg_tok);
 278        }
 279}
 280
 281static int set_where(struct conf_info *item, const char *value)
 282{
 283        if (!strcasecmp("after", value))
 284                item->where = WHERE_AFTER;
 285        else if (!strcasecmp("before", value))
 286                item->where = WHERE_BEFORE;
 287        else if (!strcasecmp("end", value))
 288                item->where = WHERE_END;
 289        else if (!strcasecmp("start", value))
 290                item->where = WHERE_START;
 291        else
 292                return -1;
 293        return 0;
 294}
 295
 296static int set_if_exists(struct conf_info *item, const char *value)
 297{
 298        if (!strcasecmp("addIfDifferent", value))
 299                item->if_exists = EXISTS_ADD_IF_DIFFERENT;
 300        else if (!strcasecmp("addIfDifferentNeighbor", value))
 301                item->if_exists = EXISTS_ADD_IF_DIFFERENT_NEIGHBOR;
 302        else if (!strcasecmp("add", value))
 303                item->if_exists = EXISTS_ADD;
 304        else if (!strcasecmp("replace", value))
 305                item->if_exists = EXISTS_REPLACE;
 306        else if (!strcasecmp("doNothing", value))
 307                item->if_exists = EXISTS_DO_NOTHING;
 308        else
 309                return -1;
 310        return 0;
 311}
 312
 313static int set_if_missing(struct conf_info *item, const char *value)
 314{
 315        if (!strcasecmp("doNothing", value))
 316                item->if_missing = MISSING_DO_NOTHING;
 317        else if (!strcasecmp("add", value))
 318                item->if_missing = MISSING_ADD;
 319        else
 320                return -1;
 321        return 0;
 322}
 323
 324static void duplicate_conf(struct conf_info *dst, struct conf_info *src)
 325{
 326        *dst = *src;
 327        if (src->name)
 328                dst->name = xstrdup(src->name);
 329        if (src->key)
 330                dst->key = xstrdup(src->key);
 331        if (src->command)
 332                dst->command = xstrdup(src->command);
 333}
 334
 335static struct trailer_item *get_conf_item(const char *name)
 336{
 337        struct trailer_item *item;
 338        struct trailer_item *previous;
 339
 340        /* Look up item with same name */
 341        for (previous = NULL, item = first_conf_item;
 342             item;
 343             previous = item, item = item->next) {
 344                if (!strcasecmp(item->conf.name, name))
 345                        return item;
 346        }
 347
 348        /* Item does not already exists, create it */
 349        item = xcalloc(sizeof(struct trailer_item), 1);
 350        duplicate_conf(&item->conf, &default_conf_info);
 351        item->conf.name = xstrdup(name);
 352
 353        if (!previous)
 354                first_conf_item = item;
 355        else {
 356                previous->next = item;
 357                item->previous = previous;
 358        }
 359
 360        return item;
 361}
 362
 363enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
 364                         TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
 365
 366static struct {
 367        const char *name;
 368        enum trailer_info_type type;
 369} trailer_config_items[] = {
 370        { "key", TRAILER_KEY },
 371        { "command", TRAILER_COMMAND },
 372        { "where", TRAILER_WHERE },
 373        { "ifexists", TRAILER_IF_EXISTS },
 374        { "ifmissing", TRAILER_IF_MISSING }
 375};
 376
 377static int git_trailer_default_config(const char *conf_key, const char *value, void *cb)
 378{
 379        const char *trailer_item, *variable_name;
 380
 381        if (!skip_prefix(conf_key, "trailer.", &trailer_item))
 382                return 0;
 383
 384        variable_name = strrchr(trailer_item, '.');
 385        if (!variable_name) {
 386                if (!strcmp(trailer_item, "where")) {
 387                        if (set_where(&default_conf_info, value) < 0)
 388                                warning(_("unknown value '%s' for key '%s'"),
 389                                        value, conf_key);
 390                } else if (!strcmp(trailer_item, "ifexists")) {
 391                        if (set_if_exists(&default_conf_info, value) < 0)
 392                                warning(_("unknown value '%s' for key '%s'"),
 393                                        value, conf_key);
 394                } else if (!strcmp(trailer_item, "ifmissing")) {
 395                        if (set_if_missing(&default_conf_info, value) < 0)
 396                                warning(_("unknown value '%s' for key '%s'"),
 397                                        value, conf_key);
 398                } else if (!strcmp(trailer_item, "separators")) {
 399                        separators = xstrdup(value);
 400                }
 401        }
 402        return 0;
 403}
 404
 405static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 406{
 407        const char *trailer_item, *variable_name;
 408        struct trailer_item *item;
 409        struct conf_info *conf;
 410        char *name = NULL;
 411        enum trailer_info_type type;
 412        int i;
 413
 414        if (!skip_prefix(conf_key, "trailer.", &trailer_item))
 415                return 0;
 416
 417        variable_name = strrchr(trailer_item, '.');
 418        if (!variable_name)
 419                return 0;
 420
 421        variable_name++;
 422        for (i = 0; i < ARRAY_SIZE(trailer_config_items); i++) {
 423                if (strcmp(trailer_config_items[i].name, variable_name))
 424                        continue;
 425                name = xstrndup(trailer_item,  variable_name - trailer_item - 1);
 426                type = trailer_config_items[i].type;
 427                break;
 428        }
 429
 430        if (!name)
 431                return 0;
 432
 433        item = get_conf_item(name);
 434        conf = &item->conf;
 435        free(name);
 436
 437        switch (type) {
 438        case TRAILER_KEY:
 439                if (conf->key)
 440                        warning(_("more than one %s"), conf_key);
 441                conf->key = xstrdup(value);
 442                break;
 443        case TRAILER_COMMAND:
 444                if (conf->command)
 445                        warning(_("more than one %s"), conf_key);
 446                conf->command = xstrdup(value);
 447                break;
 448        case TRAILER_WHERE:
 449                if (set_where(conf, value))
 450                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 451                break;
 452        case TRAILER_IF_EXISTS:
 453                if (set_if_exists(conf, value))
 454                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 455                break;
 456        case TRAILER_IF_MISSING:
 457                if (set_if_missing(conf, value))
 458                        warning(_("unknown value '%s' for key '%s'"), value, conf_key);
 459                break;
 460        default:
 461                die("internal bug in trailer.c");
 462        }
 463        return 0;
 464}