1#include "builtin.h"2#include "cache.h"3#include "commit.h"4#include "diff.h"5#include "revision.h"6#include "tag.h"78static const char * const fmt_merge_msg_usage[] = {9"git fmt-merge-msg [--log|--no-log] [--file <file>]",10NULL11};1213static int merge_summary;1415static int fmt_merge_msg_config(const char *key, const char *value, void *cb)16{17static int found_merge_log = 0;18if (!strcmp("merge.log", key)) {19found_merge_log = 1;20merge_summary = git_config_bool(key, value);21}22if (!found_merge_log && !strcmp("merge.summary", key))23merge_summary = git_config_bool(key, value);24return 0;25}2627struct list {28char **list;29void **payload;30unsigned nr, alloc;31};3233static void append_to_list(struct list *list, char *value, void *payload)34{35if (list->nr == list->alloc) {36list->alloc += 32;37list->list = xrealloc(list->list, sizeof(char *) * list->alloc);38list->payload = xrealloc(list->payload,39sizeof(char *) * list->alloc);40}41list->payload[list->nr] = payload;42list->list[list->nr++] = value;43}4445static int find_in_list(struct list *list, char *value)46{47int i;4849for (i = 0; i < list->nr; i++)50if (!strcmp(list->list[i], value))51return i;5253return -1;54}5556static void free_list(struct list *list)57{58int i;5960if (list->alloc == 0)61return;6263for (i = 0; i < list->nr; i++) {64free(list->list[i]);65free(list->payload[i]);66}67free(list->list);68free(list->payload);69list->nr = list->alloc = 0;70}7172struct src_data {73struct list branch, tag, r_branch, generic;74int head_status;75};7677static struct list srcs = { NULL, NULL, 0, 0};78static struct list origins = { NULL, NULL, 0, 0};7980static int handle_line(char *line)81{82int i, len = strlen(line);83unsigned char *sha1;84char *src, *origin;85struct src_data *src_data;86int pulling_head = 0;8788if (len < 43 || line[40] != '\t')89return 1;9091if (!prefixcmp(line + 41, "not-for-merge"))92return 0;9394if (line[41] != '\t')95return 2;9697line[40] = 0;98sha1 = xmalloc(20);99i = get_sha1(line, sha1);100line[40] = '\t';101if (i)102return 3;103104if (line[len - 1] == '\n')105line[len - 1] = 0;106line += 42;107108src = strstr(line, " of ");109if (src) {110*src = 0;111src += 4;112pulling_head = 0;113} else {114src = line;115pulling_head = 1;116}117118i = find_in_list(&srcs, src);119if (i < 0) {120i = srcs.nr;121append_to_list(&srcs, xstrdup(src),122xcalloc(1, sizeof(struct src_data)));123}124src_data = srcs.payload[i];125126if (pulling_head) {127origin = xstrdup(src);128src_data->head_status |= 1;129} else if (!prefixcmp(line, "branch ")) {130origin = xstrdup(line + 7);131append_to_list(&src_data->branch, origin, NULL);132src_data->head_status |= 2;133} else if (!prefixcmp(line, "tag ")) {134origin = line;135append_to_list(&src_data->tag, xstrdup(origin + 4), NULL);136src_data->head_status |= 2;137} else if (!prefixcmp(line, "remote branch ")) {138origin = xstrdup(line + 14);139append_to_list(&src_data->r_branch, origin, NULL);140src_data->head_status |= 2;141} else {142origin = xstrdup(src);143append_to_list(&src_data->generic, xstrdup(line), NULL);144src_data->head_status |= 2;145}146147if (!strcmp(".", src) || !strcmp(src, origin)) {148int len = strlen(origin);149if (origin[0] == '\'' && origin[len - 1] == '\'') {150origin = xmemdupz(origin + 1, len - 2);151} else {152origin = xstrdup(origin);153}154} else {155char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5);156sprintf(new_origin, "%s of %s", origin, src);157origin = new_origin;158}159append_to_list(&origins, origin, sha1);160return 0;161}162163static void print_joined(const char *singular, const char *plural,164struct list *list, struct strbuf *out)165{166if (list->nr == 0)167return;168if (list->nr == 1) {169strbuf_addf(out, "%s%s", singular, list->list[0]);170} else {171int i;172strbuf_addstr(out, plural);173for (i = 0; i < list->nr - 1; i++)174strbuf_addf(out, "%s%s", i > 0 ? ", " : "", list->list[i]);175strbuf_addf(out, " and %s", list->list[list->nr - 1]);176}177}178179static void shortlog(const char *name, unsigned char *sha1,180struct commit *head, struct rev_info *rev, int limit,181struct strbuf *out)182{183int i, count = 0;184struct commit *commit;185struct object *branch;186struct list subjects = { NULL, NULL, 0, 0 };187int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED;188189branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40);190if (!branch || branch->type != OBJ_COMMIT)191return;192193setup_revisions(0, NULL, rev, NULL);194rev->ignore_merges = 1;195add_pending_object(rev, branch, name);196add_pending_object(rev, &head->object, "^HEAD");197head->object.flags |= UNINTERESTING;198if (prepare_revision_walk(rev))199die("revision walk setup failed");200while ((commit = get_revision(rev)) != NULL) {201char *oneline, *bol, *eol;202203/* ignore merges */204if (commit->parents && commit->parents->next)205continue;206207count++;208if (subjects.nr > limit)209continue;210211bol = strstr(commit->buffer, "\n\n");212if (bol) {213unsigned char c;214do {215c = *++bol;216} while (isspace(c));217if (!c)218bol = NULL;219}220221if (!bol) {222append_to_list(&subjects, xstrdup(sha1_to_hex(223commit->object.sha1)),224NULL);225continue;226}227228eol = strchr(bol, '\n');229if (eol) {230oneline = xmemdupz(bol, eol - bol);231} else {232oneline = xstrdup(bol);233}234append_to_list(&subjects, oneline, NULL);235}236237if (count > limit)238strbuf_addf(out, "\n* %s: (%d commits)\n", name, count);239else240strbuf_addf(out, "\n* %s:\n", name);241242for (i = 0; i < subjects.nr; i++)243if (i >= limit)244strbuf_addf(out, " ...\n");245else246strbuf_addf(out, " %s\n", subjects.list[i]);247248clear_commit_marks((struct commit *)branch, flags);249clear_commit_marks(head, flags);250free_commit_list(rev->commits);251rev->commits = NULL;252rev->pending.nr = 0;253254free_list(&subjects);255}256257int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {258int limit = 20, i = 0, pos = 0;259char *sep = "";260unsigned char head_sha1[20];261const char *current_branch;262263/* get current branch */264current_branch = resolve_ref("HEAD", head_sha1, 1, NULL);265if (!current_branch)266die("No current branch");267if (!prefixcmp(current_branch, "refs/heads/"))268current_branch += 11;269270/* get a line */271while (pos < in->len) {272int len;273char *newline, *p = in->buf + pos;274275newline = strchr(p, '\n');276len = newline ? newline - p : strlen(p);277pos += len + !!newline;278i++;279p[len] = 0;280if (handle_line(p))281die ("Error in line %d: %.*s", i, len, p);282}283284strbuf_addstr(out, "Merge ");285for (i = 0; i < srcs.nr; i++) {286struct src_data *src_data = srcs.payload[i];287const char *subsep = "";288289strbuf_addstr(out, sep);290sep = "; ";291292if (src_data->head_status == 1) {293strbuf_addstr(out, srcs.list[i]);294continue;295}296if (src_data->head_status == 3) {297subsep = ", ";298strbuf_addstr(out, "HEAD");299}300if (src_data->branch.nr) {301strbuf_addstr(out, subsep);302subsep = ", ";303print_joined("branch ", "branches ", &src_data->branch,304out);305}306if (src_data->r_branch.nr) {307strbuf_addstr(out, subsep);308subsep = ", ";309print_joined("remote branch ", "remote branches ",310&src_data->r_branch, out);311}312if (src_data->tag.nr) {313strbuf_addstr(out, subsep);314subsep = ", ";315print_joined("tag ", "tags ", &src_data->tag, out);316}317if (src_data->generic.nr) {318strbuf_addstr(out, subsep);319print_joined("commit ", "commits ", &src_data->generic,320out);321}322if (strcmp(".", srcs.list[i]))323strbuf_addf(out, " of %s", srcs.list[i]);324}325326if (!strcmp("master", current_branch))327strbuf_addch(out, '\n');328else329strbuf_addf(out, " into %s\n", current_branch);330331if (merge_summary) {332struct commit *head;333struct rev_info rev;334335head = lookup_commit(head_sha1);336init_revisions(&rev, NULL);337rev.commit_format = CMIT_FMT_ONELINE;338rev.ignore_merges = 1;339rev.limited = 1;340341for (i = 0; i < origins.nr; i++)342shortlog(origins.list[i], origins.payload[i],343head, &rev, limit, out);344}345return 0;346}347348int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)349{350const char *inpath = NULL;351struct option options[] = {352OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"),353OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"),354OPT_FILENAME('F', "file", &inpath, "file to read from"),355OPT_END()356};357358FILE *in = stdin;359struct strbuf input = STRBUF_INIT, output = STRBUF_INIT;360int ret;361362git_config(fmt_merge_msg_config, NULL);363argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage,3640);365if (argc > 0)366usage_with_options(fmt_merge_msg_usage, options);367368if (inpath && strcmp(inpath, "-")) {369in = fopen(inpath, "r");370if (!in)371die("cannot open %s", inpath);372}373374if (strbuf_read(&input, fileno(in), 0) < 0)375die_errno("could not read input file");376ret = fmt_merge_msg(merge_summary, &input, &output);377if (ret)378return ret;379write_in_full(STDOUT_FILENO, output.buf, output.len);380return 0;381}