// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "SourceControlWindows.h" #include "SSourceControlSubmit.h" #include "FileHelpers.h" #include "ISourceControlModule.h" #include "SourceControlHelpers.h" #include "SourceControlOperations.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Notifications/NotificationManager.h" #include "Logging/MessageLog.h" #include "Logging/TokenizedMessage.h" #include "Misc/MessageDialog.h" #include "Widgets/Notifications/SNotificationList.h" IMPLEMENT_MODULE( FDefaultModuleImpl, SourceControlWindows ); #if SOURCE_CONTROL_WITH_SLATE #define LOCTEXT_NAMESPACE "SourceControlWindows" //--------------------------------------------------------------------------------------- // FCheckinResultInfo FCheckinResultInfo::FCheckinResultInfo() : Result(ECommandResult::Failed) , bAutoCheckedOut(false) { } //--------------------------------------------------------------------------------------- // FSourceControlWindows TWeakPtr FSourceControlWindows::ChoosePackagesToCheckInNotification; bool FSourceControlWindows::ChoosePackagesToCheckIn(const FSourceControlWindowsOnCheckInComplete& OnCompleteDelegate) { if (!ISourceControlModule::Get().IsEnabled()) { FCheckinResultInfo ResultInfo; ResultInfo.Description = LOCTEXT("SourceControlDisabled", "Source control is not enabled."); OnCompleteDelegate.ExecuteIfBound(ResultInfo); return false; } if (!ISourceControlModule::Get().GetProvider().IsAvailable()) { FCheckinResultInfo ResultInfo; ResultInfo.Description = LOCTEXT("NoSCCConnection", "No connection to source control available!"); FMessageLog EditorErrors("EditorErrors"); EditorErrors.Warning(ResultInfo.Description)->AddToken( FDocumentationToken::Create(TEXT("Engine/UI/SourceControl"))); EditorErrors.Notify(); OnCompleteDelegate.ExecuteIfBound(ResultInfo); return false; } // Start selection process... // make sure we update the SCC status of all packages (this could take a long time, so we will run it as a background task) TArray Filenames; Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir())); Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir())); Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir())); Filenames.Add(FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath())); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); FSourceControlOperationRef Operation = ISourceControlOperation::Create(); SourceControlProvider.Execute( Operation, Filenames, EConcurrency::Asynchronous, FSourceControlOperationComplete::CreateStatic(&FSourceControlWindows::ChoosePackagesToCheckInCallback, OnCompleteDelegate)); if (ChoosePackagesToCheckInNotification.IsValid()) { ChoosePackagesToCheckInNotification.Pin()->ExpireAndFadeout(); } FNotificationInfo Info(LOCTEXT("ChooseAssetsToCheckInIndicator", "Checking for assets to check in...")); Info.bFireAndForget = false; Info.ExpireDuration = 0.0f; Info.FadeOutDuration = 1.0f; if (SourceControlProvider.CanCancelOperation(Operation)) { Info.ButtonDetails.Add(FNotificationButtonInfo( LOCTEXT("ChoosePackagesToCheckIn_CancelButton", "Cancel"), LOCTEXT("ChoosePackagesToCheckIn_CancelButtonTooltip", "Cancel the check in operation."), FSimpleDelegate::CreateStatic(&FSourceControlWindows::ChoosePackagesToCheckInCancelled, Operation) )); } ChoosePackagesToCheckInNotification = FSlateNotificationManager::Get().AddNotification(Info); if (ChoosePackagesToCheckInNotification.IsValid()) { ChoosePackagesToCheckInNotification.Pin()->SetCompletionState(SNotificationItem::CS_Pending); } return true; } bool FSourceControlWindows::CanChoosePackagesToCheckIn() { return !ChoosePackagesToCheckInNotification.IsValid(); } bool FSourceControlWindows::PromptForCheckin(FCheckinResultInfo& OutResultInfo, const TArray& InPackageNames, const TArray& InPendingDeletePaths, const TArray& InConfigFiles, bool bUseSourceControlStateCache) { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Get filenames for packages and config to be checked in TArray AllFiles = SourceControlHelpers::PackageFilenames(InPackageNames); AllFiles.Append(InConfigFiles); // Prepare a list of files to have their states updated if (!bUseSourceControlStateCache) { TArray UpdateRequest; UpdateRequest.Append(AllFiles); // If there are pending delete paths to update, add them here. UpdateRequest.Append(InPendingDeletePaths); // Force an update on everything that's been requested if (UpdateRequest.Num() > 0) { SourceControlProvider.Execute(ISourceControlOperation::Create(), UpdateRequest); } } // Get file status of packages and config TArray States; SourceControlProvider.GetState(AllFiles, States, EStateCacheUsage::Use); if (InPendingDeletePaths.Num() > 0) { // Get any files pending delete TArray PendingDeleteItems = SourceControlProvider.GetCachedStateByPredicate( [](const FSourceControlStateRef& State) { return State->IsDeleted(); } ); // And append them to the list States.Append(PendingDeleteItems); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Exit if no assets needing check in if (States.Num() == 0) { OutResultInfo.Result = ECommandResult::Succeeded; OutResultInfo.Description = LOCTEXT("NoAssetsToCheckIn", "No assets to check in!"); FMessageLog EditorErrors("EditorErrors"); EditorErrors.Warning(OutResultInfo.Description); EditorErrors.Notify(); // Consider it a success even if no files were checked in return true; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Create a submit files window TSharedRef NewWindow = SNew(SWindow) .Title(NSLOCTEXT("SourceControl.SubmitWindow", "Title", "Submit Files")) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(600, 400)) .SupportsMaximize(true) .SupportsMinimize(false); TSharedRef SourceControlWidget = SNew(SSourceControlSubmitWidget) .ParentWindow(NewWindow) .Items(States); NewWindow->SetContent( SourceControlWidget ); FSlateApplication::Get().AddModalWindow(NewWindow, NULL); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Exit if cancelled by user if (SourceControlWidget->GetResult() == ESubmitResults::SUBMIT_CANCELED) { OutResultInfo.Result = ECommandResult::Cancelled; OutResultInfo.Description = LOCTEXT("CheckinCancelled", "File check in cancelled."); return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Get description from the dialog FChangeListDescription Description; SourceControlWidget->FillChangeListDescription(Description); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Revert any unchanged files if (Description.FilesForSubmit.Num() > 0) { SourceControlHelpers::RevertUnchangedFiles(SourceControlProvider, Description.FilesForSubmit); // Make sure all files are still checked out for (int32 VerifyIndex = Description.FilesForSubmit.Num()-1; VerifyIndex >= 0; --VerifyIndex) { FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(Description.FilesForSubmit[VerifyIndex], EStateCacheUsage::Use); if( SourceControlState.IsValid() && !SourceControlState->IsCheckedOut() && !SourceControlState->IsAdded() && !SourceControlState->IsDeleted() ) { Description.FilesForSubmit.RemoveAt(VerifyIndex); } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Mark files for add as needed bool bSuccess = true; // Overall success bool bAddSuccess = true; bool bCheckinSuccess = true; TArray CombinedFileList = Description.FilesForAdd; CombinedFileList.Append(Description.FilesForSubmit); if (Description.FilesForAdd.Num() > 0) { bAddSuccess = SourceControlProvider.Execute(ISourceControlOperation::Create(), Description.FilesForAdd) == ECommandResult::Succeeded; bSuccess &= bAddSuccess; OutResultInfo.FilesAdded = Description.FilesForAdd; if (!bAddSuccess) { // Note that this message may be overwritten with a checkin error below. OutResultInfo.Description = LOCTEXT("SCC_Add_Files_Error", "One or more files were not able to be marked for add to version control!"); } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Any files to check in? if (CombinedFileList.Num() == 0) { OutResultInfo.Result = bSuccess ? ECommandResult::Succeeded : ECommandResult::Failed; if (OutResultInfo.Description.IsEmpty()) { OutResultInfo.Description = LOCTEXT("SCC_No_Files", "No files were selected to check in to version control."); } return bSuccess; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Check in files TSharedRef CheckInOperation = ISourceControlOperation::Create(); CheckInOperation->SetDescription(Description.Description); bCheckinSuccess = SourceControlProvider.Execute(CheckInOperation, CombinedFileList) == ECommandResult::Succeeded; bSuccess &= bCheckinSuccess; if (bCheckinSuccess) { // report success with a notification FNotificationInfo Info(CheckInOperation->GetSuccessMessage()); Info.ExpireDuration = 8.0f; Info.HyperlinkText = LOCTEXT("SCC_Checkin_ShowLog", "Show Message Log"); Info.Hyperlink = FSimpleDelegate::CreateStatic([](){ FMessageLog("SourceControl").Open(EMessageSeverity::Info, true); }); FSlateNotificationManager::Get().AddNotification(Info); // also add to the log FMessageLog("SourceControl").Info(CheckInOperation->GetSuccessMessage()); OutResultInfo.Description = CheckInOperation->GetSuccessMessage(); OutResultInfo.FilesSubmitted = Description.FilesForSubmit; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Exit if errors if (!bSuccess) { FText Message(LOCTEXT("SCC_Checkin_Failed", "Failed to check in files!")); FMessageLog("SourceControl").Notify(Message); OutResultInfo.Result = ECommandResult::Failed; if (!bCheckinSuccess || OutResultInfo.Description.IsEmpty()) { OutResultInfo.Description = Message; } return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Do we want to to re-check out the files we just checked in? if (SourceControlWidget->WantToKeepCheckedOut()) { // Re-check out files if (SourceControlProvider.Execute(ISourceControlOperation::Create(), CombinedFileList) == ECommandResult::Succeeded) { OutResultInfo.bAutoCheckedOut = true; } else { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SCC_Checkin_ReCheckOutFailed", "Failed to re-check out files.")); } } return true; } bool FSourceControlWindows::PromptForCheckin(bool bUseSourceControlStateCache, const TArray& InPackageNames, const TArray& InPendingDeletePaths, const TArray& InConfigFiles) { FCheckinResultInfo ResultInfo; return PromptForCheckin(ResultInfo, InPackageNames, InPendingDeletePaths, InConfigFiles, bUseSourceControlStateCache); } // Note that: // - FSourceControlWindows::DisplayRevisionHistory() is defined in SSourceControlHistory.cpp // - FSourceControlWindows::PromptForRevert() is defined in SSourceControlRevert.cpp void FSourceControlWindows::ChoosePackagesToCheckInCompleted(const TArray& LoadedPackages, const TArray& PackageNames, const TArray& ConfigFiles, FCheckinResultInfo& OutResultInfo) { if (ChoosePackagesToCheckInNotification.IsValid()) { ChoosePackagesToCheckInNotification.Pin()->ExpireAndFadeout(); } ChoosePackagesToCheckInNotification.Reset(); // Prompt the user to ask if they would like to first save any dirty packages they are trying to check-in const FEditorFileUtils::EPromptReturnCode UserResponse = FEditorFileUtils::PromptForCheckoutAndSave(LoadedPackages, true, true); // If the user elected to save dirty packages, but one or more of the packages failed to save properly OR if the user // canceled out of the prompt, don't follow through on the check-in process const bool bShouldProceed = (UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Success || UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Declined); if (!bShouldProceed) { // If a failure occurred, alert the user that the check-in was aborted. This warning shouldn't be necessary if the user cancelled // from the dialog, because they obviously intended to cancel the whole operation. if (UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Failure) { OutResultInfo.Description = NSLOCTEXT("UnrealEd", "SCC_Checkin_Aborted", "Check-in aborted as a result of save failure."); FMessageDialog::Open(EAppMsgType::Ok, OutResultInfo.Description); } return; } TArray PendingDeletePaths; PendingDeletePaths.Add(FPaths::ConvertRelativePathToFull(FPaths::EngineContentDir())); PendingDeletePaths.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir())); PendingDeletePaths.Add(FPaths::ConvertRelativePathToFull(FPaths::ProjectConfigDir())); PendingDeletePaths.Add(FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath())); const bool bUseSourceControlStateCache = true; PromptForCheckin(OutResultInfo, PackageNames, PendingDeletePaths, ConfigFiles, bUseSourceControlStateCache); } void FSourceControlWindows::ChoosePackagesToCheckInCancelled(FSourceControlOperationRef InOperation) { ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); SourceControlProvider.CancelOperation(InOperation); if (ChoosePackagesToCheckInNotification.IsValid()) { ChoosePackagesToCheckInNotification.Pin()->ExpireAndFadeout(); } ChoosePackagesToCheckInNotification.Reset(); } void FSourceControlWindows::ChoosePackagesToCheckInCallback(const FSourceControlOperationRef& InOperation, ECommandResult::Type InResult, FSourceControlWindowsOnCheckInComplete OnCompleteDelegate) { if (ChoosePackagesToCheckInNotification.IsValid()) { ChoosePackagesToCheckInNotification.Pin()->ExpireAndFadeout(); } ChoosePackagesToCheckInNotification.Reset(); FCheckinResultInfo ResultInfo; if (InResult != ECommandResult::Succeeded) { switch (InResult) { case ECommandResult::Cancelled: ResultInfo.Result = ECommandResult::Cancelled; ResultInfo.Description = LOCTEXT("CheckInCancelled", "Check in cancelled."); break; case ECommandResult::Failed: { ResultInfo.Description = LOCTEXT("CheckInOperationFailed", "Failed checking source control status!"); FMessageLog EditorErrors("EditorErrors"); EditorErrors.Warning(ResultInfo.Description); EditorErrors.Notify(); } } OnCompleteDelegate.ExecuteIfBound(ResultInfo); return; } // Get a list of all the checked out packages TArray PackageNames; TArray LoadedPackages; TMap PackageStates; FEditorFileUtils::FindAllSubmittablePackageFiles(PackageStates, true); for (TMap::TConstIterator PackageIter(PackageStates); PackageIter; ++PackageIter) { const FString PackageName = *PackageIter.Key(); const FSourceControlStatePtr CurPackageSCCState = PackageIter.Value(); UPackage* Package = FindPackage(nullptr, *PackageName); if (Package != nullptr) { LoadedPackages.Add(Package); } PackageNames.Add(PackageName); } // Get a list of all the checked out config files TMap ConfigFileStates; TArray ConfigFilesToSubmit; FEditorFileUtils::FindAllSubmittableConfigFiles(ConfigFileStates); for (TMap::TConstIterator It(ConfigFileStates); It; ++It) { ConfigFilesToSubmit.Add(It.Key()); } ChoosePackagesToCheckInCompleted(LoadedPackages, PackageNames, ConfigFilesToSubmit, ResultInfo); OnCompleteDelegate.ExecuteIfBound(ResultInfo); } #undef LOCTEXT_NAMESPACE #endif // SOURCE_CONTROL_WITH_SLATE