#include "RollBack.h"
#include "RBFile.h"
#include <clientapi.h>
#include <options.h>
#include <vararray.h>
#include "RBFilter.h"
#include "RBUserFiles.h"
RollBack::RollBack( const char* cmd )
{
cmdName = new StrBuf( cmd );
backLoaded = false;
tagged = false;
client = 0x0;
ui = 0x0;
files = new VarArray;
cursor = 0;
addDeleted = editReadded = delEdited = false;
change = new StrBuf;
}
RollBack::~RollBack(void)
{
for ( int i = 0 ; i < files->Count() ; i++ )
{
delete (RBFile*)files->Get( i );
}
delete cmdName;
delete files;
delete change;
}
void RollBack::Run( ClientApi* c, ClientUser* u, int ac, char** av )
{
client = c;
ui = u;
int argc = ac;
char** argv = av;
//Argument parsing.
Error e;
StrBuf msg( "Usage: " );
msg.Append( cmdName );
msg.Append( " [ -c pendChange -d -D<flag> ] submittedChange" );
ErrorId usage = { E_FAILED, msg.Text() };
Options opts;
opts.Parse( argc, argv, "c:dD.", OPT_ONE, usage, &e );
if ( e.Test() )
{
ui->Message( &e );
return;
}
if ( StrPtr* ch = opts['c'] )
change->Set( ch );
if ( opts['d'] )
addDeleted = editReadded = delEdited = true;
if ( StrPtr* dFlags = opts['D'] )
{
if ( dFlags->Contains( StrRef( "i" ) ) )
editReadded = true;
if ( dFlags->Contains( StrRef( "s" ) ) )
delEdited = true;
if ( dFlags->Contains( StrRef( "t" ) ) )
addDeleted = true;
}
// Populating the list of files to be rolled back.
RBUserFiles rbUi( this );
StrBuf arg;
arg.Append( "//" );
arg.Append( client->GetClient().Text() );
arg.Append( "/...@" );
arg.Append( argv[0] );
arg.Append( ",@" );
arg.Append( argv[0] );
char* fArgs[1];
fArgs[0] = arg.Text();
client->SetArgv( 1, fArgs );
client->Run( "files", &rbUi );
backLoaded = true;
//Get information about head revisions where applicable.
for ( int i = 0 ; i < files->Count() ; i++ )
{
RBFile* f = (RBFile*)files->Get( i );
switch( f->bAct() )
{
case RBFile::Add:
case RBFile::Branch:
case RBFile::Import:
if ( delEdited ) continue;
break;
}
fArgs[0] = f->path().Text();
client->SetArgv( 1, fArgs );
client->RunTag( "files", &rbUi );
}
client->WaitTag();
for ( int i = 0 ; i < files->Count() ; i++ )
{
RollFile( (RBFile*)files->Get( i ) );
}
}
void RollBack::HandleError( Error* err )
{
if ( backLoaded ) return;
ui->Message( err );
}
void RollBack::ProcessFile( const StrPtr& path, int rev, int a )
{
RBFile::Action action = (RBFile::Action)a;
RBFile* file;
// Add the file to the list and record the rev we're backing out.
if ( !backLoaded )
{
file = new RBFile;
file->setPath( path );
file->initBack( rev, action );
files->Put( file );
return;
}
// Find the previously-added file and record its head revision.
file = 0x0;
for ( int i = 0 ; i < files->Count() ; i++ )
{
if ( cursor >= files->Count() ) cursor = 0;
file = (RBFile*)files->Get( cursor++ );
if ( file->path() == path ) break;
}
if ( file && file->path() == path )
file->initHead( rev, action );
}
void RollBack::RollFile( RBFile* file )
{
Error e;
StrBuf msg;
StrBuf arg;
char* args[3];
char* oArgs[3];
oArgs[0] = "-c";
oArgs[1] = change->Text();
oArgs[2] = file->path().Text();
RBFilter info( ui );
info.SetMode( RBFilter::HideInfo1 );
RBFilter err( ui );
err.SetMode( RBFilter::ErrorOnly );
switch( file->bAct() )
{
case RBFile::Add:
case RBFile::Branch:
case RBFile::Import:
//Skip add rollback if there are newer revisions.
if ( !delEdited && file->bRev() != file->hRev() )
{
msg.Set( file->path() );
msg.Append( " edited; skipping delete. (Use -Ds to delete.)" );
e.Set( E_WARN, msg.Text() );
ui->Message( &e );
return;
}
//Flush to head and delete.
args[0] = file->path().Text();
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
client->SetArgv( 1, args );
client->Run( "flush", &err );
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
if ( change->Length() )
client->SetArgv( 3, oArgs );
else
client->SetArgv( 1, oArgs+2 );
client->Run( "delete", &info );
return;
case RBFile::Delete:
//Skip delete rollback if there are newer revisions.
if ( !editReadded && file->bRev() != file->hRev() )
{
msg.Set( file->path() );
msg.Append( " re-added; skipping add. (Use -Di to edit.)" );
e.Set( E_WARN, msg.Text() );
ui->Message( &e );
return;
}
//Sync to prior rev and add/edit.
arg.Set( file->path() );
arg.Append( "#" );
arg.Append( StrNum( file->bRev() - 1 ).Text() );
args[0] = arg.Text();
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
client->SetArgv( 1, args );
client->Run( "sync", &err );
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
if ( change->Length() )
client->SetArgv( 3, oArgs );
else
client->SetArgv( 1, oArgs+2 );
if ( file->hAct() == RBFile::Delete )
client->Run( "add", &info );
else
client->Run( "edit", &info );
return;
default:
//Skip edit/whatever rollback if head rev deleted.
if ( !addDeleted && file->hAct() == RBFile::Delete )
{
msg.Set( file->path() );
msg.Append( " deleted; skipping edit. (Use -Dt to re-add.)" );
e.Set( E_WARN, msg.Text() );
ui->Message( &e );
return;
}
//Sync to prior rev and edit/add.
arg.Set( file->path() );
arg.Append( "#" );
arg.Append( StrNum( file->bRev() - 1 ).Text() );
args[0] = arg.Text();
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
client->SetArgv( 1, args );
client->Run( "sync", &err );
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
if ( change->Length() )
client->SetArgv( 3, oArgs );
else
client->SetArgv( 1, oArgs+2 );
if ( file->hAct() == RBFile::Delete )
{
client->Run( "add", &info );
return;
}
client->Run( "edit", &info );
arg.Set( file->path() );
arg.Append( "#" );
arg.Append( StrNum( file->bRev() ).Text() );
args[0] = arg.Text();
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
client->SetArgv( 1, args );
client->Run( "sync", &err );
args[0] = "-ay";
args[1] = file->path().Text();
if ( tagged ) client->SetVar( P4Tag::v_tag, "yes" );
client->SetArgv( 2, args );
client->Run( "resolve", &info );
return;
}
}