55#
66# Display all commits on active branches, merging together commits from
77# 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.
916#
1017# Most of the time, matchable commits occur in the same order on all branches,
1118# and we print them out in that order. However, if commit A occurs before
@@ -26,36 +33,84 @@ require Time::Local;
2633require Getopt::Long;
2734require IPC::Open2;
2835
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!
3039my @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) ;
3242
3343# Might want to make this parameter user-settable.
3444my $timestamp_slop = 600;
3545
46+ my $post_date = 0;
3647my $since ;
37- Getopt::Long::GetOptions(' since=s' => \$since ) || usage();
48+ Getopt::Long::GetOptions(' post-date' => \$post_date ,
49+ ' since=s' => \$since ) || usage();
3850usage()if @ARGV ;
3951
4052my @git =qw( git log --date=iso) ;
4153push @git ,' --since=' .$since if defined $since ;
4254
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
4378my %all_commits ;
4479my %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 ;
4585
4686for 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 ;
5098my %commit ;
5199while (my $line = <$git_out >) {
52- if ($line =~/ ^commit\s +(.* )/ ) {
100+ if ($line =~/ ^commit\s +(\S + )/ ) {
53101push_commit(\%commit )if %commit ;
102+ $last_tag =$rel_tags {$1 }if defined $rel_tags {$1 };
54103%commit = (
55104' branch' => $branch ,
56105' commit' => $1 ,
106+ ' last_tag' => $last_tag ,
57107' message' => ' ' ,
58108);
109+ if ($line =~/ ^commit\s +\S +\s +(\S +)/ ) {
110+ $last_parent =$1 ;
111+ }else {
112+ $last_parent =undef ;
113+ }
59114}
60115elsif ($line =~/ ^Author:\s +(.*)/ ) {
61116$commit {' author' } =$1 ;
@@ -68,9 +123,43 @@ for my $branch (@BRANCHES) {
68123}
69124}
70125push_commit(\%commit )if %commit ;
126+ $sprout_tags {$last_parent } =$branch if defined $last_parent ;
71127waitpid ($pid , 0);
72128my $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+ }
74163}
75164
76165my %position ;
@@ -104,7 +193,14 @@ while (1) {
104193last if !defined $best_branch ;
105194my $winner =
106195$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+ }
108204print " Commit-Order-Inversions:$best_inversions \n "
109205if $best_inversions != 0;
110206print " \n " ;
@@ -143,22 +239,22 @@ sub push_commit {
143239}
144240if (!defined $cc ) {
145241$cc = {
146- ' header ' => sprintf ( " Author: %s \n " , $c -> {' author' }) ,
242+ ' author ' => $c -> {' author' },
147243' message' => $c -> {' message' },
148244' commit' => $c -> {' commit' },
245+ ' commits' => [],
149246' timestamp' => $ts
150247};
151248push @{$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 ;
159249}
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 ;
162258push @{$all_commits_by_branch {$c -> {' branch' }}},$cc ;
163259$cc -> {' branch_position' }{$c -> {' branch' }} =
164260-1+@{$all_commits_by_branch {$c -> {' branch' }}};
@@ -180,7 +276,9 @@ sub parse_datetime {
180276
181277sub usage {
182278print 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
184282EOM
185283exit 1;
186284}