Skip to content

Commit

Permalink
Implement unlock-on-push, if changes had been committed without pushi…
Browse files Browse the repository at this point in the history
…ng before
  • Loading branch information
sinbad committed May 21, 2020
1 parent ad54cad commit 0aadc17
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 13 deletions.
74 changes: 72 additions & 2 deletions Source/GitSourceControl/Private/GitSourceControlOperations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,56 @@ FName FGitPushWorker::GetName() const

bool FGitPushWorker::Execute(FGitSourceControlCommand& InCommand)
{

// If we have any locked files, check if we should unlock them
TArray<FString> FilesToUnlock;
if (InCommand.bUsingGitLfsLocking)
{
TMap<FString, FString> Locks;
// Get locks as relative paths
GitSourceControlUtils::GetAllLocks(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, false, InCommand.ErrorMessages, Locks);
if(Locks.Num() > 0)
{
// test to see what lfs files we would push, and compare to locked files, unlock after if push OK
FString BranchName;
GitSourceControlUtils::GetBranchName(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, BranchName);

TArray<FString> LfsPushParameters;
LfsPushParameters.Add(TEXT("push"));
LfsPushParameters.Add(TEXT("--dry-run"));
LfsPushParameters.Add(TEXT("origin"));
LfsPushParameters.Add(BranchName);
TArray<FString> LfsPushInfoMessages;
TArray<FString> LfsPushErrMessages;
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("lfs"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, LfsPushParameters, TArray<FString>(), LfsPushInfoMessages, LfsPushErrMessages);

if(InCommand.bCommandSuccessful)
{
// Result format is of the form
// push f4ee401c063058a78842bb3ed98088e983c32aa447f346db54fa76f844a7e85e => Path/To/Asset.uasset
// With some potential informationals we can ignore
for (auto& Line : LfsPushInfoMessages)
{
if (Line.StartsWith(TEXT("push")))
{
FString Prefix, Filename;
if (Line.Split(TEXT("=>"), &Prefix, &Filename))
{
Filename = Filename.TrimStartAndEnd();
if (Locks.Contains(Filename))
{
// We do not need to check user or if the file has local modifications before attempting unlocking, git-lfs will reject the unlock if so
// No point duplicating effort here
FilesToUnlock.Add(Filename);
UE_LOG(LogSourceControl, Log, TEXT("Post-push will try to unlock: %s"), *Filename);
}
}
}
}
}
}

}
// push the branch to its default remote
// (works only if the default remote "origin" is set and does not require authentication)
TArray<FString> Parameters;
Expand All @@ -480,14 +530,34 @@ bool FGitPushWorker::Execute(FGitSourceControlCommand& InCommand)
Parameters.Add(TEXT("HEAD"));
InCommand.bCommandSuccessful = GitSourceControlUtils::RunCommand(TEXT("push"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, Parameters, TArray<FString>(), InCommand.InfoMessages, InCommand.ErrorMessages);

// NOTE: no need to update status of our files
if(InCommand.bCommandSuccessful && InCommand.bUsingGitLfsLocking && FilesToUnlock.Num() > 0)
{
// unlock files: execute the LFS command on relative filenames
for(const auto& FileToUnlock : FilesToUnlock)
{
TArray<FString> OneFile;
OneFile.Add(FileToUnlock);
bool bUnlocked = GitSourceControlUtils::RunCommand(TEXT("lfs unlock"), InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, TArray<FString>(), OneFile, InCommand.InfoMessages, InCommand.ErrorMessages);
if (!bUnlocked)
{
// Report but don't fail, it's not essential
UE_LOG(LogSourceControl, Log, TEXT("Unlock failed for %s"), *FileToUnlock);
}
}

// We need to update status if we unlock
// This command needs absolute filenames
TArray<FString> AbsFilesToUnlock = GitSourceControlUtils::AbsoluteFilenames(FilesToUnlock, InCommand.PathToRepositoryRoot);
GitSourceControlUtils::RunUpdateStatus(InCommand.PathToGitBinary, InCommand.PathToRepositoryRoot, InCommand.bUsingGitLfsLocking, AbsFilesToUnlock, InCommand.ErrorMessages, States);

}

return InCommand.bCommandSuccessful;
}

bool FGitPushWorker::UpdateStates() const
{
return false;
return GitSourceControlUtils::UpdateCachedStates(States);
}

FName FGitUpdateStatusWorker::GetName() const
Expand Down
4 changes: 4 additions & 0 deletions Source/GitSourceControl/Private/GitSourceControlOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ class FGitPushWorker : public IGitSourceControlWorker
virtual FName GetName() const override;
virtual bool Execute(class FGitSourceControlCommand& InCommand) override;
virtual bool UpdateStates() const override;

public:
/** Temporary states for results */
TArray<FGitSourceControlState> States;
};

/** Get source control status of files on local working copy. */
Expand Down
45 changes: 34 additions & 11 deletions Source/GitSourceControl/Private/GitSourceControlUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -646,15 +646,18 @@ Content\ThirdPersonBP\Blueprints\ThirdPersonGameMode.uasset SRombauts
class FGitLfsLocksParser
{
public:
FGitLfsLocksParser(const FString& InRepositoryRoot, const FString& InStatus)
FGitLfsLocksParser(const FString& InRepositoryRoot, const FString& InStatus, const bool bAbsolutePaths = true)
{
TArray<FString> Informations;
InStatus.ParseIntoArray(Informations, TEXT("\t"), true);
if(Informations.Num() >= 3)
{
Informations[0].TrimEndInline(); // Trim whitespace from the end of the filename
Informations[1].TrimEndInline(); // Trim whitespace from the end of the username
LocalFilename = FPaths::ConvertRelativePathToFull(InRepositoryRoot, Informations[0]);
if (bAbsolutePaths)
LocalFilename = FPaths::ConvertRelativePathToFull(InRepositoryRoot, Informations[0]);
else
LocalFilename = Informations[0];
LockUser = MoveTemp(Informations[1]);
}
}
Expand Down Expand Up @@ -995,6 +998,22 @@ static void ParseStatusResults(const FString& InPathToGitBinary, const FString&
}
}

bool GetAllLocks(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool bAbsolutePaths, TArray<FString>& OutErrorMessages, TMap<FString, FString>& OutLocks)
{
TArray<FString> Results;
TArray<FString> ErrorMessages;
const bool bResult = RunCommand(TEXT("lfs locks"), InPathToGitBinary, InRepositoryRoot, TArray<FString>(), TArray<FString>(), Results, ErrorMessages);
for(const FString& Result : Results)
{
FGitLfsLocksParser LockFile(InRepositoryRoot, Result, bAbsolutePaths);
// TODO LFS Debug log
UE_LOG(LogSourceControl, Log, TEXT("LockedFile(%s, %s)"), *LockFile.LocalFilename, *LockFile.LockUser);
OutLocks.Add(MoveTemp(LockFile.LocalFilename), MoveTemp(LockFile.LockUser));
}

return bResult;
}

// Run a batch of Git "status" command to update status of given files and/or directories.
bool RunUpdateStatus(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool InUsingLfsLocking, const TArray<FString>& InFiles, TArray<FString>& OutErrorMessages, TArray<FGitSourceControlState>& OutStates)
{
Expand All @@ -1004,16 +1023,8 @@ bool RunUpdateStatus(const FString& InPathToGitBinary, const FString& InReposito
// 0) Issue a "git lfs locks" command at the root of the repository
if(InUsingLfsLocking)
{
TArray<FString> Results;
TArray<FString> ErrorMessages;
bool bResult = RunCommand(TEXT("lfs locks"), InPathToGitBinary, InRepositoryRoot, TArray<FString>(), TArray<FString>(), Results, ErrorMessages);
for(const FString& Result : Results)
{
FGitLfsLocksParser LockFile(InRepositoryRoot, Result);
// TODO LFS Debug log
UE_LOG(LogSourceControl, Log, TEXT("LockedFile(%s, %s)"), *LockFile.LocalFilename, *LockFile.LockUser);
LockedFiles.Add(MoveTemp(LockFile.LocalFilename), MoveTemp(LockFile.LockUser));
}
GetAllLocks(InPathToGitBinary, InRepositoryRoot, true, ErrorMessages, LockedFiles);
}

// Git status does not show any "untracked files" when called with files from different subdirectories! (issue #3)
Expand Down Expand Up @@ -1444,6 +1455,18 @@ TArray<FString> RelativeFilenames(const TArray<FString>& InFileNames, const FStr
return RelativeFiles;
}

TArray<FString> AbsoluteFilenames(const TArray<FString>& InFileNames, const FString& InRelativeTo)
{
TArray<FString> AbsFiles;

for(FString FileName : InFileNames) // string copy to be able to convert it inplace
{
AbsFiles.Add(FPaths::Combine(InRelativeTo, FileName));
}

return AbsFiles;
}

bool UpdateCachedStates(const TArray<FGitSourceControlState>& InStates)
{
FGitSourceControlModule& GitSourceControl = FModuleManager::GetModuleChecked<FGitSourceControlModule>( "GitSourceControl" );
Expand Down
20 changes: 20 additions & 0 deletions Source/GitSourceControl/Private/GitSourceControlUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ bool RunGetHistory(const FString& InPathToGitBinary, const FString& InRepository
*/
TArray<FString> RelativeFilenames(const TArray<FString>& InFileNames, const FString& InRelativeTo);

/**
* Helper function to convert a filename array to absolute paths.
* @param InFileNames The filename array (relative paths)
* @param InRelativeTo Path to the WorkspaceRoot
* @return an array of filenames, transformed into absolute paths
*/
TArray<FString> AbsoluteFilenames(const TArray<FString>& InFileNames, const FString& InRelativeTo);

/**
* Helper function for various commands to update cached states.
* @returns true if any states were updated
Expand All @@ -197,4 +205,16 @@ bool UpdateCachedStates(const TArray<FGitSourceControlState>& InStates);
*/
void RemoveRedundantErrors(FGitSourceControlCommand& InCommand, const FString& InFilter);

/**
* Run 'git lfs locks" to extract all lock information for all files in the repository
*
* @param InPathToGitBinary The path to the Git binary
* @param InRepositoryRoot The Git repository from where to run the command - usually the Game directory
* @param bAbsolutePaths Whether to report absolute filenames, false for repo-relative
* @param OutErrorMessages Any errors (from StdErr) as an array per-line
* @param OutLocks The lock results (file, username)
* @returns true if the command succeeded and returned no errors
*/
bool GetAllLocks(const FString& InPathToGitBinary, const FString& InRepositoryRoot, const bool bAbsolutePaths, TArray<FString>& OutErrorMessages, TMap<FString, FString>& OutLocks);

}

0 comments on commit 0aadc17

Please sign in to comment.