From 00449f992b629f7f7884fb2cf46ff411a2a4f381 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 3 Jul 2006 17:18:43 +0200 Subject: [PATCH] Make git-fmt-merge-msg a builtin Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Makefile | 7 +- builtin-fmt-merge-msg.c | 355 ++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git-fmt-merge-msg.perl | 173 ----------------------- git.c | 3 +- 5 files changed, 362 insertions(+), 177 deletions(-) create mode 100644 builtin-fmt-merge-msg.c delete mode 100755 git-fmt-merge-msg.perl diff --git a/Makefile b/Makefile index 76abcc473b..a78d05ddaa 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \ + git-shortlog.perl git-rerere.perl \ git-annotate.perl git-cvsserver.perl \ git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \ git-send-email.perl @@ -173,7 +173,8 @@ BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \ git-read-tree$X git-commit-tree$X git-write-tree$X \ git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ - git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X + git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ + git-fmt-merge-msg$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -229,7 +230,7 @@ BUILTIN_OBJS = \ builtin-apply.o builtin-show-branch.o builtin-diff-files.o \ builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ - builtin-update-ref.o + builtin-update-ref.o builtin-fmt-merge-msg.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c new file mode 100644 index 0000000000..65274824d3 --- /dev/null +++ b/builtin-fmt-merge-msg.c @@ -0,0 +1,355 @@ +#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 ]"; + +static int merge_summary = 0; + +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 = realloc(list->list, sizeof(char *) * list->alloc); + list->payload = realloc(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]); + if (list->payload[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; + + if (len < 43 || line[40] != '\t') + return 1; + + if (!strncmp(line + 41, "not-for-merge", 13)) + 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; + } else + src = "HEAD"; + + i = find_in_list(&srcs, src); + if (i < 0) { + i = srcs.nr; + append_to_list(&srcs, strdup(src), + xcalloc(1, sizeof(struct src_data))); + } + src_data = srcs.payload[i]; + + if (!strncmp(line, "branch ", 7)) { + origin = strdup(line + 7); + append_to_list(&src_data->branch, origin, NULL); + src_data->head_status |= 2; + } else if (!strncmp(line, "tag ", 4)) { + origin = line; + append_to_list(&src_data->tag, strdup(origin + 4), NULL); + src_data->head_status |= 2; + } else if (!strncmp(line, "remote branch ", 14)) { + origin = strdup(line + 14); + append_to_list(&src_data->r_branch, origin, NULL); + src_data->head_status |= 2; + } else if (!strcmp(line, "HEAD")) { + origin = strdup(src); + src_data->head_status |= 1; + } else { + origin = strdup(src); + append_to_list(&src_data->generic, strdup(line), NULL); + src_data->head_status |= 2; + } + + if (!strcmp(".", src) || !strcmp(src, origin)) { + int len = strlen(origin); + if (origin[0] == '\'' && origin[len - 1] == '\'') { + char *new_origin = malloc(len - 1); + memcpy(new_origin, origin + 1, len - 2); + new_origin[len - 1] = 0; + origin = new_origin; + } else + origin = strdup(origin); + } else { + char *new_origin = malloc(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 | TREECHANGE | SEEN | SHOWN | ADDED; + + branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); + if (!branch || branch->type != TYPE_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, strdup(sha1_to_hex( + commit->object.sha1)), + NULL); + continue; + } + + bol += 2; + eol = strchr(bol, '\n'); + + if (eol) { + int len = eol - bol; + oneline = malloc(len + 1); + memcpy(oneline, bol, len); + oneline[len] = 0; + } else + oneline = strdup(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, char **argv, char **envp) +{ + int limit = 20, i = 0; + char line[1024]; + FILE *in = stdin; + const char *sep = ""; + unsigned char head_sha1[20]; + const char *head, *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 < 2) + die ("Which file?"); + if (!strcmp(argv[2], "-")) + in = stdin; + else { + fclose(in); + in = fopen(argv[2], "r"); + } + argc--; argv++; + } else + break; + argc--; argv++; + } + + if (argc > 1) + usage(fmt_merge_msg_usage); + + /* get current branch */ + head = strdup(git_path("HEAD")); + current_branch = resolve_ref(head, head_sha1, 1); + current_branch += strlen(head) - 4; + free((char *)head); + if (!strncmp(current_branch, "refs/heads/", 11)) + 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); + 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; +} + diff --git a/builtin.h b/builtin.h index f12d5e68f6..d9e5483bd5 100644 --- a/builtin.h +++ b/builtin.h @@ -49,6 +49,7 @@ extern int cmd_cat_file(int argc, const char **argv, char **envp); extern int cmd_rev_parse(int argc, const char **argv, char **envp); extern int cmd_update_index(int argc, const char **argv, char **envp); extern int cmd_update_ref(int argc, const char **argv, char **envp); +extern int cmd_fmt_merge_msg(int argc, const char **argv, char **envp); extern int cmd_write_tree(int argc, const char **argv, char **envp); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl deleted file mode 100755 index 5986e5414a..0000000000 --- a/git-fmt-merge-msg.perl +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/perl -w -# -# Copyright (c) 2005 Junio C Hamano -# -# Read .git/FETCH_HEAD and make a human readable merge message -# by grouping branches and tags together to form a single line. - -use strict; - -my @src; -my %src; -sub andjoin { - my ($label, $labels, $stuff) = @_; - my $l = scalar @$stuff; - my $m = ''; - if ($l == 0) { - return (); - } - if ($l == 1) { - $m = "$label$stuff->[0]"; - } - else { - $m = ("$labels" . - join (', ', @{$stuff}[0..$l-2]) . - " and $stuff->[-1]"); - } - return ($m); -} - -sub repoconfig { - my ($val) = qx{git-repo-config --get merge.summary}; - return $val; -} - -sub current_branch { - my ($bra) = qx{git-symbolic-ref HEAD}; - chomp($bra); - $bra =~ s|^refs/heads/||; - if ($bra ne 'master') { - $bra = " into $bra"; - } else { - $bra = ""; - } - return $bra; -} - -sub shortlog { - my ($tip) = @_; - my @result; - foreach ( qx{git-log --no-merges --topo-order --pretty=oneline $tip ^HEAD} ) { - s/^[0-9a-f]{40}\s+//; - push @result, $_; - } - die "git-log failed\n" if $?; - return @result; -} - -my @origin = (); -while (<>) { - my ($bname, $tname, $gname, $src, $sha1, $origin); - chomp; - s/^([0-9a-f]*) //; - $sha1 = $1; - next if (/^not-for-merge/); - s/^ //; - if (s/ of (.*)$//) { - $src = $1; - } else { - # Pulling HEAD - $src = $_; - $_ = 'HEAD'; - } - if (! exists $src{$src}) { - push @src, $src; - $src{$src} = { - BRANCH => [], - TAG => [], - R_BRANCH => [], - GENERIC => [], - # &1 == has HEAD. - # &2 == has others. - HEAD_STATUS => 0, - }; - } - if (/^branch (.*)$/) { - $origin = $1; - push @{$src{$src}{BRANCH}}, $1; - $src{$src}{HEAD_STATUS} |= 2; - } - elsif (/^tag (.*)$/) { - $origin = $_; - push @{$src{$src}{TAG}}, $1; - $src{$src}{HEAD_STATUS} |= 2; - } - elsif (/^remote branch (.*)$/) { - $origin = $1; - push @{$src{$src}{R_BRANCH}}, $1; - $src{$src}{HEAD_STATUS} |= 2; - } - elsif (/^HEAD$/) { - $origin = $src; - $src{$src}{HEAD_STATUS} |= 1; - } - else { - push @{$src{$src}{GENERIC}}, $_; - $src{$src}{HEAD_STATUS} |= 2; - $origin = $src; - } - if ($src eq '.' || $src eq $origin) { - $origin =~ s/^'(.*)'$/$1/; - push @origin, [$sha1, "$origin"]; - } - else { - push @origin, [$sha1, "$origin of $src"]; - } -} - -my @msg; -for my $src (@src) { - if ($src{$src}{HEAD_STATUS} == 1) { - # Only HEAD is fetched, nothing else. - push @msg, $src; - next; - } - my @this; - if ($src{$src}{HEAD_STATUS} == 3) { - # HEAD is fetched among others. - push @this, andjoin('', '', ['HEAD']); - } - push @this, andjoin("branch ", "branches ", - $src{$src}{BRANCH}); - push @this, andjoin("remote branch ", "remote branches ", - $src{$src}{R_BRANCH}); - push @this, andjoin("tag ", "tags ", - $src{$src}{TAG}); - push @this, andjoin("commit ", "commits ", - $src{$src}{GENERIC}); - my $this = join(', ', @this); - if ($src ne '.') { - $this .= " of $src"; - } - push @msg, $this; -} - -my $into = current_branch(); - -print "Merge ", join("; ", @msg), $into, "\n"; - -if (!repoconfig) { - exit(0); -} - -# We limit the merge message to the latst 20 or so per each branch. -my $limit = 20; - -for (@origin) { - my ($sha1, $name) = @$_; - my @log = shortlog($sha1); - if ($limit + 1 <= @log) { - print "\n* $name: (" . scalar(@log) . " commits)\n"; - } - else { - print "\n* $name:\n"; - } - my $cnt = 0; - for my $log (@log) { - if ($limit < ++$cnt) { - print " ...\n"; - last; - } - print " $log"; - } -} diff --git a/git.c b/git.c index ca8961f073..256730112e 100644 --- a/git.c +++ b/git.c @@ -187,7 +187,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "mailinfo", cmd_mailinfo }, { "stripspace", cmd_stripspace }, { "update-index", cmd_update_index }, - { "update-ref", cmd_update_ref } + { "update-ref", cmd_update_ref }, + { "fmt-merge-msg", cmd_fmt_merge_msg } }; int i; -- 2.11.4.GIT