1#!/usr/bin/perl -w 2# 3# Generate a nice changelist by querying perforce. 4# 5# Each change is described with the change number, description, 6# which branch the change happened in, files modified, 7# and who was responsible for entering the change. 8# 9# Can be called with a list of change numbers or a range of the 10# form "12..42". Changelog will be printed from highest number 11# to lowest. 12# 13# Outputs the changelist to stdout. 14# 15# Gurusamy Sarathy <gsar@activestate.com> 16# 17 18use Text::Wrap; 19 20$0 =~ s|^.*/||; 21unless (@ARGV) { 22 die <<USAGE; 23 $0 [-p \$P4PORT] [-bi branch_include] [-be branch_exclude] <change numbers or from..to> 24USAGE 25} 26 27my @changes; 28 29my %editkind; 30@editkind{ qw( add edit delete integrate branch )} 31 = qw( + ! - !> +> ); 32 33my $p4port = $ENV{P4PORT} || 'localhost:1666'; 34 35my @branch_include; 36my @branch_exclude; 37my %branch_include; 38my %branch_exclude; 39 40while (@ARGV) { 41 $_ = shift; 42 if (/^(\d+)\.\.(\d+)?$/) { 43 push @changes, $1 .. ($2 || (split(' ', `p4 changes -m 1`))[1]); 44 } 45 elsif (/^\d+$/) { 46 push @changes, $_; 47 } 48 elsif (/^-p(.*)$/) { 49 $p4port = $1 || shift; 50 } 51 elsif (/^-bi(.*)$/) { 52 push @branch_include, $1 || shift; 53 } 54 elsif (/^-be(.*)$/) { 55 push @branch_exclude, $1 || shift; 56 } 57 else { 58 warn "Arguments must be change numbers, ignoring `$_'\n"; 59 } 60} 61 62@changes = sort { $b <=> $a } @changes; 63 64@branch_include{@branch_include} = @branch_include if @branch_include; 65@branch_exclude{@branch_exclude} = @branch_exclude if @branch_exclude; 66 67my @desc = `p4 -p $p4port describe -s @changes`; 68if ($?) { 69 die "$0: `p4 -p $p4port describe -s @changes` failed, status[$?]\n"; 70} 71else { 72 tr/\r/\n/ foreach @desc; 73 chomp @desc; 74 while (@desc) { 75 my ($change,$who,$date,$time,@log,$branch,$file,$type,%files); 76 my $skip = 0; 77 my $nbranch = 0; 78 $_ = shift @desc; 79 if (/^Change (\d+) by (\w+)\@.+ on (\S+) (\S+)\s*$/) { 80 ($change, $who, $date, $time) = ($1,$2,$3,$4); 81 $_ = shift @desc; # get rid of empty line 82 while (@desc) { 83 $_ = shift @desc; 84 last if /^Affected/; 85 push @log, $_; 86 } 87 if (/^Affected/) { 88 $_ = shift @desc; # get rid of empty line 89 while ($_ = shift @desc) { 90 last unless /^\.\.\./; 91 if (m{^\.\.\. //depot/(.*?perl|[^/]*)/([^#]+)#\d+ (\w+)\s*$}) { 92 ($branch,$file,$type) = ($1,$2,$3); 93 $nbranch++; 94 if (exists $branch_exclude{$branch} or 95 @branch_include and 96 not exists $branch_include{$branch}) { 97 $skip++; 98 } 99 $files{$branch} = {} unless exists $files{$branch}; 100 $files{$branch}{$type} = [] unless exists $files{$branch}{$type}; 101 push @{$files{$branch}{$type}}, $file; 102 } 103 else { 104 warn "Unknown line [$_], ignoring\n"; 105 } 106 } 107 } 108 } 109 next if ((not $change) or $skip); 110 print "_" x 76, "\n"; 111 printf <<EOT, $change, $who, $date, $time; 112[%6s] By: %-25s on %9s %9s 113EOT 114 print " Log: "; 115 my $i = 0; 116 while (@log) { 117 $_ = shift @log; 118 s/^\s*//; 119 s/^\[.*\]\s*// unless $i ; 120 # don't print last empty line 121 if ($_ or @log) { 122 print " " if $i++; 123 print "$_\n"; 124 } 125 } 126 for my $branch (sort keys %files) { 127 printf "%11s: $branch\n", 'Branch'; 128 for my $kind (sort keys %{$files{$branch}}) { 129 warn("### $kind ###\n"), next unless exists $editkind{$kind}; 130 my $files = $files{$branch}{$kind}; 131 # don't show large branches and integrations 132 $files = ["($kind " . scalar(@$files) . ' files)'] 133 if (@$files > 25 && ($kind eq 'integrate' 134 || $kind eq 'branch')) 135 || @$files > 100; 136 print wrap(sprintf("%12s ", $editkind{$kind}), 137 sprintf("%12s ", $editkind{$kind}), 138 "@$files\n"); 139 } 140 } 141 } 142} 143