static void ParseDiffResults(const FP4RecordSet& InRecords, TArray<FString>& OutModifiedFiles) { if (InRecords.Num() > 0) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString FileName = ClientRecord(TEXT("clientFile")); FPaths::NormalizeFilename(FileName); OutModifiedFiles.Add(FileName); } } }
static void ParseFilesResults(const FP4RecordSet& InRecords, TArray< TSharedRef<ISourceControlRevision, ESPMode::ThreadSafe> >& OutRevisions, const FString& InClientRoot) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString DepotFile = ClientRecord(TEXT("depotFile")); FString RevisionNumber = ClientRecord(TEXT("rev")); FString Date = ClientRecord(TEXT("time")); FString ChangelistNumber = ClientRecord(TEXT("change")); FString Action = ClientRecord(TEXT("action")); check(RevisionNumber.Len() != 0); // @todo: this revision is incomplete, but is sufficient for now given the usage of labels to get files, rather // than review revision histories. TSharedRef<FPerforceSourceControlRevision, ESPMode::ThreadSafe> Revision = MakeShareable( new FPerforceSourceControlRevision() ); Revision->FileName = DepotFile; Revision->RevisionNumber = FCString::Atoi(*RevisionNumber); Revision->ChangelistNumber = FCString::Atoi(*ChangelistNumber); Revision->Action = Action; Revision->Date = FDateTime(1970, 1, 1, 0, 0, 0, 0) + FTimespan(0, 0, FCString::Atoi(*Date)); OutRevisions.Add(Revision); } }
/** Simple parsing of a record set to update state */ static void ParseRecordSetForState(const FP4RecordSet& InRecords, TMap<FString, EPerforceState::Type>& OutResults) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString FileName = ClientRecord(TEXT("clientFile")); FString Action = ClientRecord(TEXT("action")); check(FileName.Len()); FString FullPath(FileName); FPaths::NormalizeFilename(FullPath); if(Action.Len() > 0) { if(Action == TEXT("add")) { OutResults.Add(FullPath, EPerforceState::OpenForAdd); } else if(Action == TEXT("edit")) { OutResults.Add(FullPath, EPerforceState::CheckedOut); } else if(Action == TEXT("delete")) { OutResults.Add(FullPath, EPerforceState::MarkedForDelete); } else if(Action == TEXT("abandoned")) { OutResults.Add(FullPath, EPerforceState::NotInDepot); } else if(Action == TEXT("reverted")) { FString OldAction = ClientRecord(TEXT("oldAction")); if(OldAction == TEXT("add")) { OutResults.Add(FullPath, EPerforceState::NotInDepot); } else if(OldAction == TEXT("edit")) { OutResults.Add(FullPath, EPerforceState::ReadOnly); } else if(OldAction == TEXT("delete")) { OutResults.Add(FullPath, EPerforceState::ReadOnly); } } else if(Action == TEXT("branch")) { OutResults.Add(FullPath, EPerforceState::Branched); } } } }
static bool CheckUnicodeStatus(ClientApi& P4Client, bool& bIsUnicodeServer, TArray<FText>& OutErrorMessages) { #if USE_P4_API FP4RecordSet Records; FP4ClientUser User(Records, false, OutErrorMessages); P4Client.Run("info", &User); if(Records.Num() > 0) { bIsUnicodeServer = Records[0].Find(TEXT("unicode")) != NULL; } else { bIsUnicodeServer = false; } #endif return OutErrorMessages.Num() == 0; }
static void ParseOpenedResults(const FP4RecordSet& InRecords, const FString& ClientName, const FString& ClientRoot, TMap<FString, EPerforceState::Type>& OutResults) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString ClientFileName = ClientRecord(TEXT("clientFile")); FString Action = ClientRecord(TEXT("action")); check(ClientFileName.Len() > 0); // Convert the depot file name to a local file name FString FullPath = ClientFileName; const FString PathRoot = FString::Printf(TEXT("//%s"), *ClientName); if (FullPath.StartsWith(PathRoot)) { const bool bIsNullClientRootPath = (ClientRoot == TEXT("null")); if ( bIsNullClientRootPath ) { // Null clients use the pattern in PathRoot: //Workspace/FileName // Here we chop off the '//Workspace/' to return the workspace filename FullPath = FullPath.RightChop(PathRoot.Len() + 1); } else { // This is a normal workspace where we can simply replace the pathroot with the client root to form the filename FullPath = FullPath.Replace(*PathRoot, *ClientRoot); } } else { // This file is not in the workspace, ignore it continue; } if (Action.Len() > 0) { if(Action == TEXT("add")) { OutResults.Add(FullPath, EPerforceState::OpenForAdd); } else if(Action == TEXT("edit")) { OutResults.Add(FullPath, EPerforceState::CheckedOut); } else if(Action == TEXT("delete")) { OutResults.Add(FullPath, EPerforceState::MarkedForDelete); } } } }
bool FPerforceConnectWorker::Execute(FPerforceSourceControlCommand& InCommand) { FScopedPerforceConnection ScopedConnection(InCommand); if (!InCommand.IsCanceled() && ScopedConnection.IsValid()) { FPerforceConnection& Connection = ScopedConnection.GetConnection(); TArray<FString> Parameters; FP4RecordSet Records; Parameters.Add(TEXT("-o")); Parameters.Add(InCommand.ConnectionInfo.Workspace); InCommand.bCommandSuccessful = Connection.RunCommand(TEXT("client"), Parameters, Records, InCommand.ErrorMessages, FOnIsCancelled::CreateRaw(&InCommand, &FPerforceSourceControlCommand::IsCanceled), InCommand.bConnectionDropped); // If there are error messages, user name is most likely invalid. Otherwise, make sure workspace actually // exists on server by checking if we have it's update date. InCommand.bCommandSuccessful &= InCommand.ErrorMessages.Num() == 0 && Records.Num() > 0 && Records[0].Contains(TEXT("Update")); if (!InCommand.bCommandSuccessful && InCommand.ErrorMessages.Num() == 0) { InCommand.ErrorMessages.Add(LOCTEXT("InvalidWorkspace", "Invalid workspace.")); } // check if we can actually work with this workspace if(InCommand.bCommandSuccessful) { FText Notification; InCommand.bCommandSuccessful = CheckWorkspaceRecordSet(Records, InCommand.ErrorMessages, Notification); if(!InCommand.bCommandSuccessful) { check(InCommand.Operation->GetName() == GetName()); TSharedRef<FConnect, ESPMode::ThreadSafe> Operation = StaticCastSharedRef<FConnect>(InCommand.Operation); Operation->SetErrorText(Notification); } } if(InCommand.bCommandSuccessful) { ParseRecordSet(Records, InCommand.InfoMessages); } } return InCommand.bCommandSuccessful; }
/** Simple parsing of a record set into strings, one string per record */ static void ParseRecordSet(const FP4RecordSet& InRecords, TArray<FText>& OutResults) { const FString Delimiter = FString(TEXT(" ")); for (int32 RecordIndex = 0; RecordIndex < InRecords.Num(); ++RecordIndex) { const FP4Record& ClientRecord = InRecords[RecordIndex]; for(FP4Record::TConstIterator It = ClientRecord.CreateConstIterator(); It; ++It) { OutResults.Add(FText::FromString(It.Key() + Delimiter + It.Value())); } } }
static void ParseGetLabelsResults(const FP4RecordSet& InRecords, TArray< TSharedRef<ISourceControlLabel> >& OutLabels) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString LabelName = ClientRecord(TEXT("label")); if(LabelName.Len() > 0) { OutLabels.Add(MakeShareable( new FPerforceSourceControlLabel(LabelName) ) ); } } }
/** * Runs "client" command to test if the connection is actually OK. ClientApi::Init() only checks * if it can connect to server, doesn't verify user name nor workspace name. */ static bool TestConnection(ClientApi& P4Client, const FString& ClientSpecName, bool bIsUnicodeServer, TArray<FText>& OutErrorMessages) { FP4RecordSet Records; FP4ClientUser User(Records, bIsUnicodeServer, OutErrorMessages); UTF8CHAR* ClientSpecUTF8Name = nullptr; if(bIsUnicodeServer) { FTCHARToUTF8 UTF8String(*ClientSpecName); ClientSpecUTF8Name = new UTF8CHAR[UTF8String.Length() + 1]; FMemory::Memcpy(ClientSpecUTF8Name, UTF8String.Get(), UTF8String.Length() + 1); } else { ClientSpecUTF8Name = new UTF8CHAR[ClientSpecName.Len() + 1]; FMemory::Memcpy(ClientSpecUTF8Name, TCHAR_TO_ANSI(*ClientSpecName), ClientSpecName.Len() + 1); } const char *ArgV[] = { "-o", (char*)ClientSpecUTF8Name }; #if USE_P4_API P4Client.SetArgv(2, const_cast<char*const*>(ArgV)); P4Client.Run("client", &User); #endif // clean up args delete [] ClientSpecUTF8Name; // If there are error messages, user name is most likely invalid. Otherwise, make sure workspace actually // exists on server by checking if we have it's update date. bool bConnectionOK = OutErrorMessages.Num() == 0 && Records.Num() > 0 && Records[0].Contains(TEXT("Update")); if (!bConnectionOK && OutErrorMessages.Num() == 0) { OutErrorMessages.Add(LOCTEXT("InvalidWorkspace", "Invalid Workspace")); } return bConnectionOK; }
static FText ParseSubmitResults(const FP4RecordSet& InRecords) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; const FString SubmittedChange = ClientRecord(TEXT("submittedChange")); if(SubmittedChange.Len() > 0) { return FText::Format(LOCTEXT("SubmitMessage", "Submitted changelist {0}"), FText::FromString(SubmittedChange)); } } return LOCTEXT("SubmitMessageUnknown", "Submitted changelist"); }
static void ParseSyncResults(const FP4RecordSet& InRecords, TMap<FString, EPerforceState::Type>& OutResults) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString FileName = ClientRecord(TEXT("clientFile")); FString Action = ClientRecord(TEXT("action")); check(FileName.Len()); FString FullPath(FileName); FPaths::NormalizeFilename(FullPath); if(Action.Len() > 0) { if(Action == TEXT("updated")) { OutResults.Add(FullPath, EPerforceState::ReadOnly); } } } }
static void ParseHistoryResults(const FP4RecordSet& InRecords, const TArray<FPerforceSourceControlState>& InStates, FPerforceUpdateStatusWorker::FHistoryMap& OutHistory) { if (InRecords.Num() > 0) { // Iterate over each record, extracting the relevant information for each for (int32 RecordIndex = 0; RecordIndex < InRecords.Num(); ++RecordIndex) { const FP4Record& ClientRecord = InRecords[RecordIndex]; // Extract the file name check(ClientRecord.Contains(TEXT("depotFile"))); FString DepotFileName = ClientRecord(TEXT("depotFile")); FString LocalFileName = FindWorkspaceFile(InStates, DepotFileName); TArray< TSharedRef<FPerforceSourceControlRevision, ESPMode::ThreadSafe> > Revisions; int32 RevisionNumbers = 0; for (;;) { // Extract the revision number FString VarName = FString::Printf(TEXT("rev%d"), RevisionNumbers); if (!ClientRecord.Contains(*VarName)) { // No more revisions break; } FString RevisionNumber = ClientRecord(*VarName); // Extract the user name VarName = FString::Printf(TEXT("user%d"), RevisionNumbers); check(ClientRecord.Contains(*VarName)); FString UserName = ClientRecord(*VarName); // Extract the date VarName = FString::Printf(TEXT("time%d"), RevisionNumbers); check(ClientRecord.Contains(*VarName)); FString Date = ClientRecord(*VarName); // Extract the changelist number VarName = FString::Printf(TEXT("change%d"), RevisionNumbers); check(ClientRecord.Contains(*VarName)); FString ChangelistNumber = ClientRecord(*VarName); // Extract the description VarName = FString::Printf(TEXT("desc%d"), RevisionNumbers); check(ClientRecord.Contains(*VarName)); FString Description = ClientRecord(*VarName); // Extract the action VarName = FString::Printf(TEXT("action%d"), RevisionNumbers); check(ClientRecord.Contains(*VarName)); FString Action = ClientRecord(*VarName); FString FileSize(TEXT("0")); // Extract the file size if(Action.ToLower() != TEXT("delete") && Action.ToLower() != TEXT("move/delete")) //delete actions don't have a fileSize from PV4 { VarName = FString::Printf(TEXT("fileSize%d"), RevisionNumbers); check(ClientRecord.Contains(*VarName)); FileSize = ClientRecord(*VarName); } // Extract the clientspec/workspace VarName = FString::Printf(TEXT("client%d"), RevisionNumbers); check(ClientRecord.Contains(*VarName)); FString ClientSpec = ClientRecord(*VarName); // check for branch TSharedPtr<FPerforceSourceControlRevision, ESPMode::ThreadSafe> BranchSource; VarName = FString::Printf(TEXT("how%d,0"), RevisionNumbers); if(ClientRecord.Contains(*VarName)) { BranchSource = MakeShareable( new FPerforceSourceControlRevision() ); VarName = FString::Printf(TEXT("file%d,0"), RevisionNumbers); FString BranchSourceFileName = ClientRecord(*VarName); BranchSource->FileName = FindWorkspaceFile(InStates, BranchSourceFileName); VarName = FString::Printf(TEXT("erev%d,0"), RevisionNumbers); FString BranchSourceRevision = ClientRecord(*VarName); BranchSource->RevisionNumber = FCString::Atoi(*BranchSourceRevision); } TSharedRef<FPerforceSourceControlRevision, ESPMode::ThreadSafe> Revision = MakeShareable( new FPerforceSourceControlRevision() ); Revision->FileName = LocalFileName; Revision->RevisionNumber = FCString::Atoi(*RevisionNumber); Revision->Revision = RevisionNumber; Revision->ChangelistNumber = FCString::Atoi(*ChangelistNumber); Revision->Description = Description; Revision->UserName = UserName; Revision->ClientSpec = ClientSpec; Revision->Action = Action; Revision->BranchSource = BranchSource; Revision->Date = FDateTime(1970, 1, 1, 0, 0, 0, 0) + FTimespan::FromSeconds(FCString::Atoi(*Date)); Revision->FileSize = FCString::Atoi(*FileSize); Revisions.Add(Revision); RevisionNumbers++; } if(Revisions.Num() > 0) { OutHistory.Add(LocalFileName, Revisions); } } } }
static void ParseUpdateStatusResults(const FP4RecordSet& InRecords, const TArray<FText>& ErrorMessages, TArray<FPerforceSourceControlState>& OutStates) { // Iterate over each record found as a result of the command, parsing it for relevant information for (int32 Index = 0; Index < InRecords.Num(); ++Index) { const FP4Record& ClientRecord = InRecords[Index]; FString FileName = ClientRecord(TEXT("clientFile")); FString DepotFileName = ClientRecord(TEXT("depotFile")); FString HeadRev = ClientRecord(TEXT("headRev")); FString HaveRev = ClientRecord(TEXT("haveRev")); FString OtherOpen = ClientRecord(TEXT("otherOpen")); FString OpenType = ClientRecord(TEXT("type")); FString HeadAction = ClientRecord(TEXT("headAction")); FString Action = ClientRecord(TEXT("action")); FString HeadType = ClientRecord(TEXT("headType")); const bool bUnresolved = ClientRecord.Contains(TEXT("unresolved")); FString FullPath(FileName); FPaths::NormalizeFilename(FullPath); OutStates.Add(FPerforceSourceControlState(FullPath)); FPerforceSourceControlState& State = OutStates.Last(); State.DepotFilename = DepotFileName; State.State = EPerforceState::ReadOnly; if (Action.Len() > 0 && Action == TEXT("add")) { State.State = EPerforceState::OpenForAdd; } else if (Action.Len() > 0 && Action == TEXT("delete")) { State.State = EPerforceState::MarkedForDelete; } else if (OpenType.Len() > 0) { if(Action.Len() > 0 && Action == TEXT("branch")) { State.State = EPerforceState::Branched; } else { State.State = EPerforceState::CheckedOut; } } else if (OtherOpen.Len() > 0) { // OtherOpen just reports the number of developers that have a file open, now add a string for every entry int32 OtherOpenNum = FCString::Atoi(*OtherOpen); for ( int32 OpenIdx = 0; OpenIdx < OtherOpenNum; ++OpenIdx ) { const FString OtherOpenRecordKey = FString::Printf(TEXT("otherOpen%d"), OpenIdx); const FString OtherOpenRecordValue = ClientRecord(OtherOpenRecordKey); State.OtherUserCheckedOut += OtherOpenRecordValue; if(OpenIdx < OtherOpenNum - 1) { State.OtherUserCheckedOut += TEXT(", "); } } State.State = EPerforceState::CheckedOutOther; } //file has been previously deleted, ok to add again else if (HeadAction.Len() > 0 && HeadAction == TEXT("delete")) { State.State = EPerforceState::NotInDepot; } if (HeadRev.Len() > 0 && HaveRev.Len() > 0) { TTypeFromString<int>::FromString(State.DepotRevNumber, *HeadRev); TTypeFromString<int>::FromString(State.LocalRevNumber, *HaveRev); if( bUnresolved ) { int32 ResolveActionNumber = 0; for (;;) { // Extract the revision number FString VarName = FString::Printf(TEXT("resolveAction%d"), ResolveActionNumber); if (!ClientRecord.Contains(*VarName)) { // No more revisions ensureMsgf( ResolveActionNumber > 0, TEXT("Resolve is pending but no resolve actions for file %s"), *FileName ); break; } VarName = FString::Printf(TEXT("resolveBaseFile%d"), ResolveActionNumber); FString ResolveBaseFile = ClientRecord(VarName); VarName = FString::Printf(TEXT("resolveFromFile%d"), ResolveActionNumber); FString ResolveFromFile = ClientRecord(VarName); if(!ensureMsgf( ResolveFromFile == ResolveBaseFile, TEXT("Text cannot resolve %s with %s, we do not support cross file merging"), *ResolveBaseFile, *ResolveFromFile ) ) { break; } VarName = FString::Printf(TEXT("resolveBaseRev%d"), ResolveActionNumber); FString ResolveBaseRev = ClientRecord(VarName); TTypeFromString<int>::FromString(State.PendingResolveRevNumber, *ResolveBaseRev); ++ResolveActionNumber; } } } // Check binary status State.bBinary = false; if (HeadType.Len() > 0 && HeadType.Contains(TEXT("binary"))) { State.bBinary = true; } // Check exclusive checkout flag State.bExclusiveCheckout = false; if (HeadType.Len() > 0 && HeadType.Contains(TEXT("+l"))) { State.bExclusiveCheckout = true; } } // also see if we can glean anything from the error messages for (int32 Index = 0; Index < ErrorMessages.Num(); ++Index) { const FText& Error = ErrorMessages[Index]; //@todo P4 could be returning localized error messages int32 NoSuchFilePos = Error.ToString().Find(TEXT(" - no such file(s).\n"), ESearchCase::IgnoreCase, ESearchDir::FromStart); if(NoSuchFilePos != INDEX_NONE) { // found an error about a file that is not in the depot FString FullPath(Error.ToString().Left(NoSuchFilePos)); FPaths::NormalizeFilename(FullPath); OutStates.Add(FPerforceSourceControlState(FullPath)); FPerforceSourceControlState& State = OutStates.Last(); State.State = EPerforceState::NotInDepot; } //@todo P4 could be returning localized error messages int32 NotUnderClientRootPos = Error.ToString().Find(TEXT("' is not under client's root"), ESearchCase::IgnoreCase, ESearchDir::FromStart); if(NotUnderClientRootPos != INDEX_NONE) { // found an error about a file that is not under the client root static const FString Prefix(TEXT("Path \'")); FString FullPath(Error.ToString().Mid(Prefix.Len(), NotUnderClientRootPos - Prefix.Len())); FPaths::NormalizeFilename(FullPath); OutStates.Add(FPerforceSourceControlState(FullPath)); FPerforceSourceControlState& State = OutStates.Last(); State.State = EPerforceState::NotUnderClientRoot; } } }
bool FPerforceConnection::RunCommand(const FString& InCommand, const TArray<FString>& InParameters, FP4RecordSet& OutRecordSet, TArray<FText>& OutErrorMessage, FOnIsCancelled InIsCancelled, bool& OutConnectionDropped, const bool bInStandardDebugOutput, const bool bInAllowRetry) { #if USE_P4_API if (!bEstablishedConnection) { return false; } FString FullCommand = InCommand; // Prepare arguments int32 ArgC = InParameters.Num(); UTF8CHAR** ArgV = new UTF8CHAR*[ArgC]; for (int32 Index = 0; Index < ArgC; Index++) { if(bIsUnicode) { FTCHARToUTF8 UTF8String(*InParameters[Index]); ArgV[Index] = new UTF8CHAR[UTF8String.Length() + 1]; FMemory::Memcpy(ArgV[Index], UTF8String.Get(), UTF8String.Length() + 1); } else { ArgV[Index] = new UTF8CHAR[InParameters[Index].Len() + 1]; FMemory::Memcpy(ArgV[Index], TCHAR_TO_ANSI(*InParameters[Index]), InParameters[Index].Len() + 1); } if (bInStandardDebugOutput) { FullCommand += TEXT(" "); FullCommand += InParameters[Index]; } } if (bInStandardDebugOutput) { UE_LOG( LogSourceControl, Log, TEXT("Attempting 'p4 %s'"), *FullCommand ); } double SCCStartTime = FPlatformTime::Seconds(); P4Client.SetArgv(ArgC, (char**)ArgV); FP4KeepAlive KeepAlive(InIsCancelled); P4Client.SetBreak(&KeepAlive); OutRecordSet.Reset(); FP4ClientUser User(OutRecordSet, bIsUnicode, OutErrorMessage); P4Client.Run(FROM_TCHAR(*InCommand, bIsUnicode), &User); if ( P4Client.Dropped() ) { OutConnectionDropped = true; } P4Client.SetBreak(NULL); // Free arguments for (int32 Index = 0; Index < ArgC; Index++) { delete [] ArgV[Index]; } delete [] ArgV; if (bInStandardDebugOutput) { UE_LOG( LogSourceControl, VeryVerbose, TEXT("P4 execution time: %0.4f seconds. Command: %s"), FPlatformTime::Seconds() - SCCStartTime, *FullCommand ); } #endif return OutRecordSet.Num() > 0; }
bool FPerforceConnection::GetWorkspaceList(const FPerforceConnectionInfo& InConnectionInfo, FOnIsCancelled InOnIsCanceled, TArray<FString>& OutWorkspaceList, TArray<FText>& OutErrorMessages) { if(bEstablishedConnection) { TArray<FString> Params; bool bAllowWildHosts = !GIsBuildMachine; Params.Add(TEXT("-u")); Params.Add(InConnectionInfo.UserName); FP4RecordSet Records; bool bConnectionDropped = false; bool bCommandOK = RunCommand(TEXT("clients"), Params, Records, OutErrorMessages, InOnIsCanceled, bConnectionDropped); if (bCommandOK) { FString ApplicationPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*FPaths::GameDir()).ToLower(); FString LocalHostName = InConnectionInfo.HostOverride; if(LocalHostName.Len() == 0) { // No host override, check environment variable TCHAR P4HostEnv[256]; FPlatformMisc::GetEnvironmentVariable(TEXT("P4HOST"), P4HostEnv, ARRAY_COUNT(P4HostEnv)); LocalHostName = P4HostEnv; } if (LocalHostName.Len() == 0) { // no host name, use local machine name LocalHostName = FString(FPlatformProcess::ComputerName()).ToLower(); } else { LocalHostName = LocalHostName.ToLower(); } for (int32 Index = 0; Index < Records.Num(); ++Index) { const FP4Record& ClientRecord = Records[Index]; FString ClientName = ClientRecord("client"); FString HostName = ClientRecord("Host"); FString ClientRootPath = ClientRecord("Root").ToLower(); //this clientspec has to be meant for this machine ( "" hostnames mean any host can use ths clientspec in p4 land) bool bHostNameMatches = (LocalHostName == HostName.ToLower()); bool bHostNameWild = (HostName.Len() == 0); if( bHostNameMatches || (bHostNameWild && bAllowWildHosts) ) { // A workspace root could be "null" which allows the user to map depot locations to different drives. // Allow these workspaces since we already allow workspaces mapped to drive letters. const bool bIsNullClientRootPath = (ClientRootPath == TEXT("null")); //make sure all slashes point the same way ClientRootPath = ClientRootPath.Replace(TEXT("\\"), TEXT("/")); ApplicationPath = ApplicationPath.Replace(TEXT("\\"), TEXT("/")); if (!ClientRootPath.EndsWith(TEXT("/"))) { ClientRootPath += TEXT("/"); } // Only allow paths that are ACTUALLY legit for this application if (bIsNullClientRootPath || ApplicationPath.Contains(ClientRootPath) ) { OutWorkspaceList.Add(ClientName); } else { UE_LOG(LogSourceControl, Error, TEXT(" %s client specs rejected due to root directory mismatch (%s)"), *ClientName, *ClientRootPath); } //Other useful fields: Description, Owner, Host } else { UE_LOG(LogSourceControl, Error, TEXT(" %s client specs rejected due to host name mismatch (%s)"), *ClientName, *HostName); } } } return bCommandOK; } return false; }