#!/usr/bin/perl
use strict ;
use XML::Twig ;
use Getopt::Long ;
use POSIX ;
use Cwd ;
my %args = (); # all command line args go here
my $version = "0.1" ;
sub print_patch {
my($p, $action) = @_ ;
my $att = $p->atts ;
if ($action) {
print "$action " ;
}
print "id $att->{'id'} parent $att->{'parent'}, " ;
print "tag $att->{'tag'}\n" ;
}
sub print_patchset {
my ($p_elt, $action) = @_ ;
my $att = $p_elt->atts ;
my $elt ;
if ($action) {
print "$action " ;
}
print "patchset $att->{'tag'}\n" ;
$elt = $p_elt->first_child('patch') ;
while($elt) {
print_patch($elt, ' ') ;
$elt = $elt->next_sibling('patch') ;
}
}
# p_elt id and parent are required. The rest are optional
# adds a new patch onto the tree in p_elt, and returns it.
#
# if $sib is defined, it is the name of another patch under $p_elt.
# we'll try to find it, and try to place the new patch in $pos relative
# to $sib. $pos can be "before" or "after"
#
sub new_patch {
my ($p_elt, $id, $parent, $tag, $sib, $pos, $text) = @_ ;
my $elt ;
my $sib_elt ;
my %attr ;
if (!defined($id) || !defined($parent)) {
return undef ;
}
if (find_patch($p_elt, $id)) {
print STDERR "$id already exists\n" ;
return undef ;
}
if (find_patch($p_elt, $tag)) {
print STDERR "$tag already exists\n" ;
return undef ;
}
if (defined($sib)) {
$sib_elt = find_patch($p_elt, $sib) ;
if (!$sib_elt) {
print STDERR "Unable to find $sib for relative placement\n" ;
return undef ;
}
$p_elt = $sib_elt ;
} else {
$pos = "last_child" ;
}
$attr{'id'} = $id ;
$attr{'parent'} = $parent ;
if ($tag) {
$attr{'tag'} = $tag ;
}
$elt = new XML::Twig::Elt("patch", \%attr) ;
if ($text) {
$elt->set_text($text) ;
}
$elt->paste($pos, $p_elt) ;
print_patch($elt, "add: ") ;
return $elt ;
}
# remove patch with id from the tree in p_elt
sub del_patch ($$) {
my ($p_elt, $id) = @_ ;
my $elt ;
my $del = 0;
while($elt = find_patch($p_elt, $id)) {
print_patch($elt, "del:") ;
$elt->delete ;
$del++ ;
}
return $del ;
}
sub del_patchset($$) {
my ($p_elt, $tag) = @_ ;
my @elt ;
my $i ;
my $del = 0;
@elt = $p_elt->get_xpath("patchset[\@tag='$tag']") ;
foreach $i (@elt) {
print "del: patchset tag $tag\n" ;
$i->delete ;
$del++ ;
}
return $del ;
}
sub new_patchset($$) {
my ($p_elt, $tag) = @_ ;
my $elt ;
if (find_patchset($p_elt, $tag)) {
print STDERR "$tag already exists\n" ;
return undef ;
}
$elt = new XML::Twig::Elt("patchset", { 'tag' => $tag } ) ;
$elt->paste('last_child', $p_elt);
return $elt ;
}
# send prcs id, returns array with id, parent, text, tag
sub prcs_info ($$) {
my ($prcs_id, $project) = @_ ;
my $ret ;
my @ar ;
my @words ;
$ret = open(F, "prcs info -l -f -r$prcs_id ${project}|");
if (!defined($ret)) {
print STDERR "error running prcs $!\n" ;
return undef ;
}
while(<F>) {
chomp ;
@words = split/: */ ;
if (m/^($project)(\s)(\w+\.\w+).*/) {
$ar[0] = $3 ;
} elsif (m/^Parent-Version/) {
$ar[1] = $words[1] ;
} elsif (m/^(Version-Log:\s*)(.*)/) {
$ar[2] = $2 ;
# add support for :tag_word_or_phrase: in the version log
if ($ar[2] =~ m/^:([\w\-_\+\.]+):/) {
$ar[3] = $1 ;
}
} elsif (m/^Project-Description:/) {
# this comes after the version log, once we get here, we're
# done
close(F) ;
return @ar ;
} else {
# still the version log
$ar[2] .= "$_\n" ;
if (m/^:([\w\-_\+\.]+):/) {
$ar[3] = $1 ;
}
}
}
close(F) ;
return @ar ;
}
# send patchset tag and the element to start looking in
# returns reference to patchset element, or undefined
# if there are more than one, it returns undefined, and prints
# a warning
#
sub find_patchset($$) {
my ($p_elt, $tag) = @_;
my @elt ;
@elt = $p_elt->get_xpath("patchset[\@tag='$tag']") ;
if (@elt) {
if (scalar(@elt) > 1) {
print STDERR "multiple tags for $tag found\n" ;
return undef ;
}
return $elt[0] ;
}
return undef ;
}
sub cleanup_prcs_inc_diff($$) {
my ($cur_dir, $tmpdir) = @_;
chdir $cur_dir ;
# yes, its a hack, and rm -rf is a really bad idea.
#
if ($tmpdir =~ m/tmp/ && -d $tmpdir) {
print STDERR "Removing tmpdir $tmpdir\n" ;
print `rm -rf $tmpdir` ;
} else {
print STDERR "skipping cleanup on $tmpdir\n" ;
}
}
sub make_tmpdirs_inc_diff($$$$) {
my ($cur_dir, $tmpdir, $srcdir, $destdir) = @_ ;
my $ret ;
$ret = mkdir $tmpdir ;
if (!$ret) {
print STDERR "Unable to create temp dir $tmpdir $!\n" ;
return -1 ;
}
$ret = mkdir $srcdir ;
if (!$ret) {
print STDERR "Unable to create temp dir $srcdir $!\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
$ret = mkdir $destdir ;
if (!$ret) {
print STDERR "Unable to create temp dir $destdir $!\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
return 0 ;
}
sub diff_to_file ($$$) {
my ($elt, $project, $patchfile) = @_ ;
my $ret ;
$ret = open(FF, ">$patchfile") ;
if (!defined($ret)) {
print STDERR "Unable to open $patchfile for writing $!\n" ;
return -1 ;
}
select(FF) ;
$ret = gen_prcs_diff($elt, $project) ;
if ($ret) {
close(FF) ;
select(STDOUT) ;
print STDERR "Unable to generate diff\n" ;
return -1 ;
}
close(FF) ;
select(STDOUT) ;
return 0 ;
}
sub find_filenames_in_diff {
my ($outfile, @patches) = @_ ;
my $ret ;
my $p ;
my $index ;
my %h ;
my $name ;
$ret = open(FF, ">$outfile") ;
if (!defined($ret)) {
print STDERR "Unable to open $outfile for writing $!\n" ;
return -1 ;
}
foreach $p (@patches) {
$index = 0 ;
$ret = open(PP, "<$p") ;
if (!defined($ret)) {
print STDERR "Unable to open $p for reading $!\n" ;
close(FF) ;
return -1 ;
}
while(<PP>) {
chomp ;
if (m/^(Index:\s*)(\d+\.\d+\/)(.*)$/) {
if (!defined($h{$3})) {
print FF "$3\n" ;
$h{$3} = 1 ;
}
$index = 1 ;
}
# diff options origfile
if (!$index && m/^(\+\+\+ [^\/]+\/)([^\s]+).*$/) {
if (!defined($h{$2})) {
print FF "$2\n" ;
$h{$2} = 1 ;
}
}
}
close(PP) ;
}
close(FF) ;
return 0 ;
}
sub prcs_checkout_list($$$$) {
my ($destdir, $parent_id, $project, $listfile) = @_ ;
my $ret ;
$ret = open(FF, "$listfile") ;
if (!defined($ret)) {
print STDERR "Unable to open $listfile for reading\n" ;
return -1 ;
}
$ret = chdir($destdir) ;
if (!$ret) {
print STDERR "Unable to change directories to $destdir\n" ;
close(FF) ;
return -1 ;
}
print STDERR "Checking out from $parent_id\n" ;
$ret = open(XX, "|xargs prcs checkout -q -f -r$parent_id $project") ;
if (!defined($ret)) {
print STDERR "Unable to run prcs checkout $!\n" ;
close(FF) ;
return -1 ;
}
while(<FF>) {
print XX ;
}
close(FF) ;
close(XX) ;
$ret = ($? >> 8) ;
if ($ret) {
print STDERR "prcs checkout returned error $ret\n" ;
return -1 ;
}
return 0 ;
}
sub patch_working_dir($$) {
my ($dir, $patch) = @_ ;
my $ret ;
my $out ;
$ret = system("patch -d $dir -p1 -s < $patch") ;
$ret = $ret >> 8 ;
if ($ret) {
print STDERR "Errors running patch $ret\n" ;
return -1 ;
}
return 0 ;
}
# retuns the exit status of the command
#
sub pipe_hash_to_command {
my ($h, $com) = @_ ;
my $ret ;
my $f ;
$ret = open(FF, "|$com") ;
if (!defined($ret)) {
return -1 ;
}
foreach $f (keys(%$h)) {
print FF "$f\n" ;
}
close(FF) ;
$ret = $? >> 8 ;
return $ret ;
}
sub prcs_find_new_or_deleted($$) {
my ($listfile, $project) = @_ ;
my %file_list ;
my %deleted ;
my $ret ;
my $f ;
my $found_deleted = 0 ;
$ret = open(FF, $listfile) ;
if (!defined($ret)) {
print STDERR "Unable to open $listfile $!\n" ;
return -1 ;
}
while(<FF>) {
chomp ;
$file_list{$_} = 1 ;
}
close(FF) ;
# now we've got a hash with all the files
# figure out which ones have been deleted
#
foreach $f (keys(%file_list)) {
if (! -e $f) {
$found_deleted = 1 ;
$deleted{$f} = 1;
delete $file_list{$f} ;
}
}
# now have prcs populate and depopulate fix the repository
$ret = pipe_hash_to_command(\%file_list, "xargs prcs populate -f $project");
if ($ret) {
print STDERR "prcs populate failed $ret($!)\n" ;
return -1 ;
}
if ($found_deleted) {
$ret = pipe_hash_to_command(\%deleted, "xargs prcs depopulate -f $project");
if ($ret) {
print STDERR "prcs populate failed $ret($!)\n" ;
return -1 ;
}
}
return 0 ;
}
sub prcs_checkin_dir ($$$) {
my ($tmpdir, $listfile, $project) = @_ ;
my $ret ;
my $log = "";
my $branch ;
my @checkin_info ;
$ret = chdir($tmpdir) ;
if (!$ret) {
print STDERR "Unable to change directories to $tmpdir\n" ;
return -1 ;
}
$ret = prcs_find_new_or_deleted($listfile, $project) ;
if ($ret) {
return -1 ;
}
print STDERR "Enter new version log, end with '.' on empty line:\n" ;
LINE: while(<STDIN>) {
chomp ;
last LINE if (m/^\.$/) ;
$log .= "$_\n" ;
}
chomp($log) ;
$ret = change_prcs_version_log($project, $log) ;
if ($ret) {
return -1 ;
}
if (defined($args{'branch'})) {
$branch = $args{'branch'} . ".@" ;
} else {
$branch = ".@" ;
}
$ret = system("prcs checkin -q -f -r$branch $project") ;
$ret = $ret >> 8 ;
if ($ret) {
print "prcs checkin failed $ret\n" ;
return -1 ;
}
@checkin_info = prcs_info($branch, $project) ;
if (@checkin_info) {
print "Checkin complete, new id $checkin_info[0]\n" ;
}
return 0 ;
}
sub get_parent_id_override($) {
my ($pinfo) = @_ ;
my $parent_id = undef;
if (defined($args{'parentid'})) {
$parent_id = $args{'parentid'} ;
if (!is_id($pinfo, $parent_id)) {
my $l_elt = find_patch($pinfo, $parent_id) ;
if (!$l_elt) {
print STDERR "Unable to find parent patch $parent_id\n" ;
return -1 ;
}
$parent_id = $l_elt->atts->{'id'} ;
if (!$parent_id) {
print STDERR "Invalid parent id found $parent_id\n" ;
return -1 ;
}
}
}
return $parent_id ;
}
sub gen_prcs_inc_diff {
my ($pinfo, $elt, $project, $newfile) = @_ ;
my $p_elt = $elt->parent ;
my $id = $elt->atts->{'id'} ;
my $parent_id = $elt->atts->{'parent'} ;
my $ret ;
my $oldpatch ;
my $tmpdir = tmpnam() ;
my $patchfile = "$tmpdir/patch" ;
my $srcdir = "$tmpdir/$project.$id" ;
my $destdir = "$tmpdir/$project" ;
my $listfile = "$tmpdir/filelist" ;
my $cur_dir = getcwd ;
$ret = get_parent_id_override($pinfo) ;
if ($ret == -1) {
return -1 ;
} elsif ($ret) {
$parent_id = $ret ;
}
$ret = make_tmpdirs_inc_diff($cur_dir, $tmpdir, $srcdir, $destdir) ;
if ($ret) {
return -1 ;
}
print STDERR "tmpdir is $tmpdir, $srcdir, $destdir\n" ;
# make a diff for the revision in $elt
$elt->atts->{'parent'} = $parent_id ;
$ret = diff_to_file ($elt, $project, $patchfile) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
# extract list of filenames in both patches
#
$ret = find_filenames_in_diff($listfile, $patchfile, $newfile) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
# checkout all the files listed into $destdir,
#
$ret = prcs_checkout_list($destdir, $parent_id, $project, $listfile) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
# copy $destdir to $srcdir, apply corresponding patches
#
$ret = `cp --archive $destdir/* $srcdir` ;
$ret = $ret >> 8 ;
if ($ret) {
print STDERR "failed to copy source files into $srcdir: $ret\n";
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
$ret = chdir($cur_dir) ;
if (!$ret) {
print STDERR "unable to chdir to $tmpdir $!\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
$ret = patch_working_dir($srcdir, $patchfile) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
$ret = patch_working_dir($destdir, $newfile) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
# do the diff or checkin
#
$ret = chdir($tmpdir) ;
if (!$ret) {
print STDERR "unable to chdir to $tmpdir $!\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
if (defined($args{'checkin'})) {
$ret = prcs_checkin_dir($destdir, $listfile, $project) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
} else {
$ret = system("diff -urN --exclude={*.orig,.*.prcs_aux} $project.$id $project");
$ret = $ret >> 8 ;
#
# diff returns 0 for no diffs, 1 for some diffs, 2 for something bad
if ($ret > 1) {
$ret = $ret >> 8 ;
print STDERR "diff failed with exit status $ret\n";
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
}
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return 0 ;
}
#
# generates a single patch from two or more revisions that change the
# same set of files.
#
# step1 build a single patch with all the changes from all revs
# step2 checkout all the affected files from the parent of the first patch
# step3 copy sources to second directory
# step4 apply the big patch
# step5 diff the copy and the chagned versions.
#
sub gen_prcs_compound_diff {
my ($pinfo, $elt, $project, @inc) = @_ ;
my $p_elt = $elt->parent ;
my $id = $elt->atts->{'id'} ;
my $parent_id = $elt->atts->{'parent'} ;
my $ret ;
my $p ;
my $t ;
my $tmpdir = tmpnam() ;
my $destdir = "$tmpdir/$project" ;
my $srcdir ;
my $patchfile = "$tmpdir/patch" ;
my $listfile = "$tmpdir/listfile" ;
my $cur_dir = getcwd ;
$ret = get_parent_id_override($pinfo) ;
if ($ret == -1) {
return -1 ;
} elsif ($ret) {
$parent_id = $ret ;
}
$srcdir = "$tmpdir/$project.$parent_id" ;
# step1, build one big patch with all the revisions appended into it
#
$ret = make_tmpdirs_inc_diff($cur_dir, $tmpdir, $srcdir, $destdir) ;
if ($ret) {
return -1 ;
}
print STDERR "tmpdir is $tmpdir, $srcdir, $destdir\n" ;
$ret = open(FF, ">$patchfile") ;
if (!defined($ret)) {
print STDERR "Unable to open $patchfile for writing $!\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
select(FF) ;
$ret = gen_prcs_diff($elt, $project) ;
if ($ret) {
print STDERR "Unable to generate diff\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
foreach $p (@inc) {
if ($p ne "") {
$t = find_patch($pinfo, $p) ;
if ($t) {
$ret = gen_prcs_diff($t, $project) ;
if ($ret) {
return -1 ;
}
} else {
print STDERR "Unable to find patch $p\n" ;
close(FF) ;
select(STDOUT) ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
}
}
close(FF) ;
select(STDOUT) ;
# extract list of filenames in both patches
#
$ret = find_filenames_in_diff($listfile, $patchfile) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
# checkout all the files listed into $destdir,
# note, this leaves us in the $destdir directory when done.
#
$ret = prcs_checkout_list($destdir, $parent_id, $project, $listfile) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
$ret = `cp --archive $destdir/* $srcdir` ;
$ret = $ret >> 8 ;
if ($ret) {
print STDERR "failed to copy source files into $srcdir: $ret\n";
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
# now the $destdir is all set, apply the diff to it.
#
$p = `patch -p1 -s < $patchfile` ;
$ret = ($? >> 8) ;
print "$p" ;
if ($ret) {
print STDERR "patch returned $ret, continue? [yes,NO]" ;
$p = <STDIN> ;
if (!($p =~ m/^yes$/i)) {
print STDERR "abort diff\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
}
# now we're ready to make the new all inclusive patch.
#
$ret = chdir($tmpdir) ;
if (!$ret) {
print STDERR "unable to chdir to $tmpdir $!\n" ;
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
if (defined($args{'checkin'})) {
$ret = prcs_checkin_dir($destdir, $listfile, $project) ;
if ($ret) {
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
} else {
$ret = system("diff -urN --exclude={*.orig,${project}.prj,.*.prcs_aux} $project.$parent_id $project");
$ret = $ret >> 8 ;
#
# diff returns 0 for no diffs, 1 for some diffs, 2 for something bad
if ($ret > 1) {
$ret = $ret >> 8 ;
print STDERR "diff failed with exit status $ret\n";
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return -1 ;
}
}
cleanup_prcs_inc_diff($cur_dir, $tmpdir) ;
return 0 ;
}
# send an element with the patch in it, and the name of the project
#
sub gen_prcs_diff {
my ($elt, $project) = @_ ;
my $p_elt = $elt->parent ;
my $id = $elt->atts->{'id'} ;
my $parent_id = $elt->atts->{'parent'} ;
my $ret ;
if ($id eq "workingdir") {
$ret = open(F, "prcs diff -r$parent_id -N -P -f $project|") ;
} else {
$ret = open(F, "prcs diff -r$parent_id -r$id -N -P -f $project|") ;
}
if (!defined($ret)) {
print STDERR "Unable to generate patch for $id: $!\n" ;
return -1 ;
}
while(<F>) {
print ;
}
close(F) ;
$ret = $? >> 8 ;
if ($ret != 1 && $ret != 0) {
print STDERR "Error $ret running prcs diff\n" ;
return -1 ;
}
return 0 ;
}
sub change_prcs_version_log($$) {
my ($project, $log) = @_ ;
my $ret ;
$ret = open(IF, "<${project}.prj");
if (!defined($ret)) {
print STDERR "Unable to update version info in $project $!\n" ;
return -1;
}
$ret = open(OF, ">${project}.prj.new") ;
if (!defined($ret)) {
print STDERR "Unable to update version info in $project $!\n" ;
return -1;
}
while(<IF>) {
if (m/^\(New-Version-Log.*$/) {
print OF "\(New-Version-Log \"$log\"\)\n" ;
} else {
print OF ;
}
}
close(IF) ;
close(OF);
$ret = rename "${project}.prj.new", "${project}.prj" ;
if (!$ret) {
print STDERR "Unable to install new project file $!\n" ;
return -1;
}
return 0 ;
}
sub apply_prcs_diff {
my ($elt, $project, $new_branch) = @_ ;
my $log ;
my $ret ;
my $failed = 0;
my $line ;
my $branch ;
my $tmpfile = tmpnam() ;
if ($elt->text) {
$log = $elt->text ;
} else {
$log = $elt->atts->{'tag'} ;
}
$ret = open(FF, ">$tmpfile") ;
if (!defined($ret)) {
print STDERR "unable to open temp $tmpfile $!\n" ;
return -1 ;
}
select(FF) ;
$ret = gen_prcs_diff($elt, $project) ;
close(FF) ;
select(STDOUT) ;
if ($ret) {
# error during the gen_prcs_diff
unlink "$tmpfile" ;
return -1 ;
}
$ret = open(FF, "patch -f -s -p1 < $tmpfile|") ;
if (!defined($ret)) {
print STDERR "error running patch on $tmpfile $!\n" ;
unlink "$tmpfile" ;
return -1 ;
}
while(<FF>) {
print ;
if (m/.*-- saving rejects/) {
$failed++ ;
}
}
close(FF) ;
unlink "$tmpfile" ;
if ($failed > 0) {
print "\n$failed hunks failed, continue?[yes/NO] " ;
$line = <STDIN> ;
chomp $line ;
if (!($line =~ m/yes/)) {
return -$failed;
}
}
#update the prcs project file to include the new version info
#
$ret = change_prcs_version_log($project, $log) ;
if ($ret) {
return -1 ;
}
$ret = `prcs populate -d -f` ;
if ($? >> 8) {
print STDERR "failure during populate $!\n" ;
return -1 ;
}
print $ret ;
if (!defined($new_branch)) {
#$new_branch = $elt->atts->{'id'} ;
#$new_branch =~ s/(\d+).*/$1/ ;
$new_branch = "" ;
}
$ret = `prcs checkin -r${new_branch}.@ -f $project` ;
if ($? >> 8) {
print STDERR "failure during checkin $!\n" ;
return -1 ;
}
if (defined($args{'destset'})) {
my $patchtag = $elt->atts->{'tag'} ;
my %largs ;
my @info ;
if ($failed > 0) {
$patchtag = $patchtag . "-mod" ;
} else {
$patchtag = $patchtag . "-w" ;
}
@info = prcs_info("${new_branch}.@", $project) ;
if (!@info) {
print STDERR "prcs info failed for ${new_branch}.@\n" ;
return -1 ;
}
$largs{'patch'} = $patchtag ;
$largs{'set'} = $args{'destset'} ;
$largs{'id'} = $info[0] ;
$ret = _process_add($elt->root, $project, \%largs) ;
if ($ret != 1) {
return -1 ;
}
} else {
$ret = 0 ;
}
print_patch($elt, "apply:") ;
return $ret ;
}
# $elt is the patch set to apply,
# $project is the project name
# $new_branch is optional, supply if you want to apply onto a different
# branch than the current one
# returns 0 on success, -1 on failure
#
sub apply_patchset {
my ($elt, $project, $new_branch) = @_ ;
my $child ;
my $ret ;
my $tag = $elt->atts->{'tag'} ;
my $num = 0 ;
my $flush = 0 ;
print "Applying patchset $tag\n" ;
$child = $elt->first_child('patch');
while($child) {
$ret = apply_prcs_diff($child, $project, $new_branch) ;
if ($ret < 0) {
print STDERR "Error while applying " ;
print STDERR $child->atts->{'id'} ;
print STDERR " abort\n" ;
return -1 ;
} else {
if ($ret > 0) {
$flush = 1 ;
}
}
$child = $child->next_sibling('patch') ;
$num++ ;
}
print "done. $num patches applied\n" ;
return $flush ;
}
sub gen_patchset_diff($$) {
my ($elt, $project) = @_ ;
my $child ;
my $first_patch ;
my $ret ;
my @inc = () ;
my $i = 0 ;
my $compound = defined($args{'compound'}) ;
$child = $elt->first_child('patch') ;
$first_patch = $child ;
while($child) {
if ($compound) {
if ($i == 0) {
$i++ ; # first patch is stored in $first_patch
} else {
$inc[$i] = $child->atts->{'id'} ;
$i++ ;
}
} else {
$ret = gen_prcs_diff($child, $project) ;
if ($ret) {
print STDERR "Error generating patch id " ;
print STDERR $child->atts->{'id'} ;
print STDERR " abort\n" ;
return -1 ;
}
}
$child = $child->next_sibling('patch') ;
}
if ($compound) {
return gen_prcs_compound_diff($elt->parent, $first_patch, $project,
@inc) ;
}
return 0 ;
}
# returns 1 if the string starts represents an id, 0 if it represents
# an existing tag name
sub is_id($$) {
my ($p_elt, $id) = @_;
my $elt ;
$elt = _find_patch($p_elt, $id, 'tag') ;
if ($elt) {
return 0 ;
}
return 1 ;
}
sub _find_patch($$$) {
my ($p_elt, $id, $attr) = @_ ;
my @elt ;
my $p ;
my $first_id ;
@elt = $p_elt->get_xpath(".//patch[\@$attr='$id']") ;
if (@elt) {
if (scalar(@elt) > 1) {
# if there are more than one that match, make sure they are
# all the same patch
#
$first_id = $elt[0]->atts->{'id'} ;
foreach $p (@elt) {
if ($p->atts->{'id'} ne $first_id) {
print STDERR "multiple tags for $id found\n" ;
return undef ;
}
}
}
return $elt[0] ;
}
return undef ;
}
# finds patch in p_elt with id, returns a ref to the element, or
# undef. If multiple patches are found with the same id, undef
# is returned
#
sub find_patch($$) {
my ($p_elt, $id) = @_ ;
my $elt ;
$elt = _find_patch($p_elt, $id, 'tag') ;
if ($elt) {
return $elt ;
}
$elt = _find_patch($p_elt, $id, 'id') ;
return $elt ;
}
sub save_twig ($$) {
my ($twig, $file) = @_ ;
open(F, ">$file") || die "save_twig unable to open output file $!\n" ;
$twig->print(\*F) ;
close(F) ;
}
# these process the various command lines. They return:
# zero if everything worked
# one if everything worked and you need to flush file changes
# < 0 if there was an error
#
sub process_add($$) {
my ($pinfo, $project) = @_ ;
_process_add($pinfo, $project, \%args) ;
}
sub _process_add($$$) {
my ($pinfo, $project, $rargs) = @_ ;
my $p_elt ;
my $elt ;
my $patchset = '';
my $patch = '' ;
my $id ;
my @patch_attrs ;
my $tag ;
my $text ;
my $pos ;
my $sib ;
if (defined($rargs->{'set'})) {
$patchset = $rargs->{'set'} ;
$p_elt = find_patchset($pinfo, $patchset) ;
if (!$p_elt) {
$p_elt = new_patchset($pinfo, $patchset) ;
if (!$p_elt) {
print STDERR "Unable to add patchset $patchset\n" ;
return -1 ;
}
}
} else {
print STDERR "patchset name required to add a patch\n" ;
return -1 ;
}
if (!$p_elt) {
print STDERR "Unable to find patchset $patchset\n" ;
return -1 ;
}
if (defined($rargs->{'patch'})) {
$patch = $rargs->{'patch'} ;
} else {
# we're only adding the patchset, we're done
return 1;
}
# allow -patch id_num usage, where we pull the tag off of the
# prcs new version log
#
# otherwise, the usage is -patch tag -id num
#
if (!defined($rargs->{'id'})) {
$id = $patch ;
} else {
$tag = $patch ;
if (!defined($rargs->{'id'})) {
print STDERR "id required when adding a patch\n" ;
return -1 ;
}
$id = $rargs->{'id'} ;
}
@patch_attrs = prcs_info($id, $project) ;
if (!@patch_attrs) {
print STDERR "prcs info failed for $id\n" ;
return -1 ;
}
# $text = $patch_attrs[2] ;
# fill in the tag if we didn't get it from the command line
# don't include the version info, its annoying, and a duplicate
# of data available through prcs
#
$text = undef ;
if (!$tag) {
if (defined($patch_attrs[3])) {
$tag = $patch_attrs[3] ;
} else {
$tag = $patch_attrs[2] ;
}
}
if (defined($rargs->{'before'})) {
$sib = $rargs->{'before'} ;
$pos = "before" ;
} elsif (defined($rargs->{'after'})) {
$sib = $rargs->{'after'} ;
$pos = "after" ;
}
$elt = new_patch($p_elt, $id, $patch_attrs[1], $tag, $sib, $pos, $text) ;
if (!$elt) {
print STDERR "Unable to add patch $patch\n" ;
return -1 ;
}
return 1 ;
}
sub process_remove($$) {
my ($pinfo, $project) = @_ ;
my $p_elt ;
my $patchset ;
my $patch ;
my $ret ;
if (defined($args{'set'})) {
$patchset = $args{'set'} ;
$p_elt = find_patchset($pinfo, $patchset) ;
if (!$p_elt) {
print STDERR "Unable to find patchset $patchset\n" ;
return -1 ;
}
} else {
print STDERR "please supply a patchset name\n" ;
return -1 ;
}
if (!defined($args{'patch'})) {
# we are deleting the entire patchset
#
$ret = del_patchset($pinfo, $patchset) ;
if ($ret == 0) {
print STDERR "Unable to delete patchset $patchset\n" ;
return -1 ;
}
return 1 ;
}
if (!defined($args{'patch'})) {
print STDERR "no patch name supplied for deletion\n" ;
return -1 ;
}
$patch = $args{'patch'} ;
$ret = del_patch($p_elt, $patch) ;
if ($ret == 0) {
print STDERR "Unable to delete patch $patch\n" ;
return -1 ;
}
return 1;
}
sub process_apply($$) {
my ($pinfo, $project) = @_ ;
my $p_elt ;
my $elt ;
my $ret ;
my $patchset ;
my $patch ;
my $branch ;
if (defined($args{'branch'})) {
$branch = $args{'branch'} ;
}
if (defined($args{'set'})) {
$patchset = $args{'set'} ;
$p_elt = find_patchset($pinfo, $patchset) ;
if (!$p_elt) {
print STDERR "Unable to find patchset $patchset\n" ;
return -1 ;
}
} elsif (defined($args{'patch'})) {
$p_elt = $pinfo ;
} else {
print STDERR "please supply a patchset name\n" ;
return -1 ;
}
if (!defined($args{'patch'})) {
# we are applying the entire patchset
#
return apply_patchset($p_elt, $project, $branch) ;
}
$patch = $args{'patch'} ;
$elt = find_patch($p_elt, $patch) ;
if (!$elt) {
print STDERR "Unable to find patch $patch\n" ;
return -1 ;
}
$ret = apply_prcs_diff($elt, $project, $branch) ;
if ($ret < 0) {
print STDERR "Error appling patch $patch\n" ;
return -1 ;
}
return $ret ;
}
sub process_change($$) {
my ($pinfo, $project) = @_ ;
print "Change not yet coded. Carefully edit the file yourself\n" ;
return 0 ;
}
sub process_diff($$) {
my ($pinfo, $project) = @_ ;
my $p_elt ;
my $elt ;
my $ret ;
my $patchset ;
my $patch ;
my @inc ;
if (defined($args{'set'})) {
$patchset = $args{'set'} ;
$p_elt = find_patchset($pinfo, $patchset) ;
if (!$p_elt) {
print STDERR "Unable to find patchset $patchset\n" ;
return -1 ;
}
} elsif (defined($args{'patch'})) {
$p_elt = $pinfo ;
} else {
print STDERR "please supply a patchset name\n" ;
return -1 ;
}
if (!defined($args{'patch'})) {
return gen_patchset_diff($p_elt, $project);
} else {
$patch = $args{'patch'} ;
}
$elt = find_patch($p_elt, $patch) ;
if (!$elt) {
if ($patch eq "workingdir") {
$elt = new XML::Twig::Elt("patch", { 'id' => 'workingdir',
'parent' => '.'} ) ;
} else {
print STDERR "Unable to find patch $patch\n" ;
return -1 ;
}
}
if (defined($args{'compound'})) {
@inc = @{$args{'compound'}} ;
return gen_prcs_compound_diff($pinfo, $elt, $project, @inc) ;
}
if (defined($args{'inc'}) && -f $args{'inc'}) {
return gen_prcs_inc_diff($pinfo, $elt, $project, $args{'inc'}) ;
}
return gen_prcs_diff($elt, $project) ;
}
sub process_list($$) {
my ($pinfo, $project) = @_ ;
my $p_elt ;
my $elt ;
my $patch ;
my $patchset ;
if (defined($args{'set'})) {
$patchset = $args{'set'} ;
$p_elt = find_patchset($pinfo, $patchset) ;
if (!$p_elt) {
print STDERR "Unable to find patchset $patchset\n" ;
return -1 ;
}
} elsif (defined($args{'patch'})) {
$p_elt = $pinfo ;
} else {
# just list all patchsets
#
$p_elt = $pinfo->first_child('patchset') ;
while ($p_elt) {
print_patchset($p_elt) ;
$p_elt = $p_elt->next_sibling('patchset') ;
}
return 0 ;
}
if (defined($args{'patch'})) {
$patch = $args{'patch'};
$elt = find_patch($p_elt, $patch) ;
if (!$elt) {
print STDERR "Unable to find patch $patch\n";
return -1 ;
}
print_patch($elt) ;
return 0 ;
}
print_patchset($p_elt) ;
return 0 ;
}
sub process_copy($$) {
my ($pinfo, $project) = @_ ;
my $dest ;
my $elt ;
my $patch ;
if (!defined($args{'patch'})) {
print STDERR "source patch must be supplied\n" ;
return -1 ;
}
if (!defined($args{'set'})) {
print STDERR "destination patchset must be supplied\n" ;
return -1 ;
}
$patch = $args{'patch'} ;
$elt = find_patch($pinfo, $patch) ;
if (!$elt) {
print STDERR "Unable to find $patch for copy\n" ;
return -1 ;
}
$args{'patch'} = $elt->atts->{'tag'};
$args{'id'} = $elt->atts->{'id'} ;
return process_add($pinfo, $project) ;
}
# returns an element for the patch in the -patch arg, honoring -set
# if supplied, otherwise looking through the whole tree
#
sub find_patch_from_args($) {
my ($pinfo) = @_ ;
my $ret ;
my $p_elt ;
my $elt ;
if (defined($args{'set'})) {
$p_elt = find_patchset($pinfo, $args{'set'}) ;
if (!$p_elt) {
print STDERR "Unable to find patchset $args{'set'}\n" ;
return undef ;
}
} else {
$p_elt = $pinfo ;
}
if (defined($args{'patch'})) {
$elt = find_patch($pinfo, $args{'patch'}) ;
if (!$elt) {
print STDERR "Unable to find patch $args{'patch'}\n" ;
return undef ;
}
return $elt ;
} else {
print STDERR "No patch name specified\n" ;
return undef ;
}
return undef ;
}
sub process_info($$) {
my ($pinfo, $project) = @_ ;
my $elt ;
my $id ;
my @ar ;
$elt = find_patch_from_args($pinfo) ;
if (!$elt) {
return -1 ;
}
$id = $elt->atts->{'id'} ;
system("prcs info -f -l -r$id $project") ;
return 0 ;
}
sub process_checkout($$) {
my ($pinfo, $project) = @_ ;
my $elt ;
my $id ;
$elt = find_patch_from_args($pinfo) ;
if (!$elt) {
return -1 ;
}
$id = $elt->atts->{'id'} ;
system("prcs checkout -f -r$id $project") ;
return 0 ;
}
sub print_usage {
my $p = $0 ;
$p =~ s/^.*\///g ;
print "usage:\n" ;
print "$p create -project name [-file file]\n" ;
print "$p add -set name -patch name -id num [-before patch] [-after patch]\n" ;
print "$p add -set name\n" ;
print "$p copy -set dest_set -patch src_patch [-before patch] [-after patch]\n" ;
print "$p remove -set name [-patch name]\n" ;
print "$p apply -patch name [-branch id]\n" ;
print "$p apply -set name [-patch name] [-branch id]\n" ;
print "$p diff -patch name [-compound name ... [-parentid id]]\n" ;
print "$p diff -set name [-patch name [-compound name ... [-parentid id]]]\n" ;
print "$p diff -patch name [-inc name ... [-parentid id]]\n" ;
print "$p diff -set name [-patch name [-inc name ... [-parentid id]]]\n" ;
print "$p list [-set name] [-patch name]\n" ;
print "$p checkout [-set name] -patch name\n" ;
print "$p info [-set name] -patch name\n" ;
print "$p edit\n"
}
my $action = '' ;
my $process_func ;
sub action_arg {
my ($a) = @_ ;
if ($a eq "add") {
$process_func = \&process_add ;
} elsif ($a eq "remove") {
$process_func = \&process_remove ;
} elsif ($a eq "apply") {
$process_func = \&process_apply ;
} elsif ($a eq "change") {
$process_func = \&process_change ;
} elsif ($a eq "diff") {
$process_func = \&process_diff ;
} elsif ($a eq "create") {
$process_func = \&process_create ;
} elsif ($a eq "list") {
$process_func = \&process_list ;
} elsif ($a eq "edit") {
$process_func = \&process_edit ;
} elsif ($a eq "copy") {
$process_func = \&process_copy ;
} elsif ($a eq "info") {
$process_func = \&process_info ;
} elsif ($a eq "checkout") {
$process_func = \&process_checkout ;
} elsif ($action && !defined($args{'patch'})) {
# allow shortcuts for these actions, making the default -patch
#
if (($action eq "diff" ||
$action eq "add" ||
$action eq "apply" ||
$action eq "remove" ||
$action eq "info" ||
$action eq "list" ||
$action eq "copy" ||
$action eq "checkout")) {
$args{'patch'} = $a;
} else {
die "\nUnknown command $a\n" ;
}
} else {
die "\nUnknown command $a\n" ;
}
$action = $a ;
}
my $ret;
$ret = GetOptions(\%args,
'after=s',
'before=s',
'branch=s',
'compound:s@',
'checkin',
'destset=s',
'file=s',
'id=s',
'inc=s',
'parentid=s',
'patch=s',
'project=s',
'set=s',
'tag=s',
'version' => sub { print "$0: version $version\n" ; },
'<>' => \&action_arg
);
if ($ret == 0 || !defined($process_func)) {
print_usage() ;
die ;
}
my $file = "patch.info" ;
my $twig = new XML::Twig(pretty_print => "indented") ;
my $pinfo ;
my $project ;
if (! -f $file && exists($ENV{'PRCS_REPOSITORY'})) {
$file = "$ENV{'PRCS_REPOSITORY'}/patch.info" ;
}
if (defined($args{'file'})) {
$file = $args{'file'} ;
}
if ($action eq "create") {
if (-f $file) {
print STDERR "$file already exists, unable to create project\n" ;
exit 1 ;
}
if (!defined($args{'project'})) {
print STDERR "project name must be specified\n" ;
exit 1 ;
}
open(FF, ">$file") or die "Can't create $file\n" ;
print FF "<patchinfo project=\"$args{'project'}\"></patchinfo>\n" ;
close(FF) ;
exit(0) ;
}
print STDERR "using file $file\n" ;
if (-f $file) {
if ($action eq "edit") {
if (defined $ENV{'EDITOR'}) {
exec "$ENV{'EDITOR'} $file";
} else {
exec "vi $file" ;
}
}
$twig->parsefile($file) ;
$pinfo = $twig->first_elt("patchinfo") ;
if (!$pinfo) {
die "patchinfo element not found\n" ;
}
if ($pinfo) {
$project = $pinfo->atts->{'project'} ;
print STDERR "project is $project\n" ;
}
$ret = &$process_func($pinfo, $project) ;
if ($ret < 0) {
print STDERR "Error processing $action\n" ;
exit(-1) ;
} elsif ($ret == 1) {
print STDERR "saving changes to $file\n" ;
save_twig($twig, $file) ;
}
exit(0) ;
} else {
print STDERR "$file not found\n" ;
exit(1) ;
}
syntax highlighted by Code2HTML, v. 0.9.1