4 # src/tools/git_changelog
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. Most of the time, such commits occur in the same order
9 # on all branches, and we print them out in that order. However, if commit
10 # A occurs before commit B on branch X and commit B occurs before commit A
11 # on branch Y, then there's no ordering which is consistent with both
14 # When we encounter a situation where there's no single "best" commit to
15 # print next, we print the one that involves the least distortion of the
16 # commit order, summed across all branches. In the event of a further tie,
17 # the commit from the newer branch prints first. It is best not to sort
18 # based on timestamp, because git timestamps aren't necessarily in order
19 # (since the timestamp is provided by the committer's machine), even though
20 # for the portion of the history we imported from CVS, we expect that they
23 # Even though we don't use timestamps to order commits, they are used to
24 # identify which commits happened at about the same time, for the purpose
25 # of matching up commits from different branches.
34 # Adjust this list when the set of active branches changes.
35 my @BRANCHES = qw(master REL9_0_STABLE REL8_4_STABLE REL8_3_STABLE
36 REL8_2_STABLE REL8_1_STABLE REL8_0_STABLE REL7_4_STABLE);
38 # Might want to make this parameter user-settable.
39 my $timestamp_slop = 600;
42 Getopt::Long::GetOptions('since=s' => \$since) || usage();
45 my @git = qw(git log --date=iso);
46 push @git, '--since=' . $since if defined $since;
49 my %all_commits_by_branch;
52 for my $branch (@BRANCHES) {
54 IPC::Open2::open2(my $git_out, my $git_in, @git, "origin/$branch")
55 || die "can't run @git origin/$branch: $!";
56 while (my $line = <$git_out>) {
57 if ($line =~ /^commit\s+(.*)/) {
58 push_commit(\%commit) if %commit;
63 'commitnum' => $commitnum++,
66 elsif ($line =~ /^Author:\s+(.*)/) {
67 $commit{'author'} = $1;
69 elsif ($line =~ /^Date:\s+(.*)/) {
72 elsif ($line =~ /^\s+/) {
73 $commit{'message'} .= $line;
79 for my $branch (@BRANCHES) {
80 $position{$branch} = 0;
85 for my $branch (@BRANCHES) {
86 my $leader = $all_commits_by_branch{$branch}->[$position{$branch}];
87 next if !defined $leader;
89 for my $branch2 (@BRANCHES) {
90 if (defined $leader->{'branch_position'}{$branch2}) {
91 $inversions += $leader->{'branch_position'}{$branch2}
92 - $position{$branch2};
95 if (!defined $best_inversions || $inversions < $best_inversions) {
96 $best_branch = $branch;
97 $best_inversions = $inversions;
100 last if !defined $best_branch;
102 $all_commits_by_branch{$best_branch}->[$position{$best_branch}];
103 print $winner->{'header'};
104 print "Commit-Order-Inversions: $best_inversions\n"
105 if $best_inversions != 0;
106 print $winner->{'message'};
107 $winner->{'done'} = 1;
108 for my $branch (@BRANCHES) {
109 my $leader = $all_commits_by_branch{$branch}->[$position{$branch}];
110 if (defined $leader && $leader->{'done'}) {
111 ++$position{$branch};
119 my $ht = hash_commit($c);
120 my $ts = parse_datetime($c->{'date'});
122 for my $candidate (@{$all_commits{$ht}}) {
123 if (abs($ts - $candidate->{'timestamp'}) < $timestamp_slop
124 && !exists $candidate->{'branch_position'}{$c->{'branch'}})
132 'header' => sprintf("Author: %s\n", $c->{'author'}),
133 'message' => $c->{'message'},
136 push @{$all_commits{$ht}}, $cc;
138 $cc->{'header'} .= sprintf "Branch: %s [%s] %s\n",
139 $c->{'branch'}, substr($c->{'commit'}, 0, 9), $c->{'date'};
140 push @{$all_commits_by_branch{$c->{'branch'}}}, $cc;
141 $cc->{'branch_position'}{$c->{'branch'}} =
142 -1+@{$all_commits_by_branch{$c->{'branch'}}};
147 return $c->{'author'} . "\0" . $c->{'message'};
152 $dt =~ /^(\d\d\d\d)-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/;
153 return Date::Calc::Mktime($1, $2, $3, $4, $5, $6);
158 Usage: git_changelog [--since=SINCE]