#!/usr/bin/env perl # p4rollback.pl - roll back a submitted Perforce changelist. $USAGE=<<End_of_USAGE; Usage: $0 [-d] [-Di] [-Ds] [-Dt] changenumber The optional flags conform to those used by "p4 integ". End_of_USAGE die $USAGE if (scalar @ARGV < 1); # Perforce client settings are read from the current CLI environment. # # Be wary using this script on Windows with the Cygwin version of Perl. Funny # things happen with the environment that can cause some commands to end up # going to the wrong server. # # This script is based on 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 behavior, much like the options for # non-conforming adds and deletes in p4 integrate: # # -Ds: Rolling back an edit to a subsequently deleted file will re-add the # file at the pre-edit rev. # -Dt: Rolling back an add of a subsequently edited file will delete the file. # -Di: Rolling back a delete of a subsequently re-added file will open it for # edit at the pre-delete rev. # # 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; jobs which were closed by a changelist that # you're rolling back will remain closed, since the script doesn't have any # easy way to determine what the old status was. # # This script also doesn't touch filetypes, since filetypes are easy to change # but hard to "resolve". Just use "p4 edit -t OLDFILETYPE" to rollback # filetype changes. # # As always, look at what you're submitting before you submit, 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 a C++ API implementation under: # //guest/sam_stafford/rollback/... # # This script is not supported by Perforce Software, Inc. All warranties # are hereby disclaimed, et cetera. $change = 0; $ncadds = 0; $ncdels = 0; $ncedit = 0; $rbchange = 0; $addsskipped = 0; $delsskipped = 0; $editskipped = 0; $editsrolled = 0; $addsrolled = 0; $delsrolled = 0; #Step 0: Parse args. #Not a lot of error handling here. GIGO. foreach ( @ARGV ) { die $USAGE if ( /-h/ ); if ( /-d/ ) { $ncadds = 1; $ncdels = 1; $ncedit = 1; } if ( /-Di/ ) { $ncdels = 1; } if ( /-Ds/ ) { $ncadds = 1; } if ( /-Dt/ ) { $ncedit = 1; } if ( /([0-9]+)/ ) { $change = $1; } } die $USAGE if ( $change <= 1 ); $prev = $change - 1; #Step one half: Get list of rollback files. $_ = `p4 info`; if ( !/Client name: (.+)\n/ ) { print $_; die "Error talking to Perforce server. Bailing.\n"; } $client = $1; $_ = `p4 files \/\/$client\/\.\.\.\@$change,$change`; @files = split /\n/, $_; #Step 1: create new changelist to hold the rollback changes. $_ = `p4 change -o`; s/<enter description here>/Rollback change $change./; s/\t\/\/[^\n]+\n//gm; open TEMP, ">p4rollbacktempfile.tmp" or die "Can't make temp file."; print TEMP $_; close TEMP; $_ = `p4 change -i < p4rollbacktempfile.tmp`; if ( /^Change ([0-9]+) created.*/ ) { $rbchange = $1; } 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 $ncdels == 0 ) #skip nonconf add { print "Skipping re-add of $1 because it has been modified.\n"; $delsskipped++; 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++; } #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 $ncadds == 0 ) #skip nonconf del { print "Skipping delete of $1 because it has been modified.\n"; $addsskipped++; next; } print "Rolling back add of $1.\n"; `p4 flush \"$1\"`; system( "p4 delete -c $rbchange \"$1\"" ); $addsrolled++; } #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; } # Test to see if file is deleted at head rev. $head = `p4 files \"$1\"`; chomp $head; $cmd = "edit"; if ( $head =~ /(.*)#[0-9]+ - delete change.*/ ) { if ( $ncedit == 0 ) # skip nonconf add { print "Unable to rollback edit of $1 because it has been deleted.\n"; $editskipped++; next; } $cmd = "add"; } 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 $cmd -c $rbchange \"$1\"`; `p4 sync \"$1\@$change\"`; `p4 resolve -ay \"$1\"`; $editsrolled++; } # Step the last: Summary of what has been done. print "\nSUMMARY:\n"; if ( $editsrolled ) { print "Rolled back $editsrolled edits.\n"; } if ( $editskipped ) { print "Skipped $editskipped edit rollbacks of deleted files. Use -d or -Dt to open for add.\n"; } if ( $addsrolled ) { print "Rolled back $addsrolled adds\/branches.\n"; } if ( $addsskipped ) { print "Skipped $addsskipped add rollbacks of edited files. Use -d or -Ds to open for delete.\n"; } if ( $delsrolled ) { print "Rolled back $delsrolled deletes.\n"; } if ( $delsskipped ) { print "Skipped $delsskipped delete rollbacks of re-added files. Use -d or -Di to open for edit.\n"; } if ( $editsrolled == 0 and $addsrolled == 0 and $delsrolled == 0 ) { `p4 change -d $rbchange`; } else { print "\nThe above changes have been put into changelist $rbchange. Submit when ready.\n"; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#12 | 6443 | Sam Stafford |
Fix a bug with running rollback while there are already files in the default changelist. The regex I was using to clear the files from the "change -o" output was malformed. Seems to work now. Thanks go to Doreen for finding the bug, and to Matt for his superior regex skillz. |
||
#11 | 6296 | Sam Stafford | Add pointer to rollback in p4rollback.pl comments, replacing the exhortation for other users to come up with their own API-based implementations. | ||
#10 | 6255 | Sam Stafford |
Fixing up the comments a bit. Had some annoying typos, and the -D options needed some clarification. |
||
#9 | 6114 | Sam Stafford |
Use "p4 info" to get client name instead of "p4 set". "p4 set" doesn't work with "set" on Windows. |
||
#8 | 5906 | Sam Stafford |
Some minor fixes to p4rollback.pl. Make sure that target changelist is empty (rather than inheriting files from the default changelist, which defeats the purpose of putting the work into its own numbered change), and eliminate some extraneous linebreaks from the output. |
||
#7 | 5843 | Sam Stafford | Superfluous space. | ||
#6 | 5785 | Sam Stafford | Add caveat about filetypes to the comments. | ||
#5 | 5784 | Sam Stafford |
Improvements to p4rollback.pl: 1) -Dx flags rethought and rearranged to be more consistent with their meanings in "p4 integ". The logic I'm following now is that a rollback is like integrating from before the bad change into the head rev. Apologies to anyone who had gotten used to how the flags worked before. 2) Check to see if file is deleted at head rev. If so, the -Dt flag can be used to re-add the "last good" rev as the new head rev, without a resolve. (If you don't check for this and try to schedule the usual resolve, you end up getting stuck with an unsubmittable file, since you can't resolve an edit vs a delete. Nasty.) 3) Files not in your client view will no longer be processed. (I made this change after noticing during testing that the "p4 files" command was hitting a remote depot -- confining the command to the client view makes that easy to dodge.) Awesome. |
||
#4 | 5236 | Sam Stafford |
Fixed an oopsie that would cause the script to fail when attempting to roll back adds/branches of files with spaces in their names. (Thanks go to Diane Holt for spotting this problem.) |
||
#3 | 5095 | Sam Stafford |
Changes from Mark Moraes - header so it can be called directly on Unix, and ability to get the usage message from the command line. |
||
#2 | 5089 | Sam Stafford |
Remove the final resolve schedule from p4rollback.pl. There's no real need for it, and it prevents you from scheduling incremental resolves after you run the script. |
||
#1 | 4535 | Sam Stafford |
Changelist rollback script. See comments for usage notes and stern warnings. |