Make git-fmt-merge-msg a builtin
authorJohannes Schindelin <Johannes.Schindelin@gmx.de>
Mon, 3 Jul 2006 15:18:43 +0000 (17:18 +0200)
committerJunio C Hamano <junkio@cox.net>
Tue, 4 Jul 2006 02:42:41 +0000 (19:42 -0700)
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
Makefile
builtin-fmt-merge-msg.c [new file with mode: 0644]
builtin.h
git-fmt-merge-msg.perl [deleted file]
git.c

index 76abcc473b409d45c27e8171d344e2dcfa1e699b..a78d05ddaa640ad50792c4fb8c78b957eb83400c 100644 (file)
--- 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 (file)
index 0000000..6527482
--- /dev/null
@@ -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 <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;
+}
+
index f12d5e68f6778fbf96c85b81d250cf7f230edf1d..d9e5483bd577733c241c9c166a43ce28ebe80328 100644 (file)
--- 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 (executable)
index 5986e54..0000000
+++ /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 ca8961f0732bacc4efdb3afe9abc2e829ff69ee5..256730112e56e19e5df70f2bef4e3efdb1a5e362 100644 (file)
--- 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;