# HG changeset patch # User MattD # Date 1253012998 -3600 # Node ID b6b0d741e326c96cb1847f3724f9046a0bf6f264 # Parent 88f86cd8d99b34be1db765709cbf8f03afe048d0# Parent a5ee079f00dd09693f44d925bccf3445a6ce2847 catchup merge. diff -r 88f86cd8d99b -r b6b0d741e326 code_churn/churn_core.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/code_churn/churn_core.pl Tue Sep 15 12:09:58 2009 +0100 @@ -0,0 +1,567 @@ +#!perl -w + +# Copyright (c) 2009 Symbian Foundation Ltd +# This component and the accompanying materials are made available +# under the terms of the License "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Symbian Foundation Ltd - initial contribution. +# +# Contributors: +# +# Description: +# + +use strict; +use File::Find; +use File::Copy; +use Cwd; + +sub diffstat(); + +my $Logs_Dir = $ARGV[0]; +my $dir_left = $ARGV[1]; +my $dir_right = $ARGV[2]; +my $dir_tmp_left = $ARGV[0].'\\'.$ARGV[1]; +my $dir_tmp_right = $ARGV[0].'\\'.$ARGV[2]; + +print "left changeset $dir_left\n"; +print "right chnageset $dir_right\n"; +mkdir $dir_tmp_left; +mkdir $dir_tmp_right; + +# default inclusions from churn.pl are "*.cpp", "*.c", "*.cxx", "*.h", "*.hpp", "*.inl" +my @file_pattern=('\.cpp$','\.c$','\.hpp$','\.h$','\.inl$','\.cxx$','\.hrh$'); +my $totallinecount=0; +my $countcomments=0; + +if (! -d $Logs_Dir) +{ + die("$Logs_Dir does not exist \n"); +} + +$dir_left =~ m/^(\w+)\.[0-9a-fA-F]+/; +my $package_name = $1; + +$dir_left =~ m/^\w+\.([0-9a-fA-F]+)/; +my $changeset_left = $1; + +$dir_right =~ m/^\w+\.([0-9a-fA-F]+)/; +my $changeset_right = $1; + +print "\nWorking on package: $package_name\n"; +print "\nProcessing $dir_left\n"; +find(\&process_files, $dir_left); +#DEBUG INFO: +print "\nTotal linecount for changed files in $dir_left is $totallinecount\n"; +my $code_size_left = $totallinecount; + +$totallinecount=0; +print "\nProcessing $dir_right\n"; +find(\&process_files, $dir_right); +#DEBUG INFO: +print "\nTotal linecount for changed files in $dir_right is $totallinecount\n"; +my $code_size_right = $totallinecount; + +my @diffs; + +if (-d $dir_tmp_left && -d $dir_tmp_left) +{ + @diffs = `diff -r -N $dir_tmp_left $dir_tmp_right`; +} + +my $changed_lines=@diffs; +my $diffsfile = $Logs_Dir.'\\'."dirdiffs.out"; +open (DIFFS, ">$diffsfile"); +print DIFFS @diffs; +close (DIFFS); + +diffstat(); + +$dir_tmp_left =~ s{/}{\\}g; +$dir_tmp_right =~ s{/}{\\}g; + +if (-d $dir_tmp_left) +{ + system("rmdir /S /Q $dir_tmp_left"); +} + +if (-d $dir_tmp_right) +{ +system("rmdir /S /Q $dir_tmp_right"); +} + +unlink $diffsfile; +unlink "$Logs_Dir\\line_count_newdir.txt"; + +print "\n** Finished processing $package_name **\n\n\n\n\n"; + +exit(0); + +sub diffstat() +{ +open (DIFFSFILE,"$diffsfile"); + +my $curfile = ""; +my %changes = (); + +while () +{ + my $line = $_; + # diff -r -N D:/mirror\fbf_churn_output\commsfw.000000000000\serialserver\c32serialserver\Test\te_C32Performance\USB PC Side Code\resource.h + # diff -r + if ($line =~ m/^diff -r.*\.[A-Fa-f0-9]{12}[\/\\](.*)\s*$/) + { + $curfile = $1; + #DEBUG INFO: + #print "\t$curfile\n"; + if (!defined $changes{$curfile}) + { + $changes{$curfile} = {'a'=>0,'c'=>0,'d'=>0,'filetype'=>'unknown'}; + } + + $curfile =~ m/\.(\w+)$/g; + + #if filetype known... + my $filetype = $+; + + $changes{$curfile}->{'filetype'}=uc($filetype); + } + elsif ($line =~ m/^(\d+)(,(\d+))?(d)\d+(,\d+)?/) + { + if (defined $3) + { + $changes{$curfile}->{$4} += ($3-$1)+1; + } + else + { + $changes{$curfile}->{$4}++; + } + } + elsif ($line =~ m/^\d+(,\d+)?([ac])(\d+)(,(\d+))?/) + { + if (defined $5) + { + $changes{$curfile}->{$2} += ($5-$3)+1; + } + else + { + $changes{$curfile}->{$2}++; + } + } +} + +close (DIFFSFILE); + +my %package_changes = ("CPP"=>0, "H"=>0, "HPP"=>0, "INL"=>0, "C"=>0, "CXX"=>0,"HRH"=>0,); +my %package_deletions = ("CPP"=>0, "H"=>0, "HPP"=>0, "INL"=>0, "C"=>0, "CXX"=>0,"HRH"=>0,); +my %package_additions = ("CPP"=>0, "H"=>0, "HPP"=>0, "INL"=>0, "C"=>0, "CXX"=>0,"HRH"=>0,); +my $package_churn = 0; + +for my $file (keys %changes) +{ + $package_changes{$changes{$file}->{'filetype'}} += $changes{$file}->{'c'}; + $package_deletions{$changes{$file}->{'filetype'}} += $changes{$file}->{'d'}; + $package_additions{$changes{$file}->{'filetype'}} += $changes{$file}->{'a'}; +} + + +#DEBUG INFO: For printing contents of hashes containing per filetype summary +#print "\n\n\n\n"; +#print "package_changes:\n"; +#print map { "$_ => $package_changes{$_}\n" } keys %package_changes; +#print "\n\n\n\n"; +#print "package_deletions:\n"; +#print map { "$_ => $package_deletions{$_}\n" } keys %package_deletions; +#print "\n\n\n\n"; +#print "package_additions:\n"; +#print map { "$_ => $package_additions{$_}\n" } keys %package_additions; + + + +my $overall_changes = 0; +for my $filetype (keys %package_changes) +{ + $overall_changes += $package_changes{$filetype}; +} + +my $overall_deletions = 0; +for my $filetype (keys %package_deletions) +{ + $overall_deletions += $package_deletions{$filetype}; +} + +my $overall_additions = 0; +for my $filetype (keys %package_additions) +{ + $overall_additions += $package_additions{$filetype}; +} + + +$package_churn = $overall_changes + $overall_additions; + +print "\n\n\n\nSummary for Package: $package_name\n"; +print "-------------------\n"; +print "Changesets Compared: $dir_left and $dir_right\n"; +#print "Code Size for $dir_left = $code_size_left lines\n"; +#print "Code Size for $dir_right = $code_size_right lines\n"; +print "Total Lines Changed = $overall_changes\n"; +print "Total Lines Added = $overall_additions\n"; +print "Total Lines Deleted = $overall_deletions\n"; +print "Package Churn = $package_churn lines\n"; + +my @header = qw(filetype a c d); + +my $outputfile = $Logs_Dir.'\\'."$package_name\_diffstat.csv"; +open(PKGSTATCSV, ">$outputfile") or die "Coudln't open $outputfile"; + + + +print PKGSTATCSV " SF CODE-CHURN SUMMARY\n"; +print PKGSTATCSV "Package: $package_name\n"; +print PKGSTATCSV "Changesets Compared: $dir_left and $dir_right\n"; +#print PKGSTATCSV "Code Size for $dir_left = $code_size_left lines\n"; +#print PKGSTATCSV "Code Size for $dir_right = $code_size_right lines\n"; +print PKGSTATCSV "Total Lines Changed = $overall_changes\n"; +print PKGSTATCSV "Total Lines Added = $overall_additions\n"; +print PKGSTATCSV "Total Lines Deleted = $overall_deletions\n"; +print PKGSTATCSV "Package Churn = $package_churn lines\n\n\n\n\n"; + + + + +# print the header +print PKGSTATCSV "FILENAME,"; + +foreach my $name (@header) +{ + if ($name eq 'filetype') + { + print PKGSTATCSV uc($name).","; + } + elsif ($name eq 'a') + { + print PKGSTATCSV "LINES_ADDED,"; + } + elsif ($name eq 'c') + { + print PKGSTATCSV "LINES_CHANGED,"; + } + elsif ($name eq 'd') + { + print PKGSTATCSV "LINES_DELETED,"; + } + +} + +print PKGSTATCSV "\n"; + +foreach my $file (sort keys %changes) +{ + print PKGSTATCSV $file.","; + foreach my $key (@header) + { + if(defined $changes{$file}->{$key}) + { + print PKGSTATCSV $changes{$file}->{$key}; + } + print PKGSTATCSV ","; + } + print PKGSTATCSV "\n"; +} + +close (PKGSTATCSV); + + + +my $diffstat_summary = $Logs_Dir.'\\'."diffstat_summary.csv"; + +if (-e $diffstat_summary) +{ + open(DIFFSTATCSV, ">>$diffstat_summary") or die "Coudln't open $outputfile"; + print DIFFSTATCSV "$package_name,"; + print DIFFSTATCSV "$changeset_left,"; + print DIFFSTATCSV "$changeset_right,"; + + #print DIFFSTATCSV ","; + + foreach my $filetype (sort keys %package_changes) + { + if(defined $package_changes{$filetype}) + { + print DIFFSTATCSV $package_changes{$filetype}.","; + } + } + + #print DIFFSTATCSV ","; + + foreach my $filetype (sort keys %package_additions) + { + if(defined $package_additions{$filetype}) + { + print DIFFSTATCSV $package_additions{$filetype}.","; + + } + } + + #print DIFFSTATCSV ","; + + foreach my $filetype (sort keys %package_deletions) + { + if(defined $package_deletions{$filetype}) + { + print DIFFSTATCSV $package_deletions{$filetype}.","; + #print DIFFSTATCSV ","; + } + } + + #print DIFFSTATCSV ","; + print DIFFSTATCSV "$overall_changes,"; + print DIFFSTATCSV "$overall_additions,"; + print DIFFSTATCSV "$overall_deletions,"; + print DIFFSTATCSV "$package_churn,"; + + print DIFFSTATCSV "\n"; + + close (DIFFSTATCSV); +} +else +{ + open(DIFFSTATCSV, ">$diffstat_summary") or die "Couldn't open $outputfile"; + + # print the header + print DIFFSTATCSV "PACKAGE_NAME,"; + print DIFFSTATCSV "LEFT_CHANGESET,"; + print DIFFSTATCSV "RIGHT_CHANGESET,"; + + #print DIFFSTATCSV ","; + + foreach my $name (sort keys %package_changes) + { + print DIFFSTATCSV $name." CHANGES,"; + } + #print DIFFSTATCSV ","; + + + foreach my $name (sort keys %package_additions) + { + print DIFFSTATCSV $name." ADDITIONS,"; + } + #print DIFFSTATCSV ","; + + + foreach my $name (sort keys %package_deletions) + { + print DIFFSTATCSV $name." DELETIONS,"; + } + #print DIFFSTATCSV ","; + + print DIFFSTATCSV "PACKAGE_CHANGES,"; + print DIFFSTATCSV "PACKAGE_ADDITIONS,"; + print DIFFSTATCSV "PACKAGE_DELETIONS,"; + print DIFFSTATCSV "PACKAGE_CHURN,"; + print DIFFSTATCSV "\n"; + + + print DIFFSTATCSV "$package_name,"; + + print DIFFSTATCSV "$changeset_left,"; + print DIFFSTATCSV "$changeset_right,"; + + #print DIFFSTATCSV ","; + + foreach my $filetype (sort keys %package_changes) + { + if(defined $package_changes{$filetype}) + { + print DIFFSTATCSV $package_changes{$filetype}.","; + } + } + + #print DIFFSTATCSV ","; + + foreach my $filetype (sort keys %package_additions) + { + if(defined $package_additions{$filetype}) + { + print DIFFSTATCSV $package_additions{$filetype}.","; + + } + } + + #print DIFFSTATCSV ","; + + foreach my $filetype (sort keys %package_deletions) + { + if(defined $package_deletions{$filetype}) + { + print DIFFSTATCSV $package_deletions{$filetype}.","; + } + } + + #print DIFFSTATCSV ","; + print DIFFSTATCSV "$overall_changes,"; + print DIFFSTATCSV "$overall_additions,"; + print DIFFSTATCSV "$overall_deletions,"; + print DIFFSTATCSV "$package_churn,"; + + print DIFFSTATCSV "\n"; + + close (DIFFSTATCSV); +} + + + +} + +sub process_files() +{ + my $lfile = $_; + my $lfile_fullpath=$File::Find::name; + $lfile_fullpath =~ s#\/#\\#g; + #print "$lfile\t\tFull path $lfile_fullpath\n" ; + if (-f $lfile) + { + foreach my $regpat (@file_pattern) + { + if (lc($lfile) =~ m/$regpat/) + { + $lfile =~ s#\/#\\#g; + #print "Processing file $lfile (Matched $regpat) \n"; #ck + #print `type $lfile`; + # We copy mathching files to a separate temp directory + # so that the final diff can simply diff the full dir + # Note : RemoveNoneLOC routine edits the file in-situ. + my $lfile_abs = cwd().'\\'.$lfile; + my $lfile_local = $Logs_Dir.'\\'.$lfile_fullpath; + makepath($lfile_local); + print "%"; + copy($lfile_abs,$lfile_local); + $totallinecount += RemoveNonLOC( $lfile, $lfile_local, "newdir" ); + } + } + } +} + + +sub makepath() +{ + my $absfile = shift; + $absfile =~ s#\\#\/#g; + my @dirs = split /\//, $absfile; + pop @dirs; # throw away the filename + my $path = ""; + foreach my $dir (@dirs) + { + $path = ($path eq "") ? $dir : "$path/$dir"; + if (!-d $path) + { +# print "making $path \n"; + mkdir $path; + } + } +} + + +sub RemoveNonLOC($$$) { + + # Gather arguments + my $file = shift; + my $original_file = shift; + my $type_of_dir = shift; + +# print("\nDebug: in ProcessFile, file is $file, full file + path is $original_file \n"); + + # Remove comments... + + # Set up the temporary files that will be used to perform the processing steps + my $temp1File = $original_file."temp1"; + my $temp2File = $original_file."temp2"; + + open(TEMP1, "+>$temp1File"); + + if (!($countcomments)) { + + # Remove any comments from the file + my $original_file_string; + open INPUT, "<", $original_file; + { + local $/ = undef; + $original_file_string = ; + } + close INPUT; + + my $dbl = qr/"[^"\\]*(?:\\.[^"\\]*)*"/s; + my $sgl = qr/'[^'\\]*(?:\\.[^'\\]*)*'/s; + + my $C = qr{/\*.*?\*/}s; # C style comments /* */ + my $CPP = qr{//.*}; # C+ style comments // + my $com = qr{$C|$CPP}; + my $other = qr{.[^/"'\\]*}s; # all other '" + my $keep = qr{$sgl|$dbl|$other}; + + #Remove the comments (need to turn off warnings on the next regexp for unititialised variable) +no warnings 'uninitialized'; + + $original_file_string=~ s/$com|($keep)/$1/gom; + print TEMP1 "$original_file_string"; + +use warnings 'uninitialized'; + } + else { + + print("\n option --CountComments specified so comments will be included in the count\n"); + #Just copy over original with comments still in it + copy($original_file,$temp1File); + } + + close(TEMP1); + + + # Remove blank lines... +# print("\nDebug: Getting rid of blank lines in \n$temp1File to produce \n$temp2File \n"); + open (TEMP1, "+<$temp1File"); # include lines + pre-processed code + open (TEMP2, "+>$temp2File"); + + while () { + + if (!(/^\s*\n$/)) { # if line isn't blank write it to the new file + print TEMP2 $_; + } + } + close(TEMP1); + close(TEMP2); + + #Copy the final file to the original file. This updated file will form the input to diff later. + #todo dont need chmod now? + chmod(oct("0777"), $original_file) or warn "\nCannot chmod $original_file : $!\n"; +# print("\nCopying $temp2File\n to \n$original_file\n"); + + #system("copy /Y \"$temp2File\" \"$original_file\"") == 0 + #or print "\nERROR: Copy of $temp2File to $original_file failed\n"; + copy($temp2File,$original_file); + + # Store original file size + + open(LINECOUNT, ">>$Logs_Dir\\line_count_$type_of_dir.txt"); + open(SOURCEFILE, "<$original_file"); + + my @source_code = ; + print LINECOUNT "\n$original_file "; + my $linecount = scalar(@source_code); +# print LINECOUNT scalar(@source_code); + print LINECOUNT $linecount; + + close(LINECOUNT); + close(SOURCEFILE); + + #system("del /F /Q $Logs_Dir\\line_count_$type_of_dir.txt"); + + #Delete the temporary files + unlink($temp1File); + unlink($temp2File); + + return $linecount; +} \ No newline at end of file diff -r 88f86cd8d99b -r b6b0d741e326 code_churn/fbf_churn.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/code_churn/fbf_churn.pl Tue Sep 15 12:09:58 2009 +0100 @@ -0,0 +1,149 @@ +#! perl -w + +# Copyright (c) 2009 Symbian Foundation Ltd +# This component and the accompanying materials are made available +# under the terms of the License "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Symbian Foundation Ltd - initial contribution. +# +# Contributors: +# +# Description: +# + +use strict; +use Getopt::Long; + +use FindBin; +#my $churn_core = "D:\\mirror\\churn_core.pl"; +my $churn_core = "$FindBin::Bin\\churn_core.pl"; +my $churn_output_temp = "$FindBin::Bin\\fbf_churn_output"; +mkdir $churn_output_temp; + +sub Usage($) + { + my ($msg) = @_; + + print "$msg\n\n" if ($msg ne ""); + + print <<'EOF'; + + +fbf_churn.pl - simple script for calculating code churn in between two revisions +or labels for a package. This script can also be used to calculate code size for +a package. + +When used without a package name or filter, this script runs for all the packages +in the BOM (build-info.xml) file supplied to it. + +Important: + This script uses clone_all_packages.pl which clones all repositories listed in + the BOM or pull changes into a previously cloned repository. + + This script uses its accompayning script churn_core.pl - which should be + present in the same directory as this script. + +Limitations: + If a BOM is not supplied to the script using the -bom option, then the script + runs on the package locations inside both MCL and FCL producing two results + for a single package. For running the script for calculating code churn between + two release buils (using labels) or for calculating code size for a release build, + it is essential that a BOM (preferably for the newer build) is passed as an + argument using the -bom option. + + +Options: + +-o --old old revision or label for a package/respoitory + +-n --new new revision or label for a package/respoitory + +--rev revision for package/respoitory - Use this while calculating code size for a single package + +--label revision tag for package or release build - Use this while calculating code size + +-bom --bom build-info.xml files supplied with Symbian PDKs + +-verbose print the underlying "clone_all_packages" & "hg" commands before executing them + +-help print this help information + +-package only process repositories matching regular expression + +-filter only process repositories matching regular expression + +EOF + exit (1); + } + +print "\n\n==Symbian Foundation Code Churn Tool v1.0==\n\n"; + + + +my $old = "null"; +my $new = ""; +my $filter = ""; +my $codeline = ""; +my $package = ""; +my $licence = ""; +my $packagelist = ""; +my $verbose = 0; +my $help = 0; + +sub do_system + { + my (@args) = @_; + print "* ", join(" ", @args), "\n" if ($verbose); + return system(@args); + } + +# Analyse the command-line parameters +if (!GetOptions( + "n|new-rev|new-label|label|rev=s" => \$new, + "o|old-rev|old-label=s" => \$old, + "f|filter=s" => \$filter, + "p|package=s" => \$filter, + "cl|codeline=s" => \$codeline, + "li|licence=s" => \$licence, + "bom|bom=s" => \$packagelist, + "v|verbose" => \$verbose, + "h|help" => \$help, + )) + { + Usage("Invalid argument"); + } + +Usage("") if ($help); +Usage("Too few arguments....use at least one from -n|new-rev|new-label|label|rev or -bom") if ($new eq "" && $packagelist eq ""); +#Usage("Too many arguments") if ($new ne "" && $packagelist ne ""); + + +if ($old eq 'null') + { + print "\nCode size calculation....\n"; + } +else + { + print "\nCode churn calculation....\n"; + } + + +my @packagelistopts = (); +@packagelistopts = ("-packagelist", $packagelist) if ($packagelist ne ""); + +my @verboseopt = (); +@verboseopt = "-v" if ($verbose); + +my $new_rev = $new; +$new_rev = "%REV%" if ($new_rev eq ""); + +#TO_DO: Locate clone_all_packages relative to the location of this script. +#TO_DO: Remove references to absolute paths, change to relative paths. +do_system("clone_all_packages.pl",@verboseopt,"-mirror","-filter","$licence.*$codeline.*$filter",@packagelistopts,"-exec","--", + "hg","--config","\"extensions.hgext.extdiff=\"","extdiff","-p",$churn_core,"-o",$churn_output_temp, + "-r","$old","-r","$new_rev"); + +exit(0); diff -r 88f86cd8d99b -r b6b0d741e326 releaseAutomation/mercurialComparison.pl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/releaseAutomation/mercurialComparison.pl Tue Sep 15 12:09:58 2009 +0100 @@ -0,0 +1,102 @@ +#!perl -w + +# Copyright (c) 2009 Symbian Foundation Ltd +# This component and the accompanying materials are made available +# under the terms of the License "Eclipse Public License v1.0" +# which accompanies this distribution, and is available +# at the URL "http://www.eclipse.org/legal/epl-v10.html". +# +# Initial Contributors: +# Symbian Foundation Ltd - initial contribution. +# +# Contributors: +# +# Description: +# Automates the creation of part of the PDK Release Notes: "Mercurial Comparison with PDK XXXXX" + +use strict; + +use FindBin; +$FindBin::Bin =~ s{/}{\\}g;; + +my $bomInfoFile = shift or die "First argument must be BOM file for build being built/released\n"; +my $previousPdkLabel = shift or die "Second argument must be hg label to compare against\n"; +my $detailsTsvFilename = shift or die "Third argument must be filename to write detailed TSV data into\n"; +defined shift and die "No more than three arguments please\n"; + +# Use external scripts to get the raw data and produce the CSV summary (to go into Excel, etc) +system("perl $FindBin::Bin\\..\\clone_packages\\clone_all_packages.pl -packagelist $bomInfoFile -exec -- hg status -C --rev $previousPdkLabel 2>&1 | perl $FindBin::Bin\\..\\williamr\\summarise_hg_status.pl 2> nul: > $detailsTsvFilename"); + +# The redirection above means that we discard STDERR from summarise_hg_status, +# which lists packages for which it was unable to generate any data +# +# It's discarded because that happens either because it's a new package or has +# moved from SFL -> EPL or we've reverted to using the MCL instead of the FCL +# (in which cases it's dealt with in another part of the release notes) or it +# just hasn't had any changes since the last release + +# Input from TSV file +my @rawData; +open my $fh, "<", $detailsTsvFilename; +my @columns; +foreach my $line (<$fh>) +{ + chomp $line; + my @values = split "\t", $line; + if (!@columns) + { + @columns = @values; + } + else + { + my %lineData; + @lineData{@columns} = @values; + push @rawData, \%lineData; + } +} +close $fh; + +# Pivot +my %cookedData; +foreach my $datum (@rawData) +{ + next if $datum->{Change} =~ m{^(same|M)$}; + $cookedData{$datum->{Package}} += $datum->{Count}; +} + +# Cut-off for "interesting" packages +foreach my $package (keys %cookedData) +{ + delete $cookedData{$package} unless $cookedData{$package} >= 350; +} + +# Output +print <<"EOT"; +== Mercurial Comparison with $previousPdkLabel == + +The Mercurial changes from Nokia were delivered as a bulk update based on '''XXXXXXXXXXXXXXXXXXXXXX'''. + +List of the Mercurial changes (files added/removed/modified) between $previousPdkLabel and PDK '''XXXXX''' - [[Media:XXXX.txt]]. + +A short study of the results which concentrated on the added and removed files has identified these significant package changes: + +EOT + +foreach my $package (sort keys %cookedData) +{ + print <<"EOT"; +=== $package === + +$cookedData{$package} files added/removed + +* Cause1 +* etc + +EOT +} + +if (!keys %cookedData) +{ + print "'''No packages were identified with large changes.'''"; +} +