]> granicus.if.org Git - postgresql/blob - src/tools/git_changelog
Fix some more bugs in git_changelog.
[postgresql] / src / tools / git_changelog
1 #!/usr/bin/perl
2
3 #
4 # src/tools/git_changelog
5 #
6 # Display all commits on active branches, merging together commits from
7 # different branches that occur close together in time and with identical
8 # log messages.
9 #
10 # Most of the time, matchable commits occur in the same order on all branches,
11 # and we print them out in that order.  However, if commit A occurs before
12 # commit B on branch X and commit B occurs before commit A on branch Y, then
13 # there's no ordering which is consistent with both branches.
14 #
15 # When we encounter a situation where there's no single "best" commit to
16 # print next, we print the one that involves the least distortion of the
17 # commit order, summed across all branches.  In the event of a tie on the
18 # distortion measure (which is actually the common case: normally, the
19 # distortion is zero), we choose the commit with latest timestamp.  If
20 # that's a tie too, the commit from the newer branch prints first.
21 #
22
23 use strict;
24 use warnings;
25 require Time::Local;
26 require Getopt::Long;
27 require IPC::Open2;
28
29 # Adjust this list when the set of active branches changes.
30 my @BRANCHES = qw(master REL9_0_STABLE REL8_4_STABLE REL8_3_STABLE
31     REL8_2_STABLE REL8_1_STABLE REL8_0_STABLE REL7_4_STABLE);
32
33 # Might want to make this parameter user-settable.
34 my $timestamp_slop = 600;
35
36 my $since;
37 Getopt::Long::GetOptions('since=s' => \$since) || usage();
38 usage() if @ARGV;
39
40 my @git = qw(git log --date=iso);
41 push @git, '--since=' . $since if defined $since;
42
43 my %all_commits;
44 my %all_commits_by_branch;
45
46 for my $branch (@BRANCHES) {
47         my $pid =
48           IPC::Open2::open2(my $git_out, my $git_in, @git, "origin/$branch")
49               || die "can't run @git origin/$branch: $!";
50         my $commitnum = 0;
51         my %commit;
52         while (my $line = <$git_out>) {
53                 if ($line =~ /^commit\s+(.*)/) {
54                         push_commit(\%commit) if %commit;
55                         %commit = (
56                                 'branch' => $branch,
57                                 'commit' => $1,
58                                 'message' => '',
59                                 'commitnum' => $commitnum++,
60                         );
61                 }
62                 elsif ($line =~ /^Author:\s+(.*)/) {
63                         $commit{'author'} = $1;
64                 }
65                 elsif ($line =~ /^Date:\s+(.*)/) {
66                         $commit{'date'} = $1;
67                 }
68                 elsif ($line =~ /^\s\s/) {
69                         $commit{'message'} .= $line;
70                 }
71         }
72         push_commit(\%commit) if %commit;
73         waitpid($pid, 0);
74         my $child_exit_status = $? >> 8;
75         die "@git origin/$branch failed" if $child_exit_status != 0;
76 }
77
78 my %position;
79 for my $branch (@BRANCHES) {
80         $position{$branch} = 0;
81 }
82
83 while (1) {
84         my $best_branch;
85         my $best_inversions;
86         my $best_timestamp;
87         for my $branch (@BRANCHES) {
88                 my $leader = $all_commits_by_branch{$branch}->[$position{$branch}];
89                 next if !defined $leader;
90                 my $inversions = 0;
91                 for my $branch2 (@BRANCHES) {
92                         if (defined $leader->{'branch_position'}{$branch2}) {
93                                 $inversions += $leader->{'branch_position'}{$branch2}
94                                         - $position{$branch2};
95                         }
96                 }
97                 if (!defined $best_inversions ||
98                     $inversions < $best_inversions ||
99                     ($inversions == $best_inversions &&
100                      $leader->{'timestamp'} > $best_timestamp)) {
101                         $best_branch = $branch;
102                         $best_inversions = $inversions;
103                         $best_timestamp = $leader->{'timestamp'};
104                 }
105         }
106         last if !defined $best_branch;
107         my $winner =
108                 $all_commits_by_branch{$best_branch}->[$position{$best_branch}];
109         print $winner->{'header'};
110         print "Commit-Order-Inversions: $best_inversions\n"
111                 if $best_inversions != 0;
112         print "\n";
113         print $winner->{'message'};
114         print "\n";
115         $winner->{'done'} = 1;
116         for my $branch (@BRANCHES) {
117                 my $leader = $all_commits_by_branch{$branch}->[$position{$branch}];
118                 if (defined $leader && $leader->{'done'}) {
119                         ++$position{$branch};
120                         redo;
121                 }
122         }
123 }
124
125 sub push_commit {
126         my ($c) = @_;
127         my $ht = hash_commit($c);
128         my $ts = parse_datetime($c->{'date'});
129         my $cc;
130         for my $candidate (@{$all_commits{$ht}}) {
131                 if (abs($ts - $candidate->{'timestamp'}) < $timestamp_slop
132                         && !exists $candidate->{'branch_position'}{$c->{'branch'}})
133                 {
134                         $cc = $candidate;
135                         last;
136                 }
137         }
138         if (!defined $cc) {
139                 $cc = {
140                         'header' => sprintf("Author: %s\n", $c->{'author'}),
141                         'message' => $c->{'message'},
142                         'timestamp' => $ts
143                 };
144                 push @{$all_commits{$ht}}, $cc;
145         }
146         $cc->{'header'} .= sprintf "Branch: %s [%s] %s\n",
147                 $c->{'branch'}, substr($c->{'commit'}, 0, 9), $c->{'date'};
148         push @{$all_commits_by_branch{$c->{'branch'}}}, $cc;
149         $cc->{'branch_position'}{$c->{'branch'}} =
150                 -1+@{$all_commits_by_branch{$c->{'branch'}}};
151 }
152
153 sub hash_commit {
154         my ($c) = @_;
155         return $c->{'author'} . "\0" . $c->{'message'};
156 }
157
158 sub parse_datetime {
159         my ($dt) = @_;
160         $dt =~ /^(\d\d\d\d)-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)\s+([-+])(\d\d)(\d\d)$/;
161         my $gm = Time::Local::timegm($6, $5, $4, $3, $2-1, $1);
162         my $tzoffset = ($8 * 60 + $9) * 60;
163         $tzoffset = - $tzoffset if $7 eq '-';
164         return $gm - $tzoffset;
165 }
166
167 sub usage {
168         print STDERR <<EOM;
169 Usage: git_changelog [--since=SINCE]
170 EOM
171         exit 1;
172 }