#!/usr/bin/perl ## # Copyright (C) 2008 by James E.J. Bottomley ## # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. ## use strict; use Getopt::Std; # -i means run interactively (ignore the mail and error directives)' # # -t means override the tree specified in the setup directive # this is for interactive runs to try a merge in a scratch tree # using the same config file our($opt_i,$opt_t); getopts('it:'); my $treefile=$ARGV[0]; my $logfile = "/tmp/builder-jejb.$$.log"; if (!$opt_i) { close STDOUT; open STDOUT, ">$logfile"; close STDERR; open STDERR, ">$logfile"; } my @trees; my ($dir, $base, $tagbase, $master, $master_tag, $kernelbase, $maillog, $tagbasemk, $errlog); sub base_transform { my $orig_base = $_[0]; my $base; return $orig_base if (!($orig_base =~ m/-git[0-9]+$/)); $orig_base = substr($orig_base, 1) if (substr($orig_base, 0, 1) eq 'v'); my $patch = "/pub/linux/kernel/v2.6/snapshots/patch-${orig_base}.log"; if (! -f $patch) { print "PATCH FILE $patch NOT FOUND\n"; return $orig_base; } open(TMP, "<$patch") || die; my $head; chomp($head = ); close(TMP); if ($head =~ m/^commit (\S+)/) { $base = $1; print "$orig_base to $base\n"; } else { print "Unrecognised line $head in file $patch\n"; $base = $orig_base; } return $base; } open(TREES, "<$treefile") || die; chomp(@trees = ); close(TREES); END { if ($? && !$opt_i) { if ($maillog) { system("cat $logfile | mail -s \"ERROR BUILDING: $tagbase\" $maillog"); } if ($errlog) { system("cp $logfile $errlog${tagbase}"); } } unlink($logfile); } foreach(@trees) { next if (m/^\s*#/ || m/^\s*$/); print "##\n# $_\n##\n"; if (m/^\s*(git|gitfetch)\s+(\S+)\s+(\S+)\s+(\S+)/ || m/^\s*git\s+(\S+)\s+(\S+)/) { die ("no master before git\n") if (!defined $master); my $cmd = $1; my $url = $2; my $remote_branch = $3; my $local_branch = $4; $local_branch = $remote_branch if (!$local_branch); my $tmp; chomp($tmp = `git ls-remote --heads $url $remote_branch`); if (!$tmp) { print "BRANCH $remote_branch not in tree $url, SKIPPING\n"; system("git branch -D $local_branch"); next; } system("git fetch --no-tags -f $url $remote_branch:$local_branch") >> 8 == 0 || die; if ($cmd eq 'git') { system("git pull . $local_branch") >> 8 == 0 || die; } } elsif (m/^\s*gitrebase\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) { die ("no master before git\n") if (!defined $master); my $url = $1; my $remote_branch = $2; my $local_branch = $3; my $remote_merge_base = $4; my $local_merge_base = ${local_branch}.'-base'; my $tmp; chomp($tmp = `git ls-remote --heads $url $remote_branch`); if (!$tmp) { print "BRANCH $remote_branch not in tree $url, SKIPPING\n"; system("git branch -D $local_branch"); next; } system("git fetch --no-tags -f $url $remote_branch:$local_branch") >> 8 == 0 || die; system("git fetch --no-tags -f $url $remote_merge_base:$local_merge_base") >> 8 == 0 || die; system("git checkout $local_branch") >> 8 == 0 || die; system("git rebase --onto $tagbase $local_merge_base") >> 8 == 0 || die; system("git checkout $tagbase") >> 8 == 0 || die; system("git pull . $local_branch") >> 8 == 0 || die; } elsif (m/^\s*quiltstable\s+(\S+)\s+(\S+)\s+(\S+)/) { my $quilt_branch = $1; my $stable_tree = $2; my $stable_base = $3; my @patches = (); system("git checkout $quilt_branch") >> 8 == 0 || die; foreach(`git log ${stable_base}..${stable_tree}`) { chomp($_); if (/^commit (\S+)/) { push @patches, $1; } } foreach(@patches) { ## # We have to revert each patch individually so that # rename information is preserved ## if (system("git revert --no-edit $_") >> 8 != 0) { # if nothing changed, the revert is empty system("git-update-index --refresh") >> 8 == 0 || die; } } system("git pull . $stable_tree") >> 8 == 0 || die; } elsif (m/^\s*merge\s+(\S+)\s+resolve\s+(\S+)/ || m/^\s*merge\s+(\S+)/) { my $local_branch = $1; my $resolve_patch = $2; print "RESOLVE PATCH: $2\n"; system("git checkout $tagbase") >> 8 == 0 || die; if (system("git cat-file -e $local_branch 2> /dev/null") >> 8 != 0) { print "MERGE $local_branch not in tree, SKIPPING\n"; next; } if ($resolve_patch) { my @files; my $file; my @unresolved; if (system("git pull . $local_branch") >> 8 == 0) { print "WARNING: Merge successful but resolve requested\n"; die; } chomp(@files = `git status`); grep { if (/^#\s+both modified:\s+(\S+)/) { $_ = $1; } else { $_ = undef; } } @files; foreach $file (@files, $resolve_patch) { next if (!$file); open(TMP, "<$file") || die("Missing file $file"); my @contents = ; close TMP; grep { $_ = "$1<<<<<<<\n" if (m|^(-?)\<\<\<\<\<\<\<\s|); $_ = "$1>>>>>>>\n" if (m|^(-?)\>\>\>\>\>\>\>\s|); } @contents; open(TMP, ">$file"); print TMP join('', @contents); close TMP; } system("git apply ${resolve_patch}") >> 8 == 0 || die; foreach $file (@files) { next if (!$file); if (!open(TMP, "<$file")) { # patch removed the file system("git rm $file"); next; } foreach () { if (m|^\<\<\<\<\<\<\<| || m|^\>\>\>\>\>\>\>|) { push @unresolved, $file; } } } die("Unresolved files: ".join(",", @unresolved)."\n") if (@unresolved); system("git commit --all -m \"Resolve Patch: ${resolve_patch}\"") >> 8 == 0 || die; } else { system("git pull . $local_branch") >> 8 == 0 || die; } } elsif (m/^\s*patch\s+(-\S+)\s+(\S+)\s+(\S+)/ || m/^\s*patch()\s+(\S+)\s+(\S+)/) { my $flags = $1; my $patch = $2; my $branch = $3; die("no patch $patch") if (! -f $patch); system("git checkout $branch") >> 8 == 0 || die; system("git apply $flags --index $patch") >> 8 == 0 || die; system("git commit -m \"patch: $patch\"") >> 8 == 0 || die; } elsif (m/^\s*quilt\s+(\S+)\s+(\S+)\s+(base|section)\s+(\S+)/ || m/^\s*quilt\s+(\S+)\s+(\S+)/) { die ("no master before quilt") if(!defined $master); my $url = $1; my $branch = $2; my $patchdir = 'patches/'.$branch; my ($base, $section); if ($3 eq 'base') { $base = $4; } elsif ($3 eq 'section') { $section = $4; } my $cont = m/\s+noerror/; system("rm -fr .dotest"); mkdir('patches'); mkdir($patchdir); if ($url =~ m|^http://|) { system("rm -fr $patchdir && mkdir $patchdir"); system("wget -q --recursive --no-directories --no-parent --directory-prefix=$patchdir $url"); # wget has odd return codes, so don't die here } else { system("rsync -avc $url $patchdir") >>8 == 0|| die; } die("no series file") if (! -f "$patchdir/series"); my (@tmp); open(TMP, "<$patchdir/series") || die; chomp(@tmp = ); close(TMP); my $secstart = 0; foreach (@tmp) { if (!$base && (/^\s*\#\s*BASE\s+commit\s+(\S+)/ || /^\s*\#\s*BASE\s+(\S+)/)) { $base = $1; } if (/(\S+)\s*\#/) { # strip trailing comments (git doesn't like them) $_ = $1; } if ($section) { if (!$secstart) { if (m/^\s*\#\s*${section}_START/) { $secstart = 1; } $_ = '#'; } elsif (m/^\s*\#\s*${section}_END/) { $secstart = 0; } } } open(TMP, ">$patchdir/series") || die; print TMP join("\n", @tmp); close(TMP); if ($base) { # get the actual value for 2.6.xx-gitN urls (if present) $base = base_transform($base); } else { $base = $master if (!$base); $base = $tagbase if ($base eq 'TOP'); } print "Using base $base\n"; if (system("git branch -f $branch $base") >> 8 != 0) { #try with a v $base = 'v'.$base; system("git branch -f $branch $base") >> 8 == 0 || die; } system("git checkout $branch") >> 8 == 0 || die; system("git branch -f ${branch}-base") >> 8 == 0 || die; if (system("git apply --check --reverse $_ > /dev/null 2>&1") >> 8 == 0) { # if the first patch can be reverse applied assume the series # is already incorporated print "REVERSE APPLICABLE PATCH, SKIPPING QUILT\n"; system("git branch -D $branch"); next; } system("git quiltimport --author \"none \" --patches $patchdir") >> 8 == 0 || $cont || die; system("git checkout $tagbase") >> 8 == 0 || die; } elsif (m/^\s*master\s+(\S+)\s+(\S+)/) { die ("no setup before master") if(!defined $tagbase); my $url = $1; $master = $2; my $tmp; (system("git fetch -f $url master:$master") >> 8) == 0|| die; chomp($master_tag = `git describe --abbrev=0 $master`); die("Strange tag $master_tag") if (!($master_tag =~ /v(\d+\.\d+\.\d+-rc\d+)$/ || $master_tag =~/v(\d+\.\d+\.\d+)$/ )); $kernelbase = $1; $tagbasemk = $tagbase; $tagbase = $1.'-'.$tagbase; my @branches; chomp(@branches = `git branch`); $tmp = 0; foreach (@branches) { next if (!m/^..$tagbase(\d+)/); $tmp = $1 if ($1 > $tmp); } $tagbase .= ++$tmp; $tagbasemk .= $tmp; print "Using $tagbase\n"; system("git branch $tagbase $master") >> 8 == 0 || die; system("git checkout $tagbase") >> 8 == 0 || die; system("git clean -x -d -f"); system("git branch ${tagbase}-base") >> 8 == 0 || die; } elsif (m/^\s*setup\s+(\S*)\s+(\S*)/) { $tagbase = $1; $dir = $2; if ($opt_t) { print "Interactive run, overriding tree with $opt_t\n"; $dir = $opt_t; } chdir $dir || die("Failed to change to $dir"); system("git reset --hard") >> 8 == 0 || die; system("rm -fr .git/rebase-apply"); system("git fetch"); } elsif (m/^\s*diff\s+(\S+)/) { next if ($opt_i); # skip if interactive die ("no diff before git\n") if (!defined $master); my $prefix = $1; system("git diff ${tagbase}-base $tagbase > ${prefix}${tagbase}.diff") >> 8 == 0 || die; } elsif(m/^\s*log\s+(\S+)/) { my $prefix = $1; next if ($opt_i); # skip if interactive system("cp $logfile $prefix${tagbase}") >> 8 == 0 || die; } elsif(m/^\s*mail\s+(\S+)/) { $maillog = $1 } elsif(m/^\s*errors\s+(\S+)/) { $errlog = $1; } elsif(m/^\s*check\s*/) { my ($prefix, $count, $prior, $diff); ($prefix, $count) = ($tagbase =~ m/^(.*?)(\d+)$/); if ($count == 1) { print "No prior to diff against\n"; next; } $prior = $count - 1; chomp($diff=`git diff ${prefix}${prior} ${prefix}${count}`); if (!$diff) { print "Diff with prior is empty, nothing to do\n"; # remove all records of this build system("git branch -D $tagbase"); system("git branch -D ${tagbase}-base"); exit(0); } print "Differences exist, continuing\n"; } elsif (m/^\s*label\s*$/) { open(MK, '; close(MK); grep { if (m/^EXTRAVERSION =\s*(\S*)/) { $_ = 'EXTRAVERSION = '.$1.'-'.$tagbasemk."\n"; } } @makefile; open(MK, '>Makefile'); print MK join('', @makefile); close(MK); system("git commit -m \"$tagbase\" Makefile") >> 8 == 0 || die; } elsif (m/^\s*skip\s(.*)/) { print "SKIPPING: $_\n"; } elsif (m/^\s*revert\s+(\S+)\s+(\S+)/ || m/^\s*revert\s+(\S+)/) { my $revert = $1; my $branch = $2; if ($branch) { system("git checkout $branch") >> 8 == 0 || die; } system ("git revert --no-edit $revert") >> 8 == 0 || die; if ($branch) { system("git checkout $tagbase") >> 8 == 0 || die; } } else { die("Unknown line $_\n"); } } exit(0);