|
| 1 | +#!/usr/bin/perl |
| 2 | + |
| 3 | +# |
| 4 | +# Display all commits on active branches, merging together commits from |
| 5 | +# different branches that occur close together in time and with identical |
| 6 | +# log messages. Most of the time, such commits occur in the same order |
| 7 | +# on all branches, and we print them out in that order. However, if commit |
| 8 | +# A occurs before commit B on branch X and commit B occurs before commit A |
| 9 | +# on branch Y, then there's no ordering which is consistent with both |
| 10 | +# branches. |
| 11 | +# |
| 12 | +# When we encounter a situation where there's no single "best" commit to |
| 13 | +# print next, we print the one that involves the least distortion of the |
| 14 | +# commit order, summed across all branches. In the event of a further tie, |
| 15 | +# the commit from the newer branch prints first. It is best not to sort |
| 16 | +# based on timestamp, because git timestamps aren't necessarily in order |
| 17 | +# (since the timestamp is provided by the committer's machine), even though |
| 18 | +# for the portion of the history we imported from CVS, we expect that they |
| 19 | +# will be. |
| 20 | +# |
| 21 | +# Even though we don't use timestamps to order commits, it is used to |
| 22 | +# identify which commits happened at about the same time, for the purpose |
| 23 | +# of matching up commits from different branches. |
| 24 | +# |
| 25 | + |
| 26 | +use strict; |
| 27 | +use warnings; |
| 28 | +require Date::Calc; |
| 29 | +require Getopt::Long; |
| 30 | +require IPC::Open2; |
| 31 | + |
| 32 | +my@BRANCHES =qw(master REL9_0_STABLE REL8_4_STABLE REL8_3_STABLE |
| 33 | + REL8_2_STABLE REL8_1_STABLE REL8_0_STABLE REL7_4_STABLE); |
| 34 | + |
| 35 | +my$since; |
| 36 | +Getopt::Long::GetOptions('since=s'=> \$since) || usage(); |
| 37 | +usage()if@ARGV; |
| 38 | + |
| 39 | +my@git =qw(git log --date=iso); |
| 40 | +push@git,'--since=' .$sinceifdefined$since; |
| 41 | + |
| 42 | +my%all_commits; |
| 43 | +my%all_commits_by_branch; |
| 44 | + |
| 45 | +my%commit; |
| 46 | +formy$branch (@BRANCHES) { |
| 47 | +my$commitnum = 0; |
| 48 | +IPC::Open2::open2(my$git_out,my$git_in,@git,"origin/$branch") |
| 49 | +||die"can't run@git origin/$branch:$!"; |
| 50 | +while (my$line = <$git_out>) { |
| 51 | +if ($line =~/^commit\s+(.*)/) { |
| 52 | +push_commit(\%commit)if%commit; |
| 53 | +%commit = ( |
| 54 | +'branch'=>$branch, |
| 55 | +'commit'=>$1, |
| 56 | +'message'=>'', |
| 57 | +'commitnum'=>$commitnum++, |
| 58 | +); |
| 59 | +} |
| 60 | +elsif ($line =~/^Author:\s+(.*)/) { |
| 61 | +$commit{'author'} =$1; |
| 62 | +} |
| 63 | +elsif ($line =~/^Date:\s+(.*)/) { |
| 64 | +$commit{'date'} =$1; |
| 65 | +} |
| 66 | +elsif ($line =~/^\s+/) { |
| 67 | +$commit{'message'} .=$line; |
| 68 | +} |
| 69 | +} |
| 70 | +} |
| 71 | + |
| 72 | +my%position; |
| 73 | +formy$branch (@BRANCHES) { |
| 74 | +$position{$branch} = 0; |
| 75 | +} |
| 76 | +while (1) { |
| 77 | +my$best_branch; |
| 78 | +my$best_inversions; |
| 79 | +formy$branch (@BRANCHES) { |
| 80 | +my$leader =$all_commits_by_branch{$branch}->[$position{$branch}]; |
| 81 | +nextif !defined$leader; |
| 82 | +my$inversions = 0; |
| 83 | +formy$branch2 (@BRANCHES) { |
| 84 | +if (defined$leader->{'branch_position'}{$branch2}) { |
| 85 | +$inversions +=$leader->{'branch_position'}{$branch2} |
| 86 | +-$position{$branch2}; |
| 87 | +} |
| 88 | +} |
| 89 | +if (!defined$best_inversions ||$inversions <$best_inversions) { |
| 90 | +$best_branch =$branch; |
| 91 | +$best_inversions =$inversions; |
| 92 | +} |
| 93 | +} |
| 94 | +lastif !defined$best_branch; |
| 95 | +my$winner = |
| 96 | +$all_commits_by_branch{$best_branch}->[$position{$best_branch}]; |
| 97 | +print$winner->{'header'}; |
| 98 | +print"Commit-Order-Inversions:$best_inversions\n" |
| 99 | +if$best_inversions != 0; |
| 100 | +print$winner->{'message'}; |
| 101 | +$winner->{'done'} = 1; |
| 102 | +formy$branch (@BRANCHES) { |
| 103 | +my$leader =$all_commits_by_branch{$branch}->[$position{$branch}]; |
| 104 | +if (defined$leader &&$leader->{'done'}) { |
| 105 | +++$position{$branch}; |
| 106 | +redo; |
| 107 | +} |
| 108 | +} |
| 109 | +} |
| 110 | + |
| 111 | +subpush_commit { |
| 112 | +my ($c) =@_; |
| 113 | +my$ht = hash_commit($c); |
| 114 | +my$ts = parse_datetime($c->{'date'}); |
| 115 | +my$cc; |
| 116 | +formy$candidate (@{$all_commits{$ht}}) { |
| 117 | +if (abs($ts -$candidate->{'timestamp'}) < 600 |
| 118 | +&& !exists$candidate->{'branch_position'}{$c->{'branch'}}) |
| 119 | +{ |
| 120 | +$cc =$candidate; |
| 121 | +last; |
| 122 | +} |
| 123 | +} |
| 124 | +if (!defined$cc) { |
| 125 | +$cc = { |
| 126 | +'header'=>sprintf("Author:%s\n",$c->{'author'}), |
| 127 | +'message'=>$c->{'message'}, |
| 128 | +'timestamp'=>$ts |
| 129 | +}; |
| 130 | +push @{$all_commits{$ht}},$cc; |
| 131 | +} |
| 132 | +$cc->{'header'} .=sprintf"Branch:%s [%s]%s\n", |
| 133 | +$c->{'branch'},substr($c->{'commit'}, 0, 9),$c->{'date'}; |
| 134 | +push @{$all_commits_by_branch{$c->{'branch'}}},$cc; |
| 135 | +$cc->{'branch_position'}{$c->{'branch'}} = |
| 136 | +-1+@{$all_commits_by_branch{$c->{'branch'}}}; |
| 137 | +} |
| 138 | + |
| 139 | +subhash_commit { |
| 140 | +my ($c) =@_; |
| 141 | +return$c->{'author'} ."\0" .$c->{'message'}; |
| 142 | +} |
| 143 | + |
| 144 | +subparse_datetime { |
| 145 | +my ($dt) =@_; |
| 146 | +$dt =~/^(\d\d\d\d)-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/; |
| 147 | +return Date::Calc::Mktime($1,$2,$3,$4,$5,$6); |
| 148 | +} |
| 149 | + |
| 150 | +subusage { |
| 151 | +printSTDERR<<EOM; |
| 152 | +Usage: git-topo-order [--since=SINCE] |
| 153 | +EOM |
| 154 | +exit 1; |
| 155 | +} |