git-fmt-merge-msg.perlon commit git-merge-tree: generalize the "traverse <n> trees in sync" functionality (164dcb9)
   1#!/usr/bin/perl -w
   2#
   3# Copyright (c) 2005 Junio C Hamano
   4#
   5# Read .git/FETCH_HEAD and make a human readable merge message
   6# by grouping branches and tags together to form a single line.
   7
   8use strict;
   9
  10my @src;
  11my %src;
  12sub andjoin {
  13        my ($label, $labels, $stuff) = @_;
  14        my $l = scalar @$stuff;
  15        my $m = '';
  16        if ($l == 0) {
  17                return ();
  18        }
  19        if ($l == 1) {
  20                $m = "$label$stuff->[0]";
  21        }
  22        else {
  23                $m = ("$labels" .
  24                      join (', ', @{$stuff}[0..$l-2]) .
  25                      " and $stuff->[-1]");
  26        }
  27        return ($m);
  28}
  29
  30sub repoconfig {
  31        my $fh;
  32        my $val;
  33        eval {
  34                open $fh, '-|', 'git-repo-config', '--get', 'merge.summary'
  35                    or die "$!";
  36                ($val) = <$fh>;
  37                close $fh;
  38        };
  39        return $val;
  40}
  41
  42sub mergebase {
  43        my ($other) = @_;
  44        my $fh;
  45        open $fh, '-|', 'git-merge-base', '--all', 'HEAD', $other or die "$!";
  46        my (@mb) = map { chomp; $_ } <$fh>;
  47        close $fh or die "$!";
  48        return @mb;
  49}
  50
  51sub shortlog {
  52        my ($tip, $limit, @base) = @_;
  53        my ($fh, @result);
  54        open $fh, '-|', ('git-log', "--max-count=$limit", '--topo-order',
  55                         '--pretty=oneline', $tip, map { "^$_" } @base)
  56            or die "$!";
  57        while (<$fh>) {
  58                s/^[0-9a-f]{40}\s+//;
  59                push @result, $_;
  60        }
  61        close $fh or die "$!";
  62        return @result;
  63}
  64
  65my @origin = ();
  66while (<>) {
  67        my ($bname, $tname, $gname, $src, $sha1, $origin);
  68        chomp;
  69        s/^([0-9a-f]*)  //;
  70        $sha1 = $1;
  71        next if (/^not-for-merge/);
  72        s/^     //;
  73        if (s/ of (.*)$//) {
  74                $src = $1;
  75        } else {
  76                # Pulling HEAD
  77                $src = $_;
  78                $_ = 'HEAD';
  79        }
  80        if (! exists $src{$src}) {
  81                push @src, $src;
  82                $src{$src} = {
  83                        BRANCH => [],
  84                        TAG => [],
  85                        GENERIC => [],
  86                        # &1 == has HEAD.
  87                        # &2 == has others.
  88                        HEAD_STATUS => 0,
  89                };
  90        }
  91        if (/^branch (.*)$/) {
  92                $origin = $1;
  93                push @{$src{$src}{BRANCH}}, $1;
  94                $src{$src}{HEAD_STATUS} |= 2;
  95        }
  96        elsif (/^tag (.*)$/) {
  97                $origin = $_;
  98                push @{$src{$src}{TAG}}, $1;
  99                $src{$src}{HEAD_STATUS} |= 2;
 100        }
 101        elsif (/^HEAD$/) {
 102                $origin = $src;
 103                $src{$src}{HEAD_STATUS} |= 1;
 104        }
 105        else {
 106                push @{$src{$src}{GENERIC}}, $_;
 107                $src{$src}{HEAD_STATUS} |= 2;
 108                $origin = $src;
 109        }
 110        if ($src eq '.' || $src eq $origin) {
 111                $origin =~ s/^'(.*)'$/$1/;
 112                push @origin, [$sha1, "$origin"];
 113        }
 114        else {
 115                push @origin, [$sha1, "$origin of $src"];
 116        }
 117}
 118
 119my @msg;
 120for my $src (@src) {
 121        if ($src{$src}{HEAD_STATUS} == 1) {
 122                # Only HEAD is fetched, nothing else.
 123                push @msg, $src;
 124                next;
 125        }
 126        my @this;
 127        if ($src{$src}{HEAD_STATUS} == 3) {
 128                # HEAD is fetched among others.
 129                push @this, andjoin('', '', ['HEAD']);
 130        }
 131        push @this, andjoin("branch ", "branches ",
 132                           $src{$src}{BRANCH});
 133        push @this, andjoin("tag ", "tags ",
 134                           $src{$src}{TAG});
 135        push @this, andjoin("commit ", "commits ",
 136                            $src{$src}{GENERIC});
 137        my $this = join(', ', @this);
 138        if ($src ne '.') {
 139                $this .= " of $src";
 140        }
 141        push @msg, $this;
 142}
 143print "Merge ", join("; ", @msg), "\n";
 144
 145if (!repoconfig) {
 146        exit(0);
 147}
 148
 149# We limit the merge message to the latst 20 or so per each branch.
 150my $limit = 20;
 151
 152for (@origin) {
 153        my ($sha1, $name) = @$_;
 154        my @mb = mergebase($sha1);
 155        my @log = shortlog($sha1, $limit + 1, @mb);
 156        if ($limit + 1 <= @log) {
 157                print "\n* $name: (" . scalar(@log) . " commits)\n";
 158        }
 159        else {
 160                print "\n* $name:\n";
 161        }
 162        my $cnt = 0;
 163        for my $log (@log) {
 164                if ($limit < ++$cnt) {
 165                        print "  ...\n";
 166                        last;
 167                }
 168                print "  $log";
 169        }
 170}