Skip to content

Better support for reverting new and deleted files #198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 61 additions & 71 deletions Source/GitSourceControl/Private/GitSourceControlOperations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<FString>& InFiles, TArray<FString>& OutMissingFiles, TArray<FString>& OutAllExistingFiles, TArray<FString>& OutOtherThanAddedExistingFiles)
void GroupFileCommandsForRevert(const TArray<FString>& InFiles, TArray<FString>& FilesToRemove, TArray<FString>& FilesToCheckout, TArray<FString>& FilesToReset, TArray<FString>& FilesToDelete)
{
FGitSourceControlModule& GitSourceControl = FGitSourceControlModule::Get();
FGitSourceControlProvider& Provider = GitSourceControl.GetProvider();

const TArray<FString> Files = (InFiles.Num() > 0) ? (InFiles) : (Provider.GetFilesInCache());

TArray<TSharedRef<ISourceControlState, ESPMode::ThreadSafe>> 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());
}
}
}
Expand All @@ -519,63 +520,62 @@ bool FGitRevertWorker::Execute(FGitSourceControlCommand& InCommand)
InCommand.bCommandSuccessful = true;

// Filter files by status
TArray<FString> MissingFiles;
TArray<FString> AllExistingFiles;
TArray<FString> OtherThanAddedExistingFiles;
GetMissingVsExistingFiles(InCommand.Files, MissingFiles, AllExistingFiles, OtherThanAddedExistingFiles);

const bool bRevertAll = InCommand.Files.Num() < 1;
const bool bRevertAll = InCommand.Files.Num() == 0;
if (bRevertAll)
{
TArray<FString> 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<FString> 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<FString> FilesToRemove;
TArray<FString> FilesToCheckout;
TArray<FString> FilesToReset;
TArray<FString> 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<FString> 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<FString>& 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<FString> LockedFiles;
GitSourceControlUtils::GetLockedFiles(OtherThanAddedExistingFiles, LockedFiles);
GitSourceControlUtils::GetLockedFiles(RequestedReverts, LockedFiles);
if (LockedFiles.Num() > 0)
{
const TArray<FString>& RelativeFiles = GitSourceControlUtils::RelativeFilenames(LockedFiles, InCommand.PathToGitRoot);
Expand All @@ -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<FString> 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<FString, FGitSourceControlState> 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);
Expand Down