A collection of release tools used for Koha
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

349 lines
12 KiB

#!/usr/bin/perl
# Copyright 2011 Chris Nighswonger
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this software; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
#
use strict;
use warnings;
use Git;
use LWP::Simple;
use Text::CSV;
use Data::Dumper;
use Getopt::Long;
use IO::Prompt;
# TODO:
# 1. Paramatize!
# 2. Add optional verbose output
# 3. Add exit status code
# 4. Add help
# Try to determine the current branch and set that as the default
my $branch = qx/git branch | grep ^*/;
$branch =~ m/^\*\s(.*)$/;
$branch = $1;
my $HEAD = 'master';
my $limit = 0;
my $help = 0;
my $verbose = 0;
GetOptions(
'b|branch:s' => \$branch,
'h|head:s' => \$HEAD,
'l|limit:s' => \$limit,
'help|?' => \$help,
'v|verbose' => \$verbose,
);
my $usage = << 'ENDUSAGE';
This script retrieves the output of 'git cherry' and parses through it
creating a list of enhancements and bugfixes found in master (or at the
user's option an alternative 'HEAD') but not in the 'branch' passed in.
The script will then auto-cherry-pick the missing bugfixes and produce
a list for manual review of the missing enhancements.
This script has the following parameters :
-b --branch: branch to compare against 'HEAD' ('HEAD' is 'master' by default)
-h --head: 'HEAD' other than 'master' against which to compare 'branch'
-l --limit: if a limit (a commit id <sha1>) is given then commits along the HEAD branch
up to and including the limit will not be reported
--help: this message
-v --verbose: provides verbose output to STDOUT
ENDUSAGE
die $usage if $help;
my $bugs = {};
my @unapplied_bugfixes = ();
my @unapplied_anonfixes = ();
my @unapplied_enhancements = ();
my @unclean_commits = ();
if (-e "unapplied_bugfixes.$branch.txt") {
open(BF, "<unapplied_bugfixes.$branch.txt");
@unapplied_bugfixes = <BF>;
close BF;
}
if (-e "unapplied_anonfixes.$branch.txt") {
open(AF, "<unapplied_anonfixes.$branch.txt");
@unapplied_anonfixes = <AF>;
close AF;
}
if (-e "unapplied_enhancements.$branch.txt") {
open(EN, "<unapplied_enhancements.$branch.txt");
@unapplied_enhancements = <EN>;
close EN;
}
if (-e "unclean_commits.$branch.txt") {
open(UC, "<unclean_commits.$branch.txt");
@unclean_commits = <UC>;
close UC;
}
my $repo = Git->repository (Directory => '/home/cnighs/koha.3.2.test');
my @git_command = ('cherry', '-v', $branch, $HEAD);
push @git_command, $limit if $limit;
my ($fh, $c) = $repo->command_output_pipe(@git_command);
my @git_cherry = ();
foreach (<$fh>) {
unshift @git_cherry, $_;
}
$repo->command_close_pipe($fh, $c);
my $commit_count = scalar(@git_cherry);
my $commit_list = {};
my $bug_list = []; # This keeps our list of commits to be applied in the order they were committed (should probably be commit_list, but we already used that one... sorry.)
my $no_bug_number = {};
my $enhancements = {};
my $bug_fixes = {};
foreach (@git_cherry) {
$_ =~ m/^\+\s([0-9a-z]{40,40})/;
my $commit_id = $1;
if ($_ =~ m/^\+.*([B|b]ug|BZ)\s?(?<=\s)(\d{4})(?=[\s|:|,])/g) {
my $bug_number = $2;
print "Found bug number: $bug_number\n" if $verbose;
next if grep (/$bug_number/, @unapplied_bugfixes);
next if grep (/$bug_number/, @unapplied_enhancements);
next if grep (/$bug_number/, @unclean_commits);
unshift (@$bug_list, $bug_number) unless grep (/$bug_number/, @$bug_list); # avoid listing a bug twice as we group the commit ids under a single bug number entry
unshift (@{$commit_list->{"$bug_number"}}, $commit_id); # catalog commits with a bug number based on bug number
}
elsif ($_ !~ m/^\-/) {
next if grep (/$commit_id/, @unapplied_anonfixes);
next if grep (/$commit_id/, @unclean_commits);
print "Found commit missing bug number; commit id: $commit_id\n" if $verbose;
unshift (@$bug_list, $commit_id);
unshift (@{$no_bug_number->{"$commit_id"}}, $commit_id); # catalog commits without a bug number based on commit id
}
elsif ($_ =~ m/^\-/) {
$commit_count--;
# do some stuff
}
else {
# do some other stuff
}
}
print "\n\n$commit_count commits found which are in $HEAD but not in $branch\n\n";
print "Of these, " . scalar(@unapplied_bugfixes) . " are unapplied bugfixes,\n";
print scalar(@unapplied_anonfixes) . " are unapplied commits with no bug number,\n";
print scalar(@unapplied_enhancements) . " are unapplied enhancements,\n";
print scalar(@unclean_commits) . " are commits which do not apply cleanly,\n";
print "and some may be multiple commits for a single bug number.\n\n";
if (scalar(keys(%$commit_list)) == 0 && scalar(keys(%$no_bug_number)) == 0) {
print "\nCongratulations!! There are no new commits to pick!\n\n";
exit 1;
}
if (scalar(keys(%$commit_list))) {
my @bug_list = keys(%$commit_list);
@bug_list = sort {$a <=> $b} @bug_list;
my $url = "http://bugs.koha-community.org/bugzilla3/buglist.cgi?order=bug_id&columnlist=bug_severity%2Cshort_desc&bug_id=";
$url .= join '%2C', @bug_list;
$url .= "&ctype=csv";
$verbose && print "URL: $url\n";
my @csv_file = split /\n/, get($url);
my $csv = Text::CSV->new();
# Extract the column names
$csv->parse(shift @csv_file);
my @columns = $csv->fields;
while (scalar @csv_file) {
$csv->parse(shift @csv_file);
my @fields = $csv->fields;
push @{$bugs->{$fields[0]}}, $fields[1];
push @{$bugs->{$fields[0]}}, $fields[2];
print "Bug Number: $fields[0], Bug Type: $fields[1], Short Desc: $fields[2]\n" if $verbose;
if ($fields[1] =~ m/(enhancement)/) {
push (@{$enhancements->{"$fields[0]"}}, @{$commit_list->{"$fields[0]"}});
}
else {
push (@{$bug_fixes->{"$fields[0]"}}, @{$commit_list->{"$fields[0]"}});
}
}
BUGFIXES:
foreach my $bug_number (@$bug_list) {
while( prompt "Shall I apply the bugfix(s) for $bug_number? (Y/n/s/v/q)") {
if ($_ =~ m/^[Y|y]/) {
if ($bug_number =~ m/^[0-9a-z]{40,40}/) {
print "Applying commit without a bug number: $bug_number\n" if $verbose;
foreach my $commit_id (@{$no_bug_number->{$bug_number}}) {
my @git_command = ('cherry-pick', '-x', '-s', $commit_id);
my ($fh, $c) = $repo->command_output_pipe(@git_command);
my @cherry_pick = <$fh>;
eval { $repo->command_close_pipe($fh, $c); };
if ($@) {
_revert(\@unclean_commits, $repo, $commit_id, $branch);
next;
}
}
last;
}
else {
print "Applying commit(s) for bug number: $bug_number\n" if $verbose;
foreach my $commit_id (@{$bug_fixes->{$bug_number}}) {
print "\t$commit_id\n" if $verbose;
my @git_command = ('cherry-pick', '-x', '-s', $commit_id);
my ($fh, $c) = $repo->command_output_pipe(@git_command);
my @cherry_pick = <$fh>;
eval { $repo->command_close_pipe($fh, $c); };
if ($@) {
_revert(\@unclean_commits, $repo, $bug_number, $branch);
next;
}
}
last;
}
}
elsif ($_ =~ m/^[v]/) {
if ($bugs->{$bug_number}) {
print "\nBug Number: $bug_number, Bug Type: $@$bugs->{$bug_number}[0], Short Desc: $@$bugs->{$bug_number}[1]\n\n";
}
else {
print "This commit does not have a bug number so we're missing a description\n\n";
}
}
elsif ($_ =~ m/^[q]/) {
last BUGFIXES;
}
elsif ($_ =~ m/^[n]/) {
open(BF, ">>unapplied_bugfixes.$branch.txt");
print BF "$bug_number\n";
close BF;
last;
}
else {
print "Skipping bug $bug_number\n\n";
last;
}
}
}
}
#while (scalar(keys(%$no_bug_number)) && prompt "Shall we review commits without bug numbers now? (Y/n)") {
# last if $_ =~ m/^[N|n]/;
#
# NOBUGNUM:
# foreach my $commit_number (keys(%$no_bug_number)) {
# while( prompt "Shall I apply commit number $commit_number? (Y/n/s/v/q)") {
# if ($_ =~ m/^[Y|y]/) {
# foreach my $commit_id (@{$no_bug_number->{$commit_number}}) {
# my @git_command = ('cherry-pick', '-x', '-s', $commit_id);
# my ($fh, $c) = $repo->command_output_pipe(@git_command);
# my @cherry_pick = <$fh>;
# eval { $repo->command_close_pipe($fh, $c); };
# if ($@) {
# _revert(\@unclean_commits, $repo, $commit_id, $branch);
# next;
# }
# }
# last;
# }
# elsif ($_ =~ m/^[v]/) {
# print qx|git log --oneline -1 $commit_number|;
# }
# elsif ($_ =~ m/^[q]/) {
# last NOBUGNUM;
# }
# elsif ($_ =~ m/^[n]/) {
# open(AF, ">>unapplied_anonfixes.$branch.txt");
# print AF "$commit_number\n";
# close AF;
# last;
# }
# else {
# last;
# }
# }
# }
# last;
#}
while (scalar(keys(%$enhancements)) && prompt "Shall we review enhancements now? (Y/n)") {
last if $_ =~ m/^[N|n]/;
ENHANCE:
foreach my $bug_number (keys(%$enhancements)) {
while( prompt "Shall I apply enhancement $bug_number? (Y/n/s/v/q)") {
if ($_ =~ m/^[Y|y]/) {
foreach my $commit_id (@{$enhancements->{$bug_number}}) {
my @git_command = ('cherry-pick', '-x', '-s', $commit_id);
my ($fh, $c) = $repo->command_output_pipe(@git_command);
my @cherry_pick = <$fh>;
eval { $repo->command_close_pipe($fh, $c); };
if ($@) {
_revert(\@unclean_commits, $repo, $bug_number, $branch);
next;
}
}
last;
}
elsif ($_ =~ m/^[v]/) {
print "\nBug Number: $bug_number, Bug Type: $@$bugs->{$bug_number}[0], Short Desc: $@$bugs->{$bug_number}[1]\n\n";
}
elsif ($_ =~ m/^[q]/) {
last ENHANCE;
}
elsif ($_ =~ m/^[n]/) {
open(EN, ">>unapplied_enhancements.$branch.txt");
print EN "$bug_number\n";
close EN;
last;
}
else {
last;
}
}
}
last;
}
print "Please review the following unclean commits:\n- " . (join '- ', @unclean_commits) . "\n\n";
exit 0;
sub _revert {
my $unclean_commits = shift;
my ($repo, $bug_number, $branch) = @_;
print "\n\nReverting failed cherry-pick...\n\n";
my ($fh, $c) = $repo->command_output_pipe('reset', '--hard', 'HEAD');
print <$fh>;
$repo->command_close_pipe($fh, $c);
return if grep (/$bug_number/, @$unclean_commits); # Don't add to the list if it is already there
print"\n\nAdding $bug_number to the unclean_commits.$branch.txt file.\n\n";
open(UC, ">>unclean_commits.$branch.txt");
print UC "$bug_number\n";
push @$unclean_commits, "$bug_number\n";
close UC;
}