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 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); } } } }
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 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) ) ); } } }
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 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 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::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; }