diff --git a/Source/GitSourceControl/Private/GitSourceControlOperations.cpp b/Source/GitSourceControl/Private/GitSourceControlOperations.cpp index ecf3024a..1bd6d4b6 100644 --- a/Source/GitSourceControl/Private/GitSourceControlOperations.cpp +++ b/Source/GitSourceControl/Private/GitSourceControlOperations.cpp @@ -15,6 +15,7 @@ #include "SourceControlHelpers.h" #include "Logging/MessageLog.h" #include "Misc/MessageDialog.h" +#include "HAL/FileManager.h" #include "HAL/PlatformProcess.h" #include "GenericPlatform/GenericPlatformFile.h" #if ENGINE_MAJOR_VERSION >= 5 @@ -470,41 +471,41 @@ bool FGitDeleteWorker::UpdateStates() const } -// Get lists of Missing files (ie "deleted"), Modified files, and "other than Added" Existing files -void GetMissingVsExistingFiles(const TArray& InFiles, TArray& OutMissingFiles, TArray& OutAllExistingFiles, TArray& OutOtherThanAddedExistingFiles) +void GroupFileCommandsForRevert(const TArray& InFiles, TArray& FilesToRemove, TArray& FilesToCheckout, TArray& FilesToReset, TArray& FilesToDelete) { FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get(); FGitSourceControlProvider& Provider = GitSourceControl.GetProvider(); - const TArray Files = (InFiles.Num() > 0) ? (InFiles) : (Provider.GetFilesInCache()); - TArray> LocalStates; - Provider.GetState(Files, LocalStates, EStateCacheUsage::Use); + Provider.GetState(InFiles, LocalStates, EStateCacheUsage::Use); for (const auto& State : LocalStates) { - if (FPaths::FileExists(State->GetFilename())) + if (State->IsAdded()) { - if (State->IsAdded()) - { - OutAllExistingFiles.Add(State->GetFilename()); - } - else if (State->IsModified()) + if (FPaths::FileExists(State->GetFilename())) { - OutOtherThanAddedExistingFiles.Add(State->GetFilename()); - OutAllExistingFiles.Add(State->GetFilename()); + // Git rm won't delete the file because the engine still has it in use, and reset won't work on a file which doesn't exist on disk + // so we have to delete it ourselves, and then remove it from the index. + if (USourceControlPreferences::ShouldDeleteNewFilesOnRevert()) + { + FilesToDelete.Add(State->GetFilename()); + FilesToRemove.Add(State->GetFilename()); + } + else + { + FilesToReset.Add(State->GetFilename()); + } } - else if (State->CanRevert()) // for locked but unmodified files + else { - OutOtherThanAddedExistingFiles.Add(State->GetFilename()); + // When you delete a file through content browser, UE will send a revert command to allow us to clean up the stage state. + FilesToRemove.Add(State->GetFilename()); } } - else + // Checkout to head will reset the file back to what it is in your current commit, and reset the index. + else if (State->CanRevert()) { - // If already queued for deletion, don't try to delete again - if (State->IsSourceControlled() && !State->IsDeleted()) - { - OutMissingFiles.Add(State->GetFilename()); - } + FilesToCheckout.Add(State->GetFilename()); } } } @@ -519,63 +520,62 @@ bool FGitRevertWorker::Execute(FGitSourceControlCommand& InCommand) InCommand.bCommandSuccessful = true; // Filter files by status - TArray MissingFiles; - TArray AllExistingFiles; - TArray OtherThanAddedExistingFiles; - GetMissingVsExistingFiles(InCommand.Files, MissingFiles, AllExistingFiles, OtherThanAddedExistingFiles); - - const bool bRevertAll = InCommand.Files.Num() < 1; + const bool bRevertAll = InCommand.Files.Num() == 0; if (bRevertAll) { - TArray Parms; - Parms.Add(TEXT("--hard")); - InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("reset"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parms, FGitSourceControlModule::GetEmptyStringArray(), InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); + TArray Params; + Params.Add(TEXT("--hard")); + InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("reset"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Params, FGitSourceControlModule::GetEmptyStringArray(), InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); - Parms.Reset(2); - Parms.Add(TEXT("-f")); // force - Parms.Add(TEXT("-d")); // remove directories - InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("clean"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parms, FGitSourceControlModule::GetEmptyStringArray(), InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); + Params.Reset(2); + Params.Add(TEXT("-f")); // force + Params.Add(TEXT("-d")); // remove directories + InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("clean"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Params, FGitSourceControlModule::GetEmptyStringArray(), InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); } else { - if (MissingFiles.Num() > 0) + TArray FilesToRemove; + TArray FilesToCheckout; + TArray FilesToReset; + TArray FilesToDelete; + GroupFileCommandsForRevert(InCommand.Files, FilesToRemove, FilesToCheckout, FilesToReset, FilesToDelete); + + // Verify we haven't missed performing an operation on any file passed on for revert + ensure(FilesToRemove.Num() + FilesToCheckout.Num() + FilesToReset.Num() == InCommand.Files.Num()); + + for (const FString& FileName : FilesToDelete) { - // "Added" files that have been deleted needs to be removed from revision control - InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("rm"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), MissingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); + bool RequireExists = true; + bool EvenReadOnly = true; + IFileManager::Get().Delete(*FileName, RequireExists, EvenReadOnly); } - if (AllExistingFiles.Num() > 0) + if (FilesToReset.Num() > 0) { - // reset and revert any changes already added to the index - InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("reset"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), AllExistingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); - InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("checkout"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), AllExistingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); + InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("reset"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), FilesToReset, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); } - if (OtherThanAddedExistingFiles.Num() > 0) + if (FilesToRemove.Num() > 0) { - // revert any changes in working copy (this would fails if the asset was in "Added" state, since after "reset" it is now "untracked") - // may need to try a few times due to file locks from prior operations - bool CheckoutSuccess = false; - int32 Attempts = 10; - while( Attempts-- > 0 ) - { - CheckoutSuccess = GitSourceControlUtils::RunCommand(TEXT("checkout"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), OtherThanAddedExistingFiles, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); - if (CheckoutSuccess) - { - break; - } - - FPlatformProcess::Sleep(0.1f); - } - - InCommand.bCommandSuccessful &= CheckoutSuccess; + // "Added" files that have been deleted needs to be removed from revision control + InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("rm"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, FGitSourceControlModule::GetEmptyStringArray(), FilesToRemove, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); + } + if (FilesToCheckout.Num() > 0) + { + // HEAD param allows us to re-pull files which have been deleted. + TArray Params; + Params.Add(TEXT("HEAD")); + // Checkout back to the last commit for any modified files. + InCommand.bCommandSuccessful &= GitSourceControlUtils::RunCommand(TEXT("checkout"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Params, FilesToCheckout, InCommand.ResultInfo.InfoMessages, InCommand.ResultInfo.ErrorMessages); } } + // This is all the files we "asked" to revert, in the case of InCommand.Files everything *should* be changed + // in the case where we didn't pass in any files, we ran a revert on the repository root, so we should refresh everything + const TArray& RequestedReverts = bRevertAll ? FGitSourceControlModule::Get().GetProvider().GetFilesInCache() : InCommand.Files; if (InCommand.bUsingGitLfsLocking) { // unlock files: execute the LFS command on relative filenames - // (unlock only locked files, that is, not Added files) TArray LockedFiles; - GitSourceControlUtils::GetLockedFiles(OtherThanAddedExistingFiles, LockedFiles); + GitSourceControlUtils::GetLockedFiles(RequestedReverts, LockedFiles); if (LockedFiles.Num() > 0) { const TArray& RelativeFiles = GitSourceControlUtils::RelativeFilenames(LockedFiles, InCommand.PathToGitRoot); @@ -591,19 +591,9 @@ bool FGitRevertWorker::Execute(FGitSourceControlCommand& InCommand) } } - // If no files were specified (full revert), refresh all relevant files instead of the specified files (which is an empty list in full revert) - // This is required so that files that were "Marked for add" have their status updated after a full revert. - TArray FilesToUpdate = InCommand.Files; - if (InCommand.Files.Num() <= 0) - { - for (const auto& File : MissingFiles) FilesToUpdate.Add(File); - for (const auto& File : AllExistingFiles) FilesToUpdate.Add(File); - for (const auto& File : OtherThanAddedExistingFiles) FilesToUpdate.Add(File); - } - // now update the status of our files TMap UpdatedStates; - bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, FilesToUpdate, InCommand.ResultInfo.ErrorMessages, UpdatedStates); + bool bSuccess = GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, RequestedReverts, InCommand.ResultInfo.ErrorMessages, UpdatedStates); if (bSuccess) { GitSourceControlUtils::CollectNewStates(UpdatedStates, States);