builtin / fmt-merge-msg.con commit merge: Make '--log' an integer option for number of shortlog entries (96e9420)
   1#include "builtin.h"
   2#include "cache.h"
   3#include "commit.h"
   4#include "diff.h"
   5#include "revision.h"
   6#include "tag.h"
   7#include "string-list.h"
   8
   9static const char * const fmt_merge_msg_usage[] = {
  10        "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]",
  11        NULL
  12};
  13
  14static int shortlog_len;
  15
  16static int fmt_merge_msg_config(const char *key, const char *value, void *cb)
  17{
  18        static int found_merge_log = 0;
  19        if (!strcmp("merge.log", key)) {
  20                found_merge_log = 1;
  21                shortlog_len = git_config_bool(key, value) ? DEFAULT_MERGE_LOG_LEN : 0;
  22                return 0;
  23        }
  24        if (!found_merge_log && !strcmp("merge.summary", key)) {
  25                shortlog_len = git_config_bool(key, value) ? DEFAULT_MERGE_LOG_LEN : 0;
  26                return 0;
  27        }
  28        return 0;
  29}
  30
  31struct src_data {
  32        struct string_list branch, tag, r_branch, generic;
  33        int head_status;
  34};
  35
  36void init_src_data(struct src_data *data)
  37{
  38        data->branch.strdup_strings = 1;
  39        data->tag.strdup_strings = 1;
  40        data->r_branch.strdup_strings = 1;
  41        data->generic.strdup_strings = 1;
  42}
  43
  44static struct string_list srcs = { NULL, 0, 0, 1 };
  45static struct string_list origins = { NULL, 0, 0, 1 };
  46
  47static int handle_line(char *line)
  48{
  49        int i, len = strlen(line);
  50        unsigned char *sha1;
  51        char *src, *origin;
  52        struct src_data *src_data;
  53        struct string_list_item *item;
  54        int pulling_head = 0;
  55
  56        if (len < 43 || line[40] != '\t')
  57                return 1;
  58
  59        if (!prefixcmp(line + 41, "not-for-merge"))
  60                return 0;
  61
  62        if (line[41] != '\t')
  63                return 2;
  64
  65        line[40] = 0;
  66        sha1 = xmalloc(20);
  67        i = get_sha1(line, sha1);
  68        line[40] = '\t';
  69        if (i)
  70                return 3;
  71
  72        if (line[len - 1] == '\n')
  73                line[len - 1] = 0;
  74        line += 42;
  75
  76        src = strstr(line, " of ");
  77        if (src) {
  78                *src = 0;
  79                src += 4;
  80                pulling_head = 0;
  81        } else {
  82                src = line;
  83                pulling_head = 1;
  84        }
  85
  86        item = unsorted_string_list_lookup(&srcs, src);
  87        if (!item) {
  88                item = string_list_append(&srcs, src);
  89                item->util = xcalloc(1, sizeof(struct src_data));
  90                init_src_data(item->util);
  91        }
  92        src_data = item->util;
  93
  94        if (pulling_head) {
  95                origin = src;
  96                src_data->head_status |= 1;
  97        } else if (!prefixcmp(line, "branch ")) {
  98                origin = line + 7;
  99                string_list_append(&src_data->branch, origin);
 100                src_data->head_status |= 2;
 101        } else if (!prefixcmp(line, "tag ")) {
 102                origin = line;
 103                string_list_append(&src_data->tag, origin + 4);
 104                src_data->head_status |= 2;
 105        } else if (!prefixcmp(line, "remote branch ")) {
 106                origin = line + 14;
 107                string_list_append(&src_data->r_branch, origin);
 108                src_data->head_status |= 2;
 109        } else {
 110                origin = src;
 111                string_list_append(&src_data->generic, line);
 112                src_data->head_status |= 2;
 113        }
 114
 115        if (!strcmp(".", src) || !strcmp(src, origin)) {
 116                int len = strlen(origin);
 117                if (origin[0] == '\'' && origin[len - 1] == '\'')
 118                        origin = xmemdupz(origin + 1, len - 2);
 119        } else {
 120                char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);
 121                sprintf(new_origin, "%s of %s", origin, src);
 122                origin = new_origin;
 123        }
 124        string_list_append(&origins, origin)->util = sha1;
 125        return 0;
 126}
 127
 128static void print_joined(const char *singular, const char *plural,
 129                struct string_list *list, struct strbuf *out)
 130{
 131        if (list->nr == 0)
 132                return;
 133        if (list->nr == 1) {
 134                strbuf_addf(out, "%s%s", singular, list->items[0].string);
 135        } else {
 136                int i;
 137                strbuf_addstr(out, plural);
 138                for (i = 0; i < list->nr - 1; i++)
 139                        strbuf_addf(out, "%s%s", i > 0 ? ", " : "",
 140                                    list->items[i].string);
 141                strbuf_addf(out, " and %s", list->items[list->nr - 1].string);
 142        }
 143}
 144
 145static void shortlog(const char *name, unsigned char *sha1,
 146                struct commit *head, struct rev_info *rev, int limit,
 147                struct strbuf *out)
 148{
 149        int i, count = 0;
 150        struct commit *commit;
 151        struct object *branch;
 152        struct string_list subjects = { NULL, 0, 0, 1 };
 153        int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;
 154        struct strbuf sb = STRBUF_INIT;
 155
 156        branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);
 157        if (!branch || branch->type != OBJ_COMMIT)
 158                return;
 159
 160        setup_revisions(0, NULL, rev, NULL);
 161        rev->ignore_merges = 1;
 162        add_pending_object(rev, branch, name);
 163        add_pending_object(rev, &head->object, "^HEAD");
 164        head->object.flags |= UNINTERESTING;
 165        if (prepare_revision_walk(rev))
 166                die("revision walk setup failed");
 167        while ((commit = get_revision(rev)) != NULL) {
 168                struct pretty_print_context ctx = {0};
 169
 170                /* ignore merges */
 171                if (commit->parents && commit->parents->next)
 172                        continue;
 173
 174                count++;
 175                if (subjects.nr > limit)
 176                        continue;
 177
 178                format_commit_message(commit, "%s", &sb, &ctx);
 179                strbuf_ltrim(&sb);
 180
 181                if (!sb.len)
 182                        string_list_append(&subjects,
 183                                           sha1_to_hex(commit->object.sha1));
 184                else
 185                        string_list_append(&subjects, strbuf_detach(&sb, NULL));
 186        }
 187
 188        if (count > limit)
 189                strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);
 190        else
 191                strbuf_addf(out, "\n* %s:\n", name);
 192
 193        for (i = 0; i < subjects.nr; i++)
 194                if (i >= limit)
 195                        strbuf_addf(out, "  ...\n");
 196                else
 197                        strbuf_addf(out, "  %s\n", subjects.items[i].string);
 198
 199        clear_commit_marks((struct commit *)branch, flags);
 200        clear_commit_marks(head, flags);
 201        free_commit_list(rev->commits);
 202        rev->commits = NULL;
 203        rev->pending.nr = 0;
 204
 205        string_list_clear(&subjects, 0);
 206}
 207
 208static void do_fmt_merge_msg_title(struct strbuf *out,
 209        const char *current_branch) {
 210        int i = 0;
 211        char *sep = "";
 212
 213        strbuf_addstr(out, "Merge ");
 214        for (i = 0; i < srcs.nr; i++) {
 215                struct src_data *src_data = srcs.items[i].util;
 216                const char *subsep = "";
 217
 218                strbuf_addstr(out, sep);
 219                sep = "; ";
 220
 221                if (src_data->head_status == 1) {
 222                        strbuf_addstr(out, srcs.items[i].string);
 223                        continue;
 224                }
 225                if (src_data->head_status == 3) {
 226                        subsep = ", ";
 227                        strbuf_addstr(out, "HEAD");
 228                }
 229                if (src_data->branch.nr) {
 230                        strbuf_addstr(out, subsep);
 231                        subsep = ", ";
 232                        print_joined("branch ", "branches ", &src_data->branch,
 233                                        out);
 234                }
 235                if (src_data->r_branch.nr) {
 236                        strbuf_addstr(out, subsep);
 237                        subsep = ", ";
 238                        print_joined("remote branch ", "remote branches ",
 239                                        &src_data->r_branch, out);
 240                }
 241                if (src_data->tag.nr) {
 242                        strbuf_addstr(out, subsep);
 243                        subsep = ", ";
 244                        print_joined("tag ", "tags ", &src_data->tag, out);
 245                }
 246                if (src_data->generic.nr) {
 247                        strbuf_addstr(out, subsep);
 248                        print_joined("commit ", "commits ", &src_data->generic,
 249                                        out);
 250                }
 251                if (strcmp(".", srcs.items[i].string))
 252                        strbuf_addf(out, " of %s", srcs.items[i].string);
 253        }
 254
 255        if (!strcmp("master", current_branch))
 256                strbuf_addch(out, '\n');
 257        else
 258                strbuf_addf(out, " into %s\n", current_branch);
 259}
 260
 261static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
 262        struct strbuf *out, int shortlog_len) {
 263        int i = 0, pos = 0;
 264        unsigned char head_sha1[20];
 265        const char *current_branch;
 266
 267        /* get current branch */
 268        current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);
 269        if (!current_branch)
 270                die("No current branch");
 271        if (!prefixcmp(current_branch, "refs/heads/"))
 272                current_branch += 11;
 273
 274        /* get a line */
 275        while (pos < in->len) {
 276                int len;
 277                char *newline, *p = in->buf + pos;
 278
 279                newline = strchr(p, '\n');
 280                len = newline ? newline - p : strlen(p);
 281                pos += len + !!newline;
 282                i++;
 283                p[len] = 0;
 284                if (handle_line(p))
 285                        die ("Error in line %d: %.*s", i, len, p);
 286        }
 287
 288        if (!srcs.nr)
 289                return 0;
 290
 291        if (merge_title)
 292                do_fmt_merge_msg_title(out, current_branch);
 293
 294        if (shortlog_len) {
 295                struct commit *head;
 296                struct rev_info rev;
 297
 298                head = lookup_commit(head_sha1);
 299                init_revisions(&rev, NULL);
 300                rev.commit_format = CMIT_FMT_ONELINE;
 301                rev.ignore_merges = 1;
 302                rev.limited = 1;
 303
 304                if (suffixcmp(out->buf, "\n"))
 305                        strbuf_addch(out, '\n');
 306
 307                for (i = 0; i < origins.nr; i++)
 308                        shortlog(origins.items[i].string, origins.items[i].util,
 309                                        head, &rev, shortlog_len, out);
 310        }
 311        return 0;
 312}
 313
 314int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
 315                  int merge_title, int shortlog_len) {
 316        return do_fmt_merge_msg(merge_title, in, out, shortlog_len);
 317}
 318
 319int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
 320{
 321        const char *inpath = NULL;
 322        const char *message = NULL;
 323        struct option options[] = {
 324                { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
 325                  "populate log with at most <n> entries from shortlog",
 326                  PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
 327                { OPTION_INTEGER, 0, "summary", &shortlog_len, "n",
 328                  "alias for --log (deprecated)",
 329                  PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL,
 330                  DEFAULT_MERGE_LOG_LEN },
 331                OPT_STRING('m', "message", &message, "text",
 332                        "use <text> as start of message"),
 333                OPT_FILENAME('F', "file", &inpath, "file to read from"),
 334                OPT_END()
 335        };
 336
 337        FILE *in = stdin;
 338        struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;
 339        int ret;
 340
 341        git_config(fmt_merge_msg_config, NULL);
 342        argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,
 343                             0);
 344        if (argc > 0)
 345                usage_with_options(fmt_merge_msg_usage, options);
 346        if (message && !shortlog_len) {
 347                char nl = '\n';
 348                write_in_full(STDOUT_FILENO, message, strlen(message));
 349                write_in_full(STDOUT_FILENO, &nl, 1);
 350                return 0;
 351        }
 352        if (shortlog_len < 0)
 353                die("Negative --log=%d", shortlog_len);
 354
 355        if (inpath && strcmp(inpath, "-")) {
 356                in = fopen(inpath, "r");
 357                if (!in)
 358                        die_errno("cannot open '%s'", inpath);
 359        }
 360
 361        if (strbuf_read(&input, fileno(in), 0) < 0)
 362                die_errno("could not read input file");
 363
 364        if (message)
 365                strbuf_addstr(&output, message);
 366        ret = fmt_merge_msg(&input, &output,
 367                            message ? 0 : 1,
 368                            shortlog_len);
 369
 370        if (ret)
 371                return ret;
 372        write_in_full(STDOUT_FILENO, output.buf, output.len);
 373        return 0;
 374}