/* * Copyright 1995, 1996 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ # include <stdhdrs.h> # include <strbuf.h> # include <error.h> # include <handler.h> # include <filesys.h> # include <md5.h> # include "clientuser.h" # include <msgclient.h> # include "clientmerge.h" # include "clientmerge2.h" static const char *const mergeHelp[] = { "Two-way merge options:", "", " Accept:", " at Keep only changes to their file.", " ay Keep only changes to your file.", "", " Diff:", " d Diff their file against yours->", "", " Edit:", " et Edit their file (read/write).", " ey Edit your file (read/write).", "", " Misc:", " s Skip this file.", " h Print this help message.", " ^C Quit the resolve operation.", "", 0 }; ClientMerge2::ClientMerge2( ClientUser *ui, FileSysType type, FileSysType theirType ) { // Save UI for future use this->ui = ui; // Set up files. yours = ui->File( type ); theirs = ui->File( theirType ); // Make theirs temp theirs->SetDeleteOnClose(); // And zonk counters for chunks chunksYours = chunksTheirs = chunksConflict = chunksBoth = 0; // 2003.1 server sends base digests theirsMD5 = new MD5; hasDigests = 0; } ClientMerge2::~ClientMerge2() { delete yours; delete theirs; delete theirsMD5; } void ClientMerge2::Open( StrPtr *name, Error *e, CharSetCvt *cvt, int charset ) { yours->Set( *name ); if( hasDigests ) { // calculate digest for yours file yours->Digest( &yoursDigest, e ); } theirs->MakeLocalTemp( name->Text() ); theirs->Perms( FPM_RW ); theirs->Open( FOM_WRITE, e ); theirs->Translator( cvt ); if( charset ) { yours->SetContentCharSetPriv( charset ); theirs->SetContentCharSetPriv( charset ); } } void ClientMerge2::Write( StrPtr *buf, StrPtr *bits, Error *e ) { theirs->Write( buf, e ); if( hasDigests ) theirsMD5->Update( *buf ); } void ClientMerge2::Close( Error *e ) { theirs->Close( e ); if( hasDigests ) { theirsMD5->Final( theirsDigest ); // Assign the chunk variables - there can be only one. // // base == yours, base != theirs, yours != theirs 1 theirs // base != yours, base != theirs, yours != theirs 1 conflicting // base != yours, base != theirs, yours == theirs 1 both // base != yours, base == theirs, yours != theirs 1 yours if( baseDigest == yoursDigest ) { if( baseDigest != theirsDigest ) chunksTheirs = 1; } else if( baseDigest != theirsDigest ) { if( yoursDigest != theirsDigest ) chunksConflict = 1; else chunksBoth = 1; } else chunksYours = 1; } } void ClientMerge2::Chmod( const char *perms, Error *e ) { yours->Chmod2( perms, e ); } void ClientMerge2::CopyDigest( StrPtr *digest, Error *e ) { // copy the base digest from the server baseDigest.Set( digest ); hasDigests = 1; } void ClientMerge2::SetTheirModTime( StrPtr *modTime ) { theirs->ModTime( modTime ); } MergeStatus ClientMerge2::AutoResolve( MergeForce force ) { Error e; // pre 2003.1 server doesn't send digest if( !hasDigests ) { if( !yours->Compare( theirs, &e ) ) { e.Set( MsgClient::MergeMsg2 ) << 0 << 0 << 1 << 0; ui->Message( &e ); return CMS_THEIRS; } if( force == CMF_FORCE ) e.Set( MsgClient::NonTextFileMerge ); else e.Set( MsgClient::ResolveManually ); ui->Message( &e ); return CMS_SKIP; } e.Set( MsgClient::MergeMsg2 ) << chunksYours << chunksTheirs << chunksBoth << chunksConflict; ui->Message( &e ); if( chunksConflict ) return CMS_SKIP; // Now we know there are no conflicts if( !chunksYours ) return CMS_THEIRS; return CMS_YOURS; } MergeStatus ClientMerge2::Resolve( Error *e ) { /* If the files are identical, autoaccept theirs */ /* We prefer theirs over ours, because then integration history */ /* marks it as a "copy" and we know the files are now identical. */ /* Note that this silently swallows chunksBoth... */ /* Get autoresolve suggestion */ MergeStatus autoStat = AutoResolve( CMF_FORCE ); /* Iterative try to get the user to select one of the two files. */ StrBuf buf; for(;;) { const char *autoSuggest; // No default action, either suggest at or ay if applicable. switch( autoStat ) { default: case CMS_SKIP: autoSuggest = "" ; break; case CMS_THEIRS: autoSuggest = "at" ; break; case CMS_YOURS: autoSuggest = "ay" ; break; } // Prompt. If no response, use auto buf.Clear(); e->Clear(); if( yours->IsTextual() && theirs->IsTextual() ) e->Set( MsgClient::MergePrompt2Edit ) << autoSuggest; else e->Set( MsgClient::MergePrompt2 ) << autoSuggest; e->Fmt( buf, EF_PLAIN ); e->Clear(); ui->Prompt( buf, buf, 0, e ); if( e->Test() ) return CMS_QUIT; if( !buf[0] ) buf = autoSuggest; // Do the user-requested operation # define pair( a, b ) ( (a) << 8 | (b) ) switch( pair( buf[0], buf[1] ) ) { // accept yours case pair( 'a', 'y' ): return CMS_YOURS; // accept theirs case pair( 'a', 't' ): return CMS_THEIRS; // edit yours case pair( 'e', 'y' ): ui->Edit( yours, e ); break; // edit theirs case pair( 'e', 't' ): ui->Edit( theirs, e ); break; // diff case pair( 'd', 0 ): ui->Diff( theirs, yours, 1, 0, e ); break; // skip case pair( 's', 0 ): return CMS_SKIP; // help case pair( '?', 0 ): case pair( 'h', 0 ): ui->Help( mergeHelp ); break; default: e->Set( MsgClient::BadFlag ); break; } // Report Errors if( e->Test() ) { ui->Message( e ); e->Clear(); } } } void ClientMerge2::Select( MergeStatus stat, Error *e ) { // Given the automatic or manual selection, now move the proper // file into place. Usually it is already in place. switch( stat ) { case CMS_THEIRS: // accept theirs theirs->Chmod( FPM_RW, e ); theirs->Rename( yours, e ); // swap around yours and theirs objects for file type changing theirs->Set( yours->Name() ); delete yours; yours = theirs; theirs = NULL; return; default: return; } } MergeStatus ClientMerge2::DetectResolve() const { return CMS_SKIP; } const StrPtr * ClientMerge2::GetYourDigest() const { return &yoursDigest; } const StrPtr * ClientMerge2::GetTheirDigest() const { return &theirsDigest; }