//------------------------------------------------------------------------ // Copyright 2006 Kyle Turner. // // License is hereby granted to use this software and distribute it // freely, as long as this copyright notice is retained and modifications // are clearly marked. // // ALL WARRANTIES ARE HEREBY DISCLAIMED. // // $Id: //guest/kyle_turner/perforce/resolve/resolve.cpp#1 $ //------------------------------------------------------------------------ #include <stdarg.h> #include "help.h" #include "resolve.h" #define NOT_USED(a) (void)(a) //------------------------------------------------------------------------ // Class: Special Resolve Handling //------------------------------------------------------------------------ NewResolve::NewResolve () { ignoreP4Conflicts = false; keepTempFiles = false; noCommit = false; } NewResolve::~NewResolve () { } void NewResolve::Merge (FileSys *base, FileSys *theirs, FileSys *yours, FileSys *result, Error *err) { int id; char * merger = NULL; const char * charSet = NULL; if (result->IsUnicode ()) { id = result->GetContentCharSetPriv (); if (id != 0 && ((merger = enviro->Get ("P4MERGEUNICODE1")) || (merger = enviro->Get ("P4MERGEUNICODE")))) { charSet = CharSetApi::Name ((CharSetApi::CharSet) id); } } if (merger || (merger = enviro->Get ("P4MERGE1")) || (merger = enviro->Get ("P4MERGE")) || (merger = enviro->Get ("MERGE"))) { StrBuf expanded; if (strstr (merger, "%base%")) { StrBufDict varMap; varMap.SetVar ("base", base->Name ()); varMap.SetVar ("theirs", theirs->Name ()); varMap.SetVar ("yours", yours->Name ()); varMap.SetVar ("result", result->Name ()); if (charSet) varMap.SetVar ("charset", charSet); StrOps::Expand (expanded, StrRef (merger), varMap); } else { expanded.Append (merger); expanded.Append (" \""); expanded.Append (base->Name ()); expanded.Append ("\" \""); expanded.Append (theirs->Name ()); expanded.Append ("\" \""); expanded.Append (yours->Name ()); expanded.Append ("\" \""); expanded.Append (result->Name ()); expanded.Append ("\""); } #ifdef OS_MAC ToolServerExec (expanded, err); #else RunArgs cmd (expanded); RunCommand *rc = new RunCommand; rc->Run (cmd, err); delete rc; #endif } else { err->Set (MsgClient::NoMerger); } } //------------------------------------------------------------------------ // Function: Message // // We override so we can parse out the file names and discard some of the // messages we don't care about. //------------------------------------------------------------------------ void NewResolve::Message (Error *err) { char * str; StrBuf msg; StrBuf discard; // 1. Keepers err->Fmt (&msg); str = msg.Text (); if (ParseString (str, "%s - merging %s using base %s\n", &yourName, &theirName, &baseName)) { return; } // 2. Discards if (ParseString (str, "Diff chunks%s", &discard)) return; // 3. Others printf ("%s\n", msg.Text ()); } //------------------------------------------------------------------------ // Function: Resolve // // This function plugs into the normally interactive resolve flow just // after the "p4 api merge" has been run. //------------------------------------------------------------------------ int NewResolve::Resolve (ClientMerge *m, Error *err) { MergeStatus action; int numYour; int numTheir; int numBoth; int numConflicts; int altConflicts; FileSys * base; FileSys * their; FileSys * your; FileSys * merge; FileSys * altMerge; StrBuf buff; StrBuf yourPath; StrBuf yourTail; // 1. Print names saved by Message() PrintStart ("Resolve", yourName.Text ()); PrintValue ("base", baseName.Text ()); PrintValue ("their", theirName.Text ()); // 2. Get "p4 api merge" results base = m->GetBaseFile (); their = m->GetTheirFile (); your = m->GetYourFile (); merge = m->GetResultFile (); numYour = m->GetYourChunks (); numTheir = m->GetTheirChunks (); numBoth = m->GetBothChunks (); numConflicts = m->GetConflictChunks (); // 3. Save temp files (-k) ParseFileName (your->Name (), &yourPath, &yourTail); altMerge = FileSys::Create (FST_TEXT); altMerge->Perms (FPM_RW); if (keepTempFiles) { if (base) { TempName (&yourPath, &yourTail, "base", &buff); MoveFile (base, buff); } if (their) { TempName (&yourPath, &yourTail, "their", &buff); MoveFile (their, buff); } if (merge) { TempName (&yourPath, &yourTail, "merge", &buff); MoveFile (merge, buff); } TempName (&yourPath, &yourTail, "your", &buff); BackupFile (your, buff); TempName (&yourPath, &yourTail, "xmerge", &buff); altMerge->Set (buff); } else { altMerge->SetDeleteOnClose (); altMerge->MakeLocalTemp (yourPath.Text ()); } // 4. Run external merge program and // check for conflict markers altConflicts = -1; Merge (base, their, your, altMerge, err); if (err->Test ()) { Message (err); err->Clear (); } else { altMerge->Open (FOM_READ, err); if (err->Test ()) Message (err); else { altConflicts = 0; while (altMerge->ReadLine (&buff, err)) { if (strncmp (buff.Text (), "<<<<", 4) == 0) ++altConflicts; } } err->Clear (); } // 5. Determine resolve action PrintValue ("numYour", numYour); PrintValue ("numTheir", numTheir); PrintValue ("numBoth", numBoth); PrintValue ("conflicts", numConflicts); PrintValue ("xConflicts", altConflicts); action = CMS_SKIP; if (altConflicts == -1) PrintValue ("action", "skip, $P4MERGE failed"); else if (altConflicts > 0 || (numConflicts > 0 && !ignoreP4Conflicts)) { PrintValue ("action", "skip, conflicts"); } else if (numConflicts == 0 && numYour == 0) { action = CMS_THEIRS; PrintValue ("action", "accept theirs"); } else if (numConflicts == 0 && numTheir == 0) { action = CMS_YOURS; PrintValue ("action", "accept yours"); } else if (numConflicts == 0 && altMerge->Compare (merge, err) == 0) { action = CMS_MERGED; PrintValue ("action", "accept merge"); } else { action = CMS_EDIT; PrintValue ("action", "accept edit, using $P4MERGE"); // point result file to $P4MERGE result altMerge->ClearDeleteOnClose (); merge->Close (err); merge->Set (altMerge->Name ()); merge->Open (FOM_READ, err); } // 6. Clean up PrintDone (); err->Clear (); delete altMerge; return noCommit ? CMS_SKIP : action; } //------------------------------------------------------------------------ // Utilities //------------------------------------------------------------------------ bool NewResolve::ParseString (const char *src, const char *format, ...) { va_list args; bool status; bool prevVar; const char * f1; const char * f2; const char * s1; const char * s2; StrBuf * dest; StrBuf match; va_start (args, format); s1 = src; status = true; prevVar = false; for (f1 = format; f1; f1 = f2 ? &f2[2] : NULL) { // Find fixed match string f2 = strstr (f1, "%s"); if (f2) match.Set (f1, f2 - f1); else match.Set (f1); // Match in source string if (match.Length () > 0) { s2 = strstr (s1, match.Text ()); if (!s2) { status = false; // fail: non-match break; } // Save %s before match string if (prevVar) { dest = va_arg (args, StrBuf *); dest->Set (s1, s2 - s1); } s1 = s2 + match.Length (); } else if (!f2 && prevVar) { // Save final %s dest = va_arg (args, StrBuf *); dest->Set (s1); s1 += dest->Length (); } if (f2) prevVar = true; else if (*s1 != '\0') { status = false; // fail: trailing junk break; } } va_end (args); return status; } void NewResolve::ParseFileName (const char *name, StrBuf *rtnPath, StrBuf *rtnTail) { const char * p; if ((p = strrchr (name, '/')) || (p = strrchr (name, '\\'))) { rtnPath->Set (name, p - name); rtnTail->Set (&p[1]); } else { rtnPath->Set ("."); rtnTail->Set (name); } } void NewResolve::BackupFile (FileSys *file, StrBuf &newName) { FileSys * backup; StrBuf msg; Error err; backup = FileSys::Create (FST_TEXT); backup->Set (newName); backup->MkDir (&err); if (err.Test ()) { Message (&err); err.Clear (); } backup->Unlink (&err); if (err.Test ()) err.Clear (); file->Copy (backup, FPM_RO, &err); if (err.Test ()) { Message (&err); err.Clear (); } delete backup; } void NewResolve::MoveFile (FileSys *file, StrBuf &newName) { FileSys * newFile; StrBuf msg; Error err; newFile = FileSys::Create (FST_TEXT); newFile->Set (newName); newFile->MkDir (&err); if (err.Test ()) { Message (&err); err.Clear (); } file->Rename (newFile, &err); if (err.Test ()) { Message (&err); err.Clear (); } file->ClearDeleteOnClose (); file->Close (&err); if (err.Test ()) { Message (&err); err.Clear (); } file->Set (newName); file->Open (FOM_READ, &err); if (err.Test ()) { Message (&err); err.Clear (); } delete newFile; } void NewResolve::TempName (StrBuf *path, StrBuf *file, const char *which, StrBuf *result) { // Location of -k saved temp files result->Set (path); result->Append ("/_restmp/"); result->Append (file); result->Append ("/"); result->Append (which); } void NewResolve::PrintStart (const char *name, const char *value) { if (value) printf ("%s: %s\n", name, value); else printf ("%s:\n", name); } void NewResolve::PrintValue (const char *name, const char *value) { printf (" %-20s= %s\n", name, value); } void NewResolve::PrintValue (const char *name, int value) { printf (" %-20s= %d\n", name, value); } void NewResolve::PrintDone () { printf ("\n"); } //------------------------------------------------------------------------ // Main //------------------------------------------------------------------------ int main (int argc, char **argv) { int runArgc = 0; char ** runArgv = new char *[argc + 2]; P4Session session; NewResolve callbacks; // 1. Process Arguments session.SetProg (argv[0]); runArgv[runArgc++] = "-o"; for (--argc, ++argv; argc > 0; --argc, ++argv) { if (**argv == '-') { if (strcmp (*argv, "-ax") == 0) ; else if (strcmp (*argv, "-fx") == 0) callbacks.SetIgnoreP4Conflicts (true); else if (strcmp (*argv, "-k") == 0) callbacks.SetKeepTemp (true); else if (strcmp (*argv, "-n") == 0) callbacks.SetNoCommit (true); else { PrintHelp ("resolve"); exit (1); } } else runArgv[runArgc++] = *argv; } // 2. Connect to p4d if (!session.ConnectToServer ()) exit (1); // 3. Run "resolve -o [file ...]" session.Run ("resolve", runArgc, runArgv, &callbacks); // 4. Disconnect session.DisconnectFromServer (); delete runArgv; exit (0); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 5435 | Kyle Turner |
Initial version of "p4 resolve" extensions using C++ P4API. Extensions allow users to give more weight to the external merge program specified by the environment variable $P4MERGE. See README.txt for more information. |