5
5
#
6
6
# Display all commits on active branches, merging together commits from
7
7
# different branches that occur close together in time and with identical
8
- # log messages.
8
+ # log messages. Commits are annotated with branch and release info thus:
9
+ # Branch: REL8_3_STABLE Release: REL8_3_2 [92c3a8004] 2008-03-29 00:15:37 +0000
10
+ # This shows that the commit on REL8_3_STABLE was released in 8.3.2.
11
+ # Commits on master will usually instead have notes like
12
+ # Branch: master Release: REL8_4_BR [6fc9d4272] 2008-03-29 00:15:28 +0000
13
+ # showing that this commit is ancestral to release branches 8.4 and later.
14
+ # If no Release: marker appears, the commit hasn't yet made it into any
15
+ # release.
9
16
#
10
17
# Most of the time, matchable commits occur in the same order on all branches,
11
18
# and we print them out in that order. However, if commit A occurs before
@@ -26,36 +33,84 @@ require Time::Local;
26
33
require Getopt::Long;
27
34
require IPC::Open2;
28
35
29
- # Adjust this list when the set of active branches changes.
36
+ # Adjust this list when the set of interesting branches changes.
37
+ # (We could get this from "git branches", but not worth the trouble.)
38
+ # NB: master must be first!
30
39
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) ;
40
+ REL8_2_STABLE REL8_1_STABLE REL8_0_STABLE REL7_4_STABLE REL7_3_STABLE
41
+ REL7_2_STABLE REL7_1_STABLE REL7_0_PATCHES REL6_5_PATCHES REL6_4) ;
32
42
33
43
# Might want to make this parameter user-settable.
34
44
my $timestamp_slop = 600;
35
45
46
+ my $post_date = 0;
36
47
my $since ;
37
- Getopt::Long::GetOptions(' since=s' => \$since ) || usage();
48
+ Getopt::Long::GetOptions(' post-date' => \$post_date ,
49
+ ' since=s' => \$since ) || usage();
38
50
usage() if @ARGV ;
39
51
40
52
my @git = qw( git log --date=iso) ;
41
53
push @git , ' --since=' . $since if defined $since ;
42
54
55
+ # Collect the release tag data
56
+ my %rel_tags ;
57
+
58
+ {
59
+ my $cmd = " git for-each-ref refs/tags" ;
60
+ my $pid = IPC::Open2::open2(my $git_out , my $git_in , $cmd )
61
+ || die " can't run $cmd : $! " ;
62
+ while (my $line = <$git_out >) {
63
+ if ($line =~ m | ^([a-f0-9]+)\s +commit\s +refs/tags/(\S +)| ) {
64
+ my $commit = $1 ;
65
+ my $tag = $2 ;
66
+ if ($tag =~ / ^REL\d +_\d +$ / ||
67
+ $tag =~ / ^REL\d +_\d +_\d +$ / ) {
68
+ $rel_tags {$commit } = $tag ;
69
+ }
70
+ }
71
+ }
72
+ waitpid ($pid , 0);
73
+ my $child_exit_status = $? >> 8;
74
+ die " $cmd failed" if $child_exit_status != 0;
75
+ }
76
+
77
+ # Collect the commit data
43
78
my %all_commits ;
44
79
my %all_commits_by_branch ;
80
+ # This remembers where each branch sprouted from master. Note the values
81
+ # will be wrong if --since terminates the log listing before the branch
82
+ # sprouts; but in that case it doesn't matter since we also won't reach
83
+ # the part of master where it would matter.
84
+ my %sprout_tags ;
45
85
46
86
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 : $! " ;
87
+ my @cmd = @git ;
88
+ if ($branch eq " master" ) {
89
+ push @cmd , " origin/$branch " ;
90
+ } else {
91
+ push @cmd , " --parents" ;
92
+ push @cmd , " master..origin/$branch " ;
93
+ }
94
+ my $pid = IPC::Open2::open2(my $git_out , my $git_in , @cmd )
95
+ || die " can't run @cmd : $! " ;
96
+ my $last_tag = undef ;
97
+ my $last_parent ;
50
98
my %commit ;
51
99
while (my $line = <$git_out >) {
52
- if ($line =~ / ^commit\s +(.* )/ ) {
100
+ if ($line =~ / ^commit\s +(\S + )/ ) {
53
101
push_commit(\%commit ) if %commit ;
102
+ $last_tag = $rel_tags {$1 } if defined $rel_tags {$1 };
54
103
%commit = (
55
104
' branch' => $branch ,
56
105
' commit' => $1 ,
106
+ ' last_tag' => $last_tag ,
57
107
' message' => ' ' ,
58
108
);
109
+ if ($line =~ / ^commit\s +\S +\s +(\S +)/ ) {
110
+ $last_parent = $1 ;
111
+ } else {
112
+ $last_parent = undef ;
113
+ }
59
114
}
60
115
elsif ($line =~ / ^Author:\s +(.*)/ ) {
61
116
$commit {' author' } = $1 ;
@@ -68,9 +123,43 @@ for my $branch (@BRANCHES) {
68
123
}
69
124
}
70
125
push_commit(\%commit ) if %commit ;
126
+ $sprout_tags {$last_parent } = $branch if defined $last_parent ;
71
127
waitpid ($pid , 0);
72
128
my $child_exit_status = $? >> 8;
73
- die " @git origin/$branch failed" if $child_exit_status != 0;
129
+ die " @cmd failed" if $child_exit_status != 0;
130
+ }
131
+
132
+ # Run through the master branch and apply tags. We already tagged the other
133
+ # branches, but master needs a separate pass after we've acquired the
134
+ # sprout_tags data. Also, in post-date mode we need to add phony entries
135
+ # for branches that sprouted after a particular master commit was made.
136
+ {
137
+ my $last_tag = undef ;
138
+ my %sprouted_branches ;
139
+ for my $cc (@{$all_commits_by_branch {' master' }}) {
140
+ my $commit = $cc -> {' commit' };
141
+ my $c = $cc -> {' commits' }-> [0];
142
+ $last_tag = $rel_tags {$commit } if defined $rel_tags {$commit };
143
+ if (defined $sprout_tags {$commit }) {
144
+ $last_tag = $sprout_tags {$commit };
145
+ # normalize branch names for making sprout tags
146
+ $last_tag =~ s / ^(REL\d +_\d +).*/ $1_BR/ ;
147
+ }
148
+ $c -> {' last_tag' } = $last_tag ;
149
+ if ($post_date ) {
150
+ if (defined $sprout_tags {$commit }) {
151
+ $sprouted_branches {$sprout_tags {$commit }} = 1;
152
+ }
153
+ # insert new commits between master and any other commits
154
+ my @new_commits = ( shift @{$cc -> {' commits' }} );
155
+ for my $branch (reverse sort keys %sprouted_branches ) {
156
+ my $ccopy = {%{$c }};
157
+ $ccopy -> {' branch' } = $branch ;
158
+ push @new_commits , $ccopy ;
159
+ }
160
+ $cc -> {' commits' } = [ @new_commits , @{$cc -> {' commits' }} ];
161
+ }
162
+ }
74
163
}
75
164
76
165
my %position ;
@@ -104,7 +193,14 @@ while (1) {
104
193
last if !defined $best_branch ;
105
194
my $winner =
106
195
$all_commits_by_branch {$best_branch }-> [$position {$best_branch }];
107
- print $winner -> {' header' };
196
+ printf " Author: %s \n " , $winner -> {' author' };
197
+ foreach my $c (@{$winner -> {' commits' }}) {
198
+ printf " Branch: %s " , $c -> {' branch' };
199
+ if (defined $c -> {' last_tag' }) {
200
+ printf " Release: %s " , $c -> {' last_tag' };
201
+ }
202
+ printf " [%s ] %s \n " , substr ($c -> {' commit' }, 0, 9), $c -> {' date' };
203
+ }
108
204
print " Commit-Order-Inversions: $best_inversions \n "
109
205
if $best_inversions != 0;
110
206
print " \n " ;
@@ -143,22 +239,22 @@ sub push_commit {
143
239
}
144
240
if (!defined $cc ) {
145
241
$cc = {
146
- ' header ' => sprintf ( " Author: %s \n " , $c -> {' author' }) ,
242
+ ' author ' => $c -> {' author' },
147
243
' message' => $c -> {' message' },
148
244
' commit' => $c -> {' commit' },
245
+ ' commits' => [],
149
246
' timestamp' => $ts
150
247
};
151
248
push @{$all_commits {$ht }}, $cc ;
152
- } elsif ($cc -> {' commit' } eq $c -> {' commit' }) {
153
- # If this is exactly the same commit we saw before on another
154
- # branch, ignore it. Hence, a commit that's reachable from more
155
- # than one branch head will be reported only for the first
156
- # head it's reachable from. This will give the desired results
157
- # so long as @BRANCHES is ordered with master first.
158
- return ;
159
249
}
160
- $cc -> {' header' } .= sprintf " Branch: %s [%s ] %s \n " ,
161
- $c -> {' branch' }, substr ($c -> {' commit' }, 0, 9), $c -> {' date' };
250
+ # stash only the fields we'll need later
251
+ my $smallc = {
252
+ ' branch' => $c -> {' branch' },
253
+ ' commit' => $c -> {' commit' },
254
+ ' date' => $c -> {' date' },
255
+ ' last_tag' => $c -> {' last_tag' }
256
+ };
257
+ push @{$cc -> {' commits' }}, $smallc ;
162
258
push @{$all_commits_by_branch {$c -> {' branch' }}}, $cc ;
163
259
$cc -> {' branch_position' }{$c -> {' branch' }} =
164
260
-1+@{$all_commits_by_branch {$c -> {' branch' }}};
@@ -180,7 +276,9 @@ sub parse_datetime {
180
276
181
277
sub usage {
182
278
print STDERR <<EOM ;
183
- Usage: git_changelog [--since=SINCE]
279
+ Usage: git_changelog [--post-date/-p] [--since=SINCE]
280
+ --post-date Show branches made after a commit occurred
281
+ --since Print only commits dated since SINCE
184
282
EOM
185
283
exit 1;
186
284
}
0 commit comments