# p4rollback.pl - roll back a submitted Perforce changelist. # Usage: perl p4rollback.pl [-d] [-Ds] [-Dt] change # The optional flags conform to those used by "p4 integ". # This script is basically a translation of the instructions given in # Tech Note 14, "How do you back out a change?" # http://www.perforce.com/perforce/technotes/note014.html # When executed, the script will examine each file in the target changelist, # and figure out what action to take to roll it back. For adds and deletes we # diverge here a bit from TN 14; the script will refuse to roll back an add or # delete if the file has been modified since then (edited or re-added), since # we'd then be implicitly rolling back changes other than the target change. # The -Ds and -Dt options override that reluctance, much like the non-conforming # adds and deletes in p4 integrate (which are there for the same reason). # At the end of it all, the script will leave you with a new numbered changelist # and possibly some unscheduled resolves (if you backed out any old edits). # Reasonable attempts are made to check for errors, but common sense must apply. # For example, if you try to roll back files that are outside of your client view, # or that you don't have write access to, the operations on those files will fail. # Similarly, if the files are opened for edit, they've been flushed, modified without # being opened for edit, et cetera, you'll get unexpected results. # This script doesn't touch fixes. Presumably you'll know what you want to do with # those yourself. # And as always, look at what you're submitting before you're submitting, including # testing the build if applicable. Especially if you had to resolve new edits against # the rollback results. # You might notice that the script runs a bit slowly - that's in part because it's # checking each individual file for things like non-conforming adds/deletes, and in # part because I'm calling p4 instead of using API functions. There's plenty of # room for optimization. If you feel like optimizing, I encourage you to submit your # results to the Public Depot so we can all benefit. # This isn't an officially supported script. Direct queries to samwise@perforce.com # and I'll help out if I can. $change = 0; $ncadds = 0; $ncdels = 0; $rbchange = 0; $addsskipped = 0; $delsskipped = 0; $editsrolled = 0; $addsrolled = 0; $delsrolled = 0; #Step 0: Parse args. #Not a lot of error handling here. GIGO. foreach ( @ARGV ) { if ( /-d/ ) { $ncadds = 1; $ncdels = 1; } if ( /-Ds/ ) { $ncdels = 1; } if ( /-Dt/ ) { $ncadds = 1; } if ( /([0-9]+)/ ) { $change = $1; } } $prev = $change - 1; #Step one half: Get list of rollback files. $_ = `p4 -s changes -m1`; if ( /exit: 1/ ) { die "Error talking to Perforce server. Bailing.\n"; } $_ = `p4 files \@$change,$change`; @files = split /\n/, $_; #Step 1: create new changelist to hold the rollback changes. $_ = `p4 change -o`; s//Rollback change $change./; open TEMP, ">p4rollbacktempfile.tmp" or die "Can't make temp file."; print TEMP $_; close TEMP; open NEWCHANGE, "p4 change -i < p4rollbacktempfile.tmp |" or die "Can't create new changelist."; while ( ) { if ( /^Change ([0-9]+) created.*/ ) { $rbchange = $1; } } close NEWCHANGE; unlink ( "p4rollbacktempfile.tmp " ); # Step 1 accomplished. # $rbchange is the change that's doing the work. # @files is the raw "p4 files" output of what to roll back. # Step 2: Roll back deletes. foreach ( @files ) { $file = $_; chomp $file; if ( $file !~ /(.*)#[0-9]+ - delete change.*/ ) { next; } #test whether file has been readded $head = `p4 files \"$1\"`; chomp $head; if ( $head ne $file and $ncadds == 0 ) #skip nonconf add { print "Skipping re-add of $1 because it has been modified.\n"; $addsskipped++; next; } print "Rolling back delete of $1.\n"; `p4 sync \"$1\@$prev\"`; if ( $head eq $file or $head =~/(.*)#[0-9]+ - delete change.*/ ) { system( "p4 add -c $rbchange \"$1\"" ); } else { system( "p4 edit -c $rbchange \"$1\"" ); } $delsrolled++; print ( "\n" ); } #step 3: Roll back adds/branches/imports. foreach ( @files ) { $file = $_; chomp $file; if ( $file !~ /(.*)#[0-9]+ - add change.*/ and $file !~ /(.*)#[0-9]+ - branch change.*/ and $file !~ /(.*)#[0-9]+ - import change.*/ ) { next; } #test whether file has been edited $head = `p4 files \"$1\"`; chomp $head; if ( $head ne $file and $ncdels == 0 ) #skip nonconf del { print "Skipping delete of $1 because it has been modified.\n"; $delsskipped++; next; } print "Rolling back add of $1.\n"; `p4 flush $1`; system( "p4 delete -c $rbchange \"$1\"" ); $addsrolled++; print ( "\n" ); } #step 4: Roll back edits/integs/purges. foreach ( @files ) { $file = $_; chomp $file; if ( $file !~ /(.*)#[0-9]+ - edit change.*/ and $file !~ /(.*)#[0-9]+ - integrate change.*/ and $file !~ /(.*)#[0-9]+ - purge change.*/ ) { next; } print "Rolling back edit of $1.\n"; $sync = `p4 sync \"$1\@$prev\"`; if ( $sync =~ /^$1#[0-9] - deleted as .*/ ) { #Must have been a purge. print "Can't roll back $1 because nothing exists at change $prev. (tempobj?)\n"; next; } `p4 edit -c $rbchange \"$1\"`; `p4 sync \"$1\@$change\"`; `p4 resolve -ay \"$1\"`; $editsrolled++; print ( "\n" ); } # Step the last: Summary of what has been done. print "\nSUMMARY:\n"; if ( $editsrolled ) { print "Rolled back $editsrolled edits.\n"; } if ( $addsrolled ) { print "Rolled back $addsrolled adds\/branches.\n"; } if ( $delsskipped ) { print "Skipped $delsskipped add\/branch rollbacks. Use -d or -Ds to do 'em anyway.\n"; } if ( $delsrolled ) { print "Rolled back $delsrolled deletes.\n"; } if ( $addsskipped ) { print "Skipped $addsskipped delete rollbacks. Use -d or -Dt to do 'em anyway.\n"; } print "\nThe above changes have been put into changelist $rbchange. Submit when ready.\n";