/*
* Copyright 1995, 2001 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
# ifdef USE_EBCDIC
# define NEED_EBCDIC
# endif
# include <stdhdrs.h>
# include <strbuf.h>
# include <strdict.h>
# include <strops.h>
# include <strarray.h>
# include <strtable.h>
# include <error.h>
# include <mapapi.h>
# include <handler.h>
# include <rpc.h>
# include <md5.h>
# include <i18napi.h>
# include <charcvt.h>
# include <transdict.h>
# include <ignore.h>
# include <debug.h>
# include <tunable.h>
# include <p4tags.h>
# include <msgclient.h>
# include <msgsupp.h>
# include <filesys.h>
# include <pathsys.h>
# include <enviro.h>
# include <readfile.h>
# include <diffsp.h>
# include <diffan.h>
# include <diff.h>
# include "clientuser.h"
# include "client.h"
# include "clientprog.h"
# include "clientservice.h"
/*
* ReconcileHandle - handle reconcile's list of files to skip when adding
*/
class ReconcileHandle : public LastChance {
public:
ReconcileHandle()
{
pathArray = new StrArray;
delCount = 0;
}
~ReconcileHandle()
{
delete pathArray;
}
StrArray *pathArray;
int delCount;
} ;
void
clientReconcileFlush( Client *client, Error *e )
{
// Delete the client's reconcile handle
StrRef skipAdd( "skipAdd" );
ReconcileHandle *recHandle =
(ReconcileHandle *)client->handles.Get( &skipAdd );
if( recHandle )
delete recHandle;
}
/*
* clientReconcileEdit() -- "inquire" about file, for 'p4 reconcile'
*
* This routine performs clientCheckFile's scenario 1 checking, but
* also saves the list of files that are in the depot so they can be
* compared to the list of files on the client when reconciling later for add.
*
*/
void
clientReconcileEdit( Client *client, Error *e )
{
client->NewHandler();
StrPtr *clientType = client->GetVar( P4Tag::v_type );
StrPtr *digest = client->GetVar( P4Tag::v_digest );
StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
StrPtr *fileSize = client->GetVar( P4Tag::v_fileSize );
StrPtr *submitTime = client->GetVar( P4Tag::v_time );
if( e->Test() && !e->IsFatal() )
{
client->OutputError( e );
return;
}
const char *status = "exists";
const char *ntype = clientType ? clientType->Text() : "text";
// For adding files, checkSize is a maximum (or use alt type)
// For flush, checkSize is an optimization check on binary files.
offL_t checkSize = fileSize ? fileSize->Atoi64() : 0;
/*
* If we do know the type, we want to know if it's missing.
* If it isn't missing and a digest is given, we want to know if
* it is the same.
*/
FileSys *f = ClientSvc::File( client, e );
if( e->Test() || !f )
return;
int statVal = f->Stat();
// Save the list of depot files. We'll diff it against the list of all
// files on client to find files to add in clientReconcileAdd
StrRef skipAdd( "skipAdd" );
ReconcileHandle *recHandle =
(ReconcileHandle *)client->handles.Get( &skipAdd );
if( !recHandle )
{
recHandle = new ReconcileHandle;
client->handles.Install( &skipAdd, recHandle, e );
if( e->Test() )
return;
}
if( !( statVal & ( FSF_SYMLINK|FSF_EXISTS ) ) )
{
status = "missing";
recHandle->delCount++;
}
else if ( ( !( statVal & FSF_SYMLINK ) && ( f->IsSymlink() ) )
|| ( ( statVal & FSF_SYMLINK ) && !( f->IsSymlink() ) ) )
{
recHandle->pathArray->Put()->Set( f->Name() );
}
else if( digest )
{
recHandle->pathArray->Put()->Set( f->Name() );
if( !checkSize || checkSize == f->GetSize() )
{
StrBuf localDigest;
f->Translator( ClientSvc::XCharset( client, FromClient ) );
// Bypass expensive digest computation with -m unless the
// local file is newer than the submit time.
if( !submitTime ||
( submitTime && ( f->StatModTime() > submitTime->Atoi()) ) )
{
f->Digest( &localDigest, e );
if( !e->Test() && !localDigest.XCompare( *digest ) )
status = "same";
}
else if( submitTime )
status = "same";
}
// If we can't read the file (for some reason -- wrong type?)
// we consider the files different.
e->Clear();
}
delete f;
// tell the server
client->SetVar( P4Tag::v_type, ntype );
client->SetVar( P4Tag::v_status, status );
client->Confirm( confirm );
// Report non-fatal error and clear it.
client->OutputError( e );
}
void
clientTraverseDirs( Client *client, const char *dir, int traverse,
int noIgnore, MapApi *map, StrArray *files,
StrArray *sizes, Error *e )
{
// Return all files in dir, and optionally traverse dirs in dir,
// while checking each file against map before returning it
// Scan the directory.
FileSys *f = client->GetUi()->File( FST_BINARY );
f->SetContentCharSetPriv( client->content_charset );
f->Set( StrRef( dir ) );
int fstat = f->Stat();
Ignore *ignore = client->GetIgnore();
StrPtr ignored = client->GetIgnoreFile();
StrBuf from;
StrBuf to;
CharSetCvt *cvt = ( (TransDict *)client->transfname )->ToCvt();
const char *fileName;
// Use server case-sensitivity rules to find files to add
from.SetCaseFolding( client->protocolNocase );
// With unicode server and client using character set, we need
// to send files back as utf8.
if( client != client->translated )
fileName = cvt->FastCvt( f->Name(), strlen(f->Name()), 0 );
else
fileName = f->Name();
// If this is a file, not a directory, and not to be ignored,
// save the filename
if( !( fstat & FSF_DIRECTORY ) )
{
if( ( fstat & FSF_EXISTS ) || ( fstat & FSF_SYMLINK ) )
{
if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored ) )
{
files->Put()->Set( fileName );
sizes->Put()->Set( StrNum( f->GetSize() ) );
}
}
delete f;
return;
}
// If this is a symlink to a directory, and not to be ignored,
// return filename and quit
if( ( fstat & FSF_SYMLINK ) && ( fstat & FSF_DIRECTORY ) )
{
if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored ) )
{
files->Put()->Set( fileName );
sizes->Put()->Set( StrNum( f->GetSize() ) );
}
delete f;
return;
}
// This is a directory to be scanned.
StrArray *a = f->ScanDir( e );
if( e->Test() )
{
// report error but keep moving
delete f;
client->OutputError( e );
return;
}
a->Sort(0);
// PathSys for concatenating dir and local path,
// to get full path
PathSys *p = PathSys::Create();
p->SetCharSet( f->GetCharSetPriv() );
// For each directory entry.
for( int i = 0; i < a->Count(); i++ )
{
// Check mapping, ignore files before sending file or symlink back
p->SetLocal( StrRef( dir ), *a->Get(i) );
f->Set( *p );
int stat = f->Stat();
if( client != client->translated )
fileName = cvt->FastCvt( f->Name(), strlen(f->Name()) );
else
fileName = f->Name();
if( stat & FSF_DIRECTORY )
{
if( stat & FSF_SYMLINK )
{
from.Set( fileName );
from << "/";
if( !map->Translate( from, to, MapLeftRight ) )
continue;
if( noIgnore || !ignore->Reject( StrRef(f->Name()),ignored))
{
files->Put()->Set( fileName );
sizes->Put()->Set( StrNum( f->GetSize() ) );
}
}
else if( traverse )
clientTraverseDirs( client, f->Name(), traverse, noIgnore,
map, files, sizes, e );
}
else if( ( stat & FSF_EXISTS ) || ( stat & FSF_SYMLINK ) )
{
from.Set( fileName );
if( !map->Translate( from, to, MapLeftRight ) )
continue;
if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored ) )
{
files->Put()->Set( fileName );
sizes->Put()->Set( StrNum( f->GetSize() ) );
}
}
}
delete p;
delete a;
delete f;
}
void
clientReconcileAdd( Client *client, Error *e )
{
/*
* Reconcile add confirm
*
* Scans the directory (local syntax) and returns
* files in the directory using the full path. This
* differs from clientScanDir in that it returns full
* paths, supports traversing subdirectories, and checks
* against a mapTable.
*/
client->NewHandler();
StrPtr *dir = client->transfname->GetVar( P4Tag::v_dir, e );
StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
StrPtr *traverse = client->GetVar( "traverse" );
StrPtr *skipIgnore = client->GetVar( "skipIgnore" );
StrPtr *mapItem;
if( e->Test() )
return;
MapApi *map = new MapApi;
StrArray *files = new StrArray();
StrArray *sizes = new StrArray();
// Construct a MapTable object from the strings passed in by server
for( int i = 0; mapItem = client->GetVar( StrRef("mapTable"), i ); i++)
{
MapType m;
int j;
char *c = mapItem->Text();
switch( *c )
{
case '-': m = MapExclude; j = 1; break;
case '+': m = MapOverlay; j = 1; break;
default: m = MapInclude; j = 0; break;
}
map->Insert( StrRef( c+j ), StrRef( c+j ), m );
}
clientTraverseDirs( client, dir->Text(), traverse != 0, skipIgnore != 0,
map, files, sizes, e );
delete map;
// If we have a list of files we know are in the depot already,
// filter them out of our list of files to add
StrRef skipAdd( "skipAdd" );
ReconcileHandle *recHandle =
(ReconcileHandle *)client->handles.Get( &skipAdd );
if( recHandle )
{
recHandle->pathArray->Sort(0);
int i1 = 0, i2 = 0, i0 = 0, l = 0;
while( i1 < files->Count() )
{
if( i2 >= recHandle->pathArray->Count())
l = -1;
else
l = files->Get( i1 )->SCompare(
*recHandle->pathArray->Get( i2 ) );
if( !l )
{
++i1;
++i2;
}
else if( l < 0 )
{
client->SetVar( P4Tag::v_file, i0, *files->Get( i1 ) );
if( recHandle->delCount )
{
// Deleted files? Send filesize info so the
// server can try to pair up moves.
client->SetVar( P4Tag::v_fileSize,
i0, *sizes->Get( i1 ) );
}
++i0;
++i1;
}
else
{
++i2;
}
}
}
else
{
for( int j = 0; j < files->Count(); j++ )
client->SetVar( P4Tag::v_file, j, *files->Get(j) );
}
client->Confirm( confirm );
delete files;
delete sizes;
}
void
clientExactMatch( Client *client, Error *e )
{
// Compare existing digest to list of
// new client files, return match, or not.
// Args:
// type = existing file type (clientpart)
// digest = existing file digest
// fileSize = existing file size
// charSet = existing file charset
// toFileN = new file local path
// indexN = new file index
// confirm = return callback
//
// Return:
// toFile = exact match
// index = exact match
client->NewHandler();
StrPtr *clientType = client->GetVar( P4Tag::v_type );
StrPtr *digest = client->GetVar( P4Tag::v_digest );
StrPtr *fileSize = client->GetVar( P4Tag::v_fileSize );
StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
if( e->Test() )
return;
StrPtr *matchFile = 0;
StrPtr *matchIndex = 0;
FileSys *f = 0;
for( int i = 0 ;
client->GetVar( StrRef( P4Tag::v_toFile ), i ) ;
i++ )
{
delete f;
StrVarName path = StrVarName( StrRef( P4Tag::v_toFile ), i );
f = ClientSvc::FileFromPath( client, path.Text(), e );
// If we encounter a problem with a file, we just don't return
// it as a match. No need to blat out lots of errors.
if( e->Test() || !f )
{
e->Clear();
continue;
}
int statVal = f->Stat();
// Skip files that are symlinks when we
// aren't looking for symlinks.
if( ( !( statVal & ( FSF_SYMLINK|FSF_EXISTS ) ) )
|| ( !( statVal & FSF_SYMLINK ) && ( f->IsSymlink() ) )
|| ( ( statVal & FSF_SYMLINK ) && !( f->IsSymlink() ) ) )
continue;
if( !digest )
continue;
StrBuf localDigest;
f->Translator( ClientSvc::XCharset( client, FromClient ) );
f->Digest( &localDigest, e );
if( e->Test() )
{
e->Clear();
continue;
}
if( !localDigest.XCompare( *digest ) )
{
matchFile = client->GetVar( StrRef(P4Tag::v_toFile), i );
matchIndex = client->GetVar( StrRef(P4Tag::v_index), i );
break; // doesn't get any better
}
}
delete f;
if( matchFile && matchIndex )
{
client->SetVar( P4Tag::v_toFile, matchFile );
client->SetVar( P4Tag::v_index, matchIndex );
}
client->Confirm( confirm );
}
void
clientOpenMatch( Client *client, ClientFile *f, Error *e )
{
// Follow on from clientOpenFile, not called by server directly.
// Grab RPC vars and attach them to the file handle so that
// clientCloseMatch can use them for N-way diffing.
StrPtr *fromFile = client->GetVar( P4Tag::v_fromFile, e );
StrPtr *key = client->GetVar( P4Tag::v_key, e );
StrPtr *flags = client->GetVar( P4Tag::v_diffFlags );
if( e->Test() )
return;
f->matchDict = new StrBufDict;
f->matchDict->SetVar( P4Tag::v_fromFile, fromFile );
f->matchDict->SetVar( P4Tag::v_key, key );
if( flags )
f->matchDict->SetVar( P4Tag::v_diffFlags, flags );
for( int i = 0 ; ; i++ )
{
StrPtr *index = client->GetVar(
StrRef( P4Tag::v_index ), i );
StrPtr *file = client->GetVar(
StrRef( P4Tag::v_toFile ), i );
if( !index || !file )
break;
f->matchDict->SetVar(
StrRef( P4Tag::v_index ), i, *index );
f->matchDict->SetVar(
StrRef( P4Tag::v_toFile ), i, *file );
}
}
void
clientCloseMatch( Client *client, ClientFile *f1, Error *e )
{
// Follow on from clientCloseFile, not called by server directly.
// Compare temp file to existing client files. Figure out the
// best match, along with a quantitative measure of how good
// the match was (lines matched vs total lines). Stash it
// in the handle so clientAckMatch can report it back.
if( !f1->matchDict )
{
e->Set( MsgSupp::NoParm ) << "clientCloseMatch";
return;
}
StrPtr *matchFile = 0;
StrPtr *matchIndex = 0;
StrPtr *fname;
FileSys *f2 = 0;
DiffFlags flags;
if( StrPtr* diffFlags = f1->matchDict->GetVar( P4Tag::v_diffFlags ) )
flags.Init( diffFlags );
int bestNum = 0;
int bestSame = 0;
int totalLines = 0;
for( int i = 0 ;
fname = f1->matchDict->GetVar( StrRef( P4Tag::v_toFile ), i ) ;
i++ )
{
delete f2;
f2 = client->GetUi()->File( f1->file->GetType() );
f2->SetContentCharSetPriv( f1->file->GetContentCharSetPriv() );
f2->Set( *fname );
if( e->Test() || !f2 )
{
// don't care
e->Clear();
continue;
}
Sequence s1( f1->file, flags, e );
Sequence s2( f2, flags, e );
if ( e->Test() )
{
// still don't care
e->Clear();
continue;
}
DiffAnalyze diff( &s1, &s2 );
int same = 0;
for( Snake *s = diff.GetSnake() ; s ; s = s->next )
{
same += ( s->u - s->x );
if( s->u > totalLines )
totalLines = s->u;
}
if( same > bestSame )
{
bestNum = i;
bestSame = same;
}
}
delete f2;
f1->file->Close( e );
totalLines++; // snake lines start at zero
if( bestSame )
{
f1->matchDict->SetVar( P4Tag::v_index,
f1->matchDict->GetVar( StrRef( P4Tag::v_index ), bestNum ) );
f1->matchDict->SetVar( P4Tag::v_toFile,
f1->matchDict->GetVar( StrRef( P4Tag::v_toFile ), bestNum ) );
f1->matchDict->SetVar( P4Tag::v_lower, bestSame );
f1->matchDict->SetVar( P4Tag::v_upper, totalLines );
}
// clientAckMatch will send this back
}
void
clientAckMatch( Client *client, Error *e )
{
StrPtr *handle = client->GetVar( P4Tag::v_handle, e );
StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
if( e->Test() )
return;
// Get handle.
ClientFile *f = (ClientFile *)client->handles.Get( handle, e );
if( e->Test() )
return;
// Fire everything back.
StrPtr *fromFile = f->matchDict->GetVar( P4Tag::v_fromFile );
StrPtr *key = f->matchDict->GetVar( P4Tag::v_key );
StrPtr *toFile = f->matchDict->GetVar( P4Tag::v_toFile );
StrPtr *index = f->matchDict->GetVar( P4Tag::v_index );
StrPtr *lower = f->matchDict->GetVar( P4Tag::v_lower );
StrPtr *upper = f->matchDict->GetVar( P4Tag::v_upper );
if( fromFile && key )
{
client->SetVar( P4Tag::v_fromFile, fromFile );
client->SetVar( P4Tag::v_key, key );
}
else
{
e->Set( MsgSupp::NoParm ) << "fromFile/key";
return;
}
if( toFile && index && lower && upper )
{
client->SetVar( P4Tag::v_toFile, toFile );
client->SetVar( P4Tag::v_index, index );
client->SetVar( P4Tag::v_lower, lower );
client->SetVar( P4Tag::v_upper, upper );
}
client->Confirm( confirm );
delete f;
}