diff options
Diffstat (limited to 'builtin-fmt-merge-msg.c')
-rw-r--r-- | builtin-fmt-merge-msg.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c new file mode 100644 index 0000000000..6163bd4975 --- /dev/null +++ b/builtin-fmt-merge-msg.c @@ -0,0 +1,354 @@ +#include "builtin.h" +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "tag.h" + +static const char *fmt_merge_msg_usage = + "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]"; + +static int merge_summary; + +static int fmt_merge_msg_config(const char *key, const char *value) +{ + if (!strcmp("merge.summary", key)) + merge_summary = git_config_bool(key, value); + return 0; +} + +struct list { + char **list; + void **payload; + unsigned nr, alloc; +}; + +static void append_to_list(struct list *list, char *value, void *payload) +{ + if (list->nr == list->alloc) { + list->alloc += 32; + list->list = xrealloc(list->list, sizeof(char *) * list->alloc); + list->payload = xrealloc(list->payload, + sizeof(char *) * list->alloc); + } + list->payload[list->nr] = payload; + list->list[list->nr++] = value; +} + +static int find_in_list(struct list *list, char *value) +{ + int i; + + for (i = 0; i < list->nr; i++) + if (!strcmp(list->list[i], value)) + return i; + + return -1; +} + +static void free_list(struct list *list) +{ + int i; + + if (list->alloc == 0) + return; + + for (i = 0; i < list->nr; i++) { + free(list->list[i]); + free(list->payload[i]); + } + free(list->list); + free(list->payload); + list->nr = list->alloc = 0; +} + +struct src_data { + struct list branch, tag, r_branch, generic; + int head_status; +}; + +static struct list srcs = { NULL, NULL, 0, 0}; +static struct list origins = { NULL, NULL, 0, 0}; + +static int handle_line(char *line) +{ + int i, len = strlen(line); + unsigned char *sha1; + char *src, *origin; + struct src_data *src_data; + int pulling_head = 0; + + if (len < 43 || line[40] != '\t') + return 1; + + if (!prefixcmp(line + 41, "not-for-merge")) + return 0; + + if (line[41] != '\t') + return 2; + + line[40] = 0; + sha1 = xmalloc(20); + i = get_sha1(line, sha1); + line[40] = '\t'; + if (i) + return 3; + + if (line[len - 1] == '\n') + line[len - 1] = 0; + line += 42; + + src = strstr(line, " of "); + if (src) { + *src = 0; + src += 4; + pulling_head = 0; + } else { + src = line; + pulling_head = 1; + } + + i = find_in_list(&srcs, src); + if (i < 0) { + i = srcs.nr; + append_to_list(&srcs, xstrdup(src), + xcalloc(1, sizeof(struct src_data))); + } + src_data = srcs.payload[i]; + + if (pulling_head) { + origin = xstrdup(src); + src_data->head_status |= 1; + } else if (!prefixcmp(line, "branch ")) { + origin = xstrdup(line + 7); + append_to_list(&src_data->branch, origin, NULL); + src_data->head_status |= 2; + } else if (!prefixcmp(line, "tag ")) { + origin = line; + append_to_list(&src_data->tag, xstrdup(origin + 4), NULL); + src_data->head_status |= 2; + } else if (!prefixcmp(line, "remote branch ")) { + origin = xstrdup(line + 14); + append_to_list(&src_data->r_branch, origin, NULL); + src_data->head_status |= 2; + } else { + origin = xstrdup(src); + append_to_list(&src_data->generic, xstrdup(line), NULL); + src_data->head_status |= 2; + } + + if (!strcmp(".", src) || !strcmp(src, origin)) { + int len = strlen(origin); + if (origin[0] == '\'' && origin[len - 1] == '\'') { + origin = xmemdupz(origin + 1, len - 2); + } else { + origin = xstrdup(origin); + } + } else { + char *new_origin = xmalloc(strlen(origin) + strlen(src) + 5); + sprintf(new_origin, "%s of %s", origin, src); + origin = new_origin; + } + append_to_list(&origins, origin, sha1); + return 0; +} + +static void print_joined(const char *singular, const char *plural, + struct list *list) +{ + if (list->nr == 0) + return; + if (list->nr == 1) { + printf("%s%s", singular, list->list[0]); + } else { + int i; + printf("%s", plural); + for (i = 0; i < list->nr - 1; i++) + printf("%s%s", i > 0 ? ", " : "", list->list[i]); + printf(" and %s", list->list[list->nr - 1]); + } +} + +static void shortlog(const char *name, unsigned char *sha1, + struct commit *head, struct rev_info *rev, int limit) +{ + int i, count = 0; + struct commit *commit; + struct object *branch; + struct list subjects = { NULL, NULL, 0, 0 }; + int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; + + branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); + if (!branch || branch->type != OBJ_COMMIT) + return; + + setup_revisions(0, NULL, rev, NULL); + rev->ignore_merges = 1; + add_pending_object(rev, branch, name); + add_pending_object(rev, &head->object, "^HEAD"); + head->object.flags |= UNINTERESTING; + prepare_revision_walk(rev); + while ((commit = get_revision(rev)) != NULL) { + char *oneline, *bol, *eol; + + /* ignore merges */ + if (commit->parents && commit->parents->next) + continue; + + count++; + if (subjects.nr > limit) + continue; + + bol = strstr(commit->buffer, "\n\n"); + if (!bol) { + append_to_list(&subjects, xstrdup(sha1_to_hex( + commit->object.sha1)), + NULL); + continue; + } + + bol += 2; + eol = strchr(bol, '\n'); + if (eol) { + oneline = xmemdupz(bol, eol - bol); + } else { + oneline = xstrdup(bol); + } + append_to_list(&subjects, oneline, NULL); + } + + if (count > limit) + printf("\n* %s: (%d commits)\n", name, count); + else + printf("\n* %s:\n", name); + + for (i = 0; i < subjects.nr; i++) + if (i >= limit) + printf(" ...\n"); + else + printf(" %s\n", subjects.list[i]); + + clear_commit_marks((struct commit *)branch, flags); + clear_commit_marks(head, flags); + free_commit_list(rev->commits); + rev->commits = NULL; + rev->pending.nr = 0; + + free_list(&subjects); +} + +int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) +{ + int limit = 20, i = 0; + char line[1024]; + FILE *in = stdin; + const char *sep = ""; + unsigned char head_sha1[20]; + const char *current_branch; + + git_config(fmt_merge_msg_config); + + while (argc > 1) { + if (!strcmp(argv[1], "--summary")) + merge_summary = 1; + else if (!strcmp(argv[1], "--no-summary")) + merge_summary = 0; + else if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--file")) { + if (argc < 3) + die ("Which file?"); + if (!strcmp(argv[2], "-")) + in = stdin; + else { + fclose(in); + in = fopen(argv[2], "r"); + if (!in) + die("cannot open %s", argv[2]); + } + argc--; argv++; + } else + break; + argc--; argv++; + } + + if (argc > 1) + usage(fmt_merge_msg_usage); + + /* get current branch */ + current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + if (!current_branch) + die("No current branch"); + if (!prefixcmp(current_branch, "refs/heads/")) + current_branch += 11; + + while (fgets(line, sizeof(line), in)) { + i++; + if (line[0] == 0) + continue; + if (handle_line(line)) + die ("Error in line %d: %s", i, line); + } + + printf("Merge "); + for (i = 0; i < srcs.nr; i++) { + struct src_data *src_data = srcs.payload[i]; + const char *subsep = ""; + + printf(sep); + sep = "; "; + + if (src_data->head_status == 1) { + printf(srcs.list[i]); + continue; + } + if (src_data->head_status == 3) { + subsep = ", "; + printf("HEAD"); + } + if (src_data->branch.nr) { + printf(subsep); + subsep = ", "; + print_joined("branch ", "branches ", &src_data->branch); + } + if (src_data->r_branch.nr) { + printf(subsep); + subsep = ", "; + print_joined("remote branch ", "remote branches ", + &src_data->r_branch); + } + if (src_data->tag.nr) { + printf(subsep); + subsep = ", "; + print_joined("tag ", "tags ", &src_data->tag); + } + if (src_data->generic.nr) { + printf(subsep); + print_joined("commit ", "commits ", &src_data->generic); + } + if (strcmp(".", srcs.list[i])) + printf(" of %s", srcs.list[i]); + } + + if (!strcmp("master", current_branch)) + putchar('\n'); + else + printf(" into %s\n", current_branch); + + if (merge_summary) { + struct commit *head; + struct rev_info rev; + + head = lookup_commit(head_sha1); + init_revisions(&rev, prefix); + rev.commit_format = CMIT_FMT_ONELINE; + rev.ignore_merges = 1; + rev.limited = 1; + + for (i = 0; i < origins.nr; i++) + shortlog(origins.list[i], origins.payload[i], + head, &rev, limit); + } + + /* No cleanup yet; is standalone anyway */ + + return 0; +} |