/* * 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; } ; /* * SendDir - utility method used by clientTraverseShort to decide if a * filename should be output as a file or as a directory (status -s) */ int SendDir( PathSys *fileName, StrPtr *cwd, StrArray *dirs, int &idx, int skip ) { int isDir = 0; // Skip printing file in current directory and just report subdirectory if( skip ) { fileName->SetLocal( *cwd, StrRef( "..." ) ); return 1; } // If file is in the current directory: isDirs is unset so that our // caller will send back the original file. fileName->ToParent(); if( !fileName->SCompare( *cwd ) ) return isDir; // Set path to the directory under cwd containing this file. // 'dirs' is the list of dirs in cwd on workspace. for( ; idx < dirs->Count() && !isDir; idx++ ) { if( fileName->IsUnderRoot( *dirs->Get( idx ) ) ) { fileName->SetLocal( *dirs->Get(idx), StrRef( "..." )); ++isDir; } } return isDir; } 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's timestamp is different from the server's. 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 ); } int clientTraverseShort( Client *client, StrPtr *cwd, const char *dir, int traverse, int noIgnore, int initial, int skipCheck, int skipCurrent, MapApi *map, StrArray *files, StrArray *dirs, int &idx, StrArray *depotFiles, int &ddx, const char *config, Error *e ) { // Variant of clientTraverseDirs that computes the files to be // added during traversal of directories instead of at the end, // and returns directories and files rather than all files. // This is used by 'status -s'. // 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; int found = 0; // 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 ); if( !fileName ) fileName = f->Name(); } 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, config ) ) { files->Put()->Set( fileName ); found = 1; } } delete f; return found; } // 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, config ) ) { files->Put()->Set( fileName ); found = 1; } delete f; return found; } // This is a directory to be scanned. StrArray *ua = f->ScanDir( e ); if( e->Test() ) { // report error but keep moving delete f; client->OutputError( e ); return 0; } // PathSys for concatenating dir and local path, // to get full path PathSys *p = PathSys::Create(); p->SetCharSet( f->GetCharSetPriv() ); // Attach path delimiter to dirs so Sort() works correctly, and also to // save relevant Stat() information. StrArray *a = new StrArray(); StrBuf dirDelim; StrBuf symDelim; #ifdef OS_NT dirDelim.Set( "\\" ); symDelim.Set( "\\\\" ); #else dirDelim.Set( "/" ); symDelim.Set( "//" ); #endif // Now files & dirs at this level from ScanDir() will be sorted // in the same order as the depotFiles array. And delimiters tell us // the Stat() of those files. for( int i = 0; i < ua->Count(); i++ ) { p->SetLocal( StrRef( dir ), *ua->Get(i) ); f->Set( *p ); int stat = f->Stat(); StrBuf out; if( ( stat & FSF_DIRECTORY ) && !( stat & FSF_SYMLINK ) ) out << ua->Get( i ) << dirDelim; else if( ( stat & FSF_DIRECTORY ) && ( stat & FSF_SYMLINK ) ) out << ua->Get( i ) << symDelim; else if( ( stat & FSF_EXISTS ) || ( stat & FSF_SYMLINK ) ) out << ua->Get(i); else continue; a->Put()->Set( out ); } a->Sort( !StrBuf::CaseUsage() ); delete ua; // If directory is unknown to p4, we don't need to check that files // are in depot (they aren't), so just return after the first file // is found and bypass checking. int doSkipCheck = skipCheck; int dddx = 0; int matched; StrArray *depotDirs = new StrArray(); // First time through we save depot dirs if( initial ) { StrPtr *ddir; for( int j=0; ddir=client->GetVar( StrRef("depotDirs"), j); j++) { StrBuf dirD; dirD << ddir << dirDelim; depotDirs->Put()->Set( dirD ); } depotDirs->Sort( !StrBuf::CaseUsage() ); } // For each directory entry. for( int i = 0; i < a->Count(); i++ ) { // Strip delimiters and use the hints to determine stat int isDir = 0; int isSymDir = 0; StrBuf fName; if( a->Get(i)->EndsWith( symDelim.Text(), 2 ) ) { ++isDir; ++isSymDir; fName.Set( a->Get(i)->Text(), a->Get(i)->Length() - 2 ); } else if( a->Get(i)->EndsWith( dirDelim.Text(), 1 ) ) { ++isDir; fName.Set( a->Get(i)->Text(), a->Get(i)->Length() - 1 ); } else fName.Set( a->Get(i) ); // Check mapping, ignore files before sending file or symlink back p->SetLocal( StrRef( dir ), fName ); f->Set( *p ); int checkFile = 0; StrPtr *ddir; if( client != client->translated ) { fileName = cvt->FastCvt( f->Name(), strlen(f->Name()) ); if( !fileName ) fileName = f->Name(); } else fileName = f->Name(); if( isDir ) { if( isSymDir ) { from.Set( fileName ); from << "/"; if( client->protocolNocase != StrBuf::CaseUsage() ) { from.SetCaseFolding( client->protocolNocase ); matched = map->Translate( from, to, MapLeftRight ); from.SetCaseFolding( !client->protocolNocase ); } else matched = map->Translate( from, to, MapLeftRight ); if( !matched ) continue; if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored, config ) ) { if( doSkipCheck ) { p->Set( fileName ); (void)SendDir( p, cwd, dirs, idx, skipCurrent ); files->Put()->Set( p ); found = 1; break; } else ++checkFile; } } else if( traverse ) { if( initial ) { dirs->Put()->Set( fileName ); int foundOne = 0; int l; // If this directory is unknown to the depot, we don't // need to compare against depot files. for( ; dddx < depotDirs->Count() && !foundOne; dddx++) { StrBuf fName; fName << fileName << dirDelim; const StrPtr *ddir = depotDirs->Get( dddx ); p->SetLocal( *cwd, *ddir ); l = fName.SCompare( *p ); if( !l ) ++foundOne; else if( l < 0 ) break; } skipCheck = !foundOne ? 1 : 0; } found = clientTraverseShort( client, cwd, f->Name(), traverse, noIgnore, 0, skipCheck, skipCurrent, map, files, dirs, idx, depotFiles, ddx, config, e ); // Stop traversing directories when we have a file to // to add, unless we are at the top and need to check // for files in the current directory. if( found && !initial ) break; else if( found && initial && !skipCurrent ) found = 0; if( found ) break; } } else { from.Set( fileName ); if( client->protocolNocase != StrBuf::CaseUsage() ) { from.SetCaseFolding( client->protocolNocase ); matched = map->Translate( from, to, MapLeftRight ); from.SetCaseFolding( !client->protocolNocase ); } else matched = map->Translate( from, to, MapLeftRight ); if( !matched ) continue; if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored, config ) ) { if( doSkipCheck ) { p->Set( fileName ); (void)SendDir( p, cwd, dirs, idx, skipCurrent ); files->Put()->Set( p ); found = 1; break; } else ++checkFile; } } // See if file is in depot and if not, either set the file // or directory to be reported back to the server. if( checkFile ) { int l = 0; int finished = 0; while ( !finished ) { if( ddx >= depotFiles->Count()) l = -1; else l = StrRef( fileName ).SCompare( *depotFiles->Get(ddx)); if( !l ) { ++ddx; ++finished; } else if( l < 0 ) { p->Set( fileName ); if( initial && skipCurrent ) { p->ToParent(); p->SetLocal( *p, StrRef("...") ); files->Put()->Set( p ); } else if( SendDir( p, cwd, dirs, idx, skipCurrent ) ) files->Put()->Set( p ); else files->Put()->Set( fileName ); found = 1; break; } else ++ddx; } if( ( !initial || skipCurrent ) && found ) break; } } delete p; delete a; delete f; delete depotDirs; return found; } void clientTraverseDirs( Client *client, const char *dir, int traverse, int noIgnore, int getDigests, MapApi *map, StrArray *files, StrArray *sizes, StrArray *digests, int &hasIndex, StrArray *hasList, const char *config, 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; StrBuf localDigest; // 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 ); if( !fileName ) fileName = f->Name(); } 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, config ) ) { files->Put()->Set( fileName ); sizes->Put()->Set( StrNum( f->GetSize() ) ); if( getDigests ) { f->Translator( ClientSvc::XCharset(client,FromClient)); f->Digest( &localDigest, e ); digests->Put()->Set( localDigest ); } } } 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, config ) ) { files->Put()->Set( fileName ); sizes->Put()->Set( StrNum( f->GetSize() ) ); if( getDigests ) { f->Translator( ClientSvc::XCharset(client,FromClient)); f->Digest( &localDigest, e ); digests->Put()->Set( localDigest ); } } delete f; return; } // Directory might be ignored, bail if( !noIgnore && ignore->RejectDir( StrRef( f->Name() ), ignored, config ) ) { 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; } // Sort in case sensitivity of client a->Sort( !StrBuf::CaseUsage() ); // PathSys for concatenating dir and local path, // to get full path PathSys *p = PathSys::Create(); p->SetCharSet( f->GetCharSetPriv() ); int matched; // 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 ); if( client != client->translated ) { fileName = cvt->FastCvt( f->Name(), strlen(f->Name()) ); if( !fileName ) fileName = f->Name(); } else fileName = f->Name(); // Do compare with array list (skip files if possible) int cmp = -1; while( hasList && hasIndex < hasList->Count() ) { cmp = f->Path()->SCompare( *hasList->Get( hasIndex ) ); if( cmp < 0 ) break; hasIndex++; if( cmp == 0 ) break; } // Don't stat if we matched a file from the edit list if( cmp == 0 ) continue; int stat = f->Stat(); if( stat & FSF_DIRECTORY ) { if( stat & FSF_SYMLINK ) { from.Set( fileName ); from << "/"; if( client->protocolNocase != StrBuf::CaseUsage() ) { from.SetCaseFolding( client->protocolNocase ); matched = map->Translate( from, to, MapLeftRight ); from.SetCaseFolding( !client->protocolNocase ); } else matched = map->Translate( from, to, MapLeftRight ); if( !matched ) continue; if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored, config ) ) { files->Put()->Set( fileName ); sizes->Put()->Set( StrNum( f->GetSize() ) ); if( getDigests ) { f->Translator( ClientSvc::XCharset(client,FromClient)); f->Digest( &localDigest, e ); digests->Put()->Set( localDigest ); } } } else if( traverse ) clientTraverseDirs( client, f->Name(), traverse, noIgnore, getDigests, map, files, sizes, digests, hasIndex, hasList, config, e ); } else if( ( stat & FSF_EXISTS ) || ( stat & FSF_SYMLINK ) ) { from.Set( fileName ); if( client->protocolNocase != StrBuf::CaseUsage() ) { from.SetCaseFolding( client->protocolNocase ); matched = map->Translate( from, to, MapLeftRight ); from.SetCaseFolding( !client->protocolNocase ); } else matched = map->Translate( from, to, MapLeftRight ); if( !matched ) continue; if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored, config ) ) { files->Put()->Set( fileName ); sizes->Put()->Set( StrNum( f->GetSize() ) ); if( getDigests ) { f->Translator( ClientSvc::XCharset(client,FromClient)); f->Digest( &localDigest, e ); digests->Put()->Set( localDigest ); } } } } 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 *summary = client->GetVar( "summary" ); StrPtr *skipIgnore = client->GetVar( "skipIgnore" ); StrPtr *skipCurrent = client->GetVar( "skipCurrent" ); StrPtr *sendDigest = client->GetVar( "sendDigest" ); StrPtr *mapItem; if( e->Test() ) return; MapApi *map = new MapApi; StrArray *files = new StrArray(); StrArray *sizes = new StrArray(); StrArray *dirs = new StrArray(); StrArray *depotFiles = new StrArray(); StrArray *digests = 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 ); } // If we have a list of files we know are in the depot already, // filter them out of our list of files to add. For -s option, // we need to have this list of depot files for computing files // and directories to add (even if it is an empty list). StrRef skipAdd( "skipAdd" ); ReconcileHandle *recHandle = (ReconcileHandle *)client->handles.Get( &skipAdd ); if( recHandle ) { recHandle->pathArray->Sort( !StrBuf::CaseUsage() ); } else if( !recHandle && summary != 0 ) { recHandle = new ReconcileHandle; client->handles.Install( &skipAdd, recHandle, e ); if( e->Test() ) return; } // status -s also needs the list of files opened for add appended // to the list of depot files. if( summary != 0 ) { const StrPtr *dfile; for( int j=0; dfile=client->GetVar( StrRef("depotFiles"), j); j++) depotFiles->Put()->Set( dfile ); for( int j=0; dfile=recHandle->pathArray->Get(j); j++ ) depotFiles->Put()->Set( dfile ); depotFiles->Sort( !StrBuf::CaseUsage() ); } // status -s will output files in the current directory and paths // rather than all of the files individually. Compare against depot // files early so we can abort traversal early if we can. int hasIndex = 0; const char *config = client->GetEnviro()->Get( "P4CONFIG" ); if( summary != 0 ) { int idx = 0; int ddx = 0; (void)clientTraverseShort( client, dir, dir->Text(), traverse != 0, skipIgnore != 0, 1, 0, skipCurrent != 0, map, files, dirs, idx, depotFiles, ddx, config, e ); } else clientTraverseDirs( client, dir->Text(), traverse != 0, skipIgnore != 0, sendDigest != 0, map, files, sizes, digests, hasIndex, recHandle ? recHandle->pathArray : 0, config, e ); delete map; // Compare list of files on client with list of files in the depot // if we have this list from ReconcileEdit. Skip this comparison // if summary because it was done already. if( recHandle && !summary ) { 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( !sendDigest && 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 ) ); } if( sendDigest ) client->SetVar( P4Tag::v_digest, i0, *digests->Get(i1)); ++i0; ++i1; } else { ++i2; } } } else { for( int j = 0; j < files->Count(); j++ ) { client->SetVar( P4Tag::v_file, j, *files->Get(j) ); if( sendDigest ) client->SetVar( P4Tag::v_digest, j, *digests->Get(j) ); } } client->Confirm( confirm ); delete files; delete sizes; delete dirs; delete depotFiles; delete digests; } 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; }