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
}