Skip to content
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

Fix history of a renamed asset #103

Merged
merged 5 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
236 changes: 140 additions & 96 deletions Source/PlasticSourceControl/Private/PlasticSourceControlParsers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -550,20 +550,30 @@ FPlasticMergeConflictParser::FPlasticMergeConflictParser(const FString& InResult
}
}

// Types of changes in source control revisions, using Perforce terminology for the History window
static const TCHAR* SourceControlActionAdded = TEXT("add");
static const TCHAR* SourceControlActionDeleted = TEXT("delete");
static const TCHAR* SourceControlActionMoved = TEXT("branch");
static const TCHAR* SourceControlActionMerged = TEXT("integrate");
static const TCHAR* SourceControlActionChanged = TEXT("edit");

// Convert a file state to a string ala Perforce, see also ParseShelveFileStatus()
FString FileStateToAction(const EWorkspaceState InState)
{
switch (InState)
{
case EWorkspaceState::Added:
return TEXT("add");
return SourceControlActionAdded;
case EWorkspaceState::Deleted:
return TEXT("delete");
return SourceControlActionDeleted;
case EWorkspaceState::Moved:
return TEXT("branch");
case EWorkspaceState::Copied:
return SourceControlActionMoved;
case EWorkspaceState::Replaced:
return SourceControlActionMerged;
case EWorkspaceState::CheckedOutChanged:
default:
return TEXT("edit");
return SourceControlActionChanged;
}
}

Expand Down Expand Up @@ -598,18 +608,37 @@ static FString DecodeXmlEntities(const FString& InString)
<CreationDate>2019-10-14T09:52:07+02:00</CreationDate>
<RevisionType>bin</RevisionType>
<ChangesetNumber>7</ChangesetNumber>
<Owner>SRombauts</Owner>
<Owner>sebastien.rombauts</Owner>
<Comment>New tests</Comment>
<Repository>UE4PlasticPluginDev</Repository>
<Server>localhost:8087</Server>
<RepositorySpec>UE4PlasticPluginDev@localhost:8087</RepositorySpec>
<DataStatus>Available</DataStatus>
<ItemId>1657</ItemId>
<Size>22356</Size>
<Hash>zzuB6G9fbWz1md12+tvBxg==</Hash>
</Revision>
...
<Revision>
<RevisionSpec>C:/Workspace/UE4PlasticPluginDev/Content/FirstPersonBP/Blueprints/BP_TestsRenamed.uasset#cs:12</RevisionSpec>
<Branch>Removed /Content/FirstPersonBP/Blueprints/BP_TestsRenamed.uasset</Branch>
<Branch>/main/rename_test</Branch>
<CreationDate>2022-04-28T16:00:37+02:00</CreationDate>
<RevisionType>bin</RevisionType>
<ChangesetNumber>12</ChangesetNumber>
<Owner>sebastien.rombauts</Owner>
<Comment>Renamed sphere blueprint</Comment>
<Repository>UE4PlasticPluginDev</Repository>
<Server>localhost:8087</Server>
<RepositorySpec>UE4PlasticPluginDev@localhost:8087</RepositorySpec>
<DataStatus>Available</DataStatus>
<ItemPathOrSpec>C:/Workspace/UE4PlasticPluginDev/Content/FirstPersonBP/Blueprints/BP_TestsRenamed.uasset</ItemPathOrSpec>
<ItemId>1657</ItemId>
<Size>28603</Size>
<Hash>MREVIZ1qKNqu1h2iq9WiRg==</Hash>
</Revision>
<Revision>
<RevisionSpec>C:/Workspace/UE4PlasticPluginDev/Content/FirstPersonBP/Blueprints/BP_TestsRenamed.uasset#cs:12</RevisionSpec>
<Branch>Moved from /Content/FirstPersonBP/Blueprints/BP_ToRename.uasset to /Content/FirstPersonBP/Blueprints/BP_TestsRenamed.uasset</Branch>
<CreationDate>2022-04-28T16:00:37+02:00</CreationDate>
<RevisionType />
<ChangesetNumber>12</ChangesetNumber>
Expand All @@ -618,10 +647,13 @@ static FString DecodeXmlEntities(const FString& InString)
<Repository>UE4PlasticPluginDev</Repository>
<Server>localhost:8087</Server>
<RepositorySpec>UE4PlasticPluginDev@localhost:8087</RepositorySpec>
<Size>22406</Size>
<Hash>uR7NdDRAyKqADdyAqh67Rg==</Hash>
<DataStatus />
<ItemPathOrSpec />
<ItemId />
<Size>0</Size>
<Hash />
</Revision>

...
</Revisions>
</RevisionHistory>
<RevisionHistory>
Expand Down Expand Up @@ -700,127 +732,139 @@ static bool ParseHistoryResults(const bool bInUpdateHistory, const FXmlFile& InX
// Note: limit to last 100 changes, like Perforce
static const int32 MaxRevisions = 100;
const int32 MinIndex = FMath::Max(0, RevisionNodes.Num() - MaxRevisions);
for (int32 Index = RevisionNodes.Num() - 1; Index >= MinIndex; Index--)
bool bNextEntryIsAMove = false;
for (int32 RevisionIndex = RevisionNodes.Num() - 1; RevisionIndex >= MinIndex; RevisionIndex--)
{
if (const FXmlNode* RevisionNode = RevisionNodes[Index])
{
const TSharedRef<FPlasticSourceControlRevision, ESPMode::ThreadSafe> SourceControlRevision = MakeShareable(new FPlasticSourceControlRevision);
SourceControlRevision->State = &InOutState;
SourceControlRevision->Filename = Filename;
const FXmlNode* RevisionNode = RevisionNodes[RevisionIndex];
check(RevisionNode);

const TSharedRef<FPlasticSourceControlRevision, ESPMode::ThreadSafe> SourceControlRevision = MakeShareable(new FPlasticSourceControlRevision);
SourceControlRevision->State = &InOutState;
SourceControlRevision->Filename = Filename;

if (const FXmlNode* RevisionTypeNode = RevisionNode->FindChildNode(RevisionType))
if (const FXmlNode* RevisionTypeNode = RevisionNode->FindChildNode(RevisionType))
{
// There are two entries for a Move of an asset;
// 1. a regular one with the normal data: revision, comment, branch, Id, size, hash etc.
// 2. and another "empty" one for the Move
// => Since the parsing is done in reverse order, the detection of a Move need to apply to the next entry
if (RevisionTypeNode->GetContent().IsEmpty())
{
if (!RevisionTypeNode->GetContent().IsEmpty())
{
if (Index == 0)
{
SourceControlRevision->Action = FileStateToAction(EWorkspaceState::Added);
}
else
{
SourceControlRevision->Action = FileStateToAction(EWorkspaceState::CheckedOutChanged);
}
}
else
{
SourceControlRevision->Action = FileStateToAction(EWorkspaceState::Deleted);
}
// Empty RevisionType signals a Move: Raises a flag to treat the next entry as a Move, and skip this one as it is empty (it's just an additional entry with data for the move)
bNextEntryIsAMove = true;
continue;
}

if (const FXmlNode* ChangesetNumberNode = RevisionNode->FindChildNode(ChangesetNumber))
else
{
const FString& Changeset = ChangesetNumberNode->GetContent();
SourceControlRevision->ChangesetNumber = FCString::Atoi(*Changeset); // Value now used in the Revision column and in the Asset Menu History

// Also append depot name to the revision, but only when it is different from the default one (ie for xlinks sub repository)
if (!InOutState.RepSpec.IsEmpty() && (InOutState.RepSpec != RootRepSpec))
if (bNextEntryIsAMove)
{
bNextEntryIsAMove = false;
SourceControlRevision->Action = SourceControlActionMoved;
}
else if (RevisionIndex == 0)
{
TArray<FString> RepSpecs;
InOutState.RepSpec.ParseIntoArray(RepSpecs, TEXT("@"));
SourceControlRevision->Revision = FString::Printf(TEXT("cs:%s@%s"), *Changeset, *RepSpecs[0]);
SourceControlRevision->Action = SourceControlActionAdded;
}
else
{
SourceControlRevision->Revision = FString::Printf(TEXT("cs:%s"), *Changeset);
SourceControlRevision->Action = SourceControlActionChanged;
}
}
if (const FXmlNode* CommentNode = RevisionNode->FindChildNode(Comment))
{
SourceControlRevision->Description = DecodeXmlEntities(CommentNode->GetContent());
}
if (const FXmlNode* OwnerNode = RevisionNode->FindChildNode(Owner))
{
SourceControlRevision->UserName = PlasticSourceControlUtils::UserNameToDisplayName(OwnerNode->GetContent());
}
if (const FXmlNode* DateNode = RevisionNode->FindChildNode(CreationDate))
{
const FString& DateIso = DateNode->GetContent();
FDateTime::ParseIso8601(*DateIso, SourceControlRevision->Date);
}
if (const FXmlNode* BranchNode = RevisionNode->FindChildNode(Branch))
}

if (const FXmlNode* ChangesetNumberNode = RevisionNode->FindChildNode(ChangesetNumber))
{
const FString& Changeset = ChangesetNumberNode->GetContent();
SourceControlRevision->ChangesetNumber = FCString::Atoi(*Changeset); // Value now used in the Revision column and in the Asset Menu History

// Also append depot name to the revision, but only when it is different from the default one (ie for xlinks sub repository)
if (!InOutState.RepSpec.IsEmpty() && (InOutState.RepSpec != RootRepSpec))
{
SourceControlRevision->Branch = DecodeXmlEntities(BranchNode->GetContent());
TArray<FString> RepSpecs;
InOutState.RepSpec.ParseIntoArray(RepSpecs, TEXT("@"));
SourceControlRevision->Revision = FString::Printf(TEXT("cs:%s@%s"), *Changeset, *RepSpecs[0]);
}
if (const FXmlNode* SizeNode = RevisionNode->FindChildNode(Size))
else
{
SourceControlRevision->FileSize = FCString::Atoi(*SizeNode->GetContent());
SourceControlRevision->Revision = FString::Printf(TEXT("cs:%s"), *Changeset);
}
}
if (const FXmlNode* CommentNode = RevisionNode->FindChildNode(Comment))
{
SourceControlRevision->Description = DecodeXmlEntities(CommentNode->GetContent());
}
if (const FXmlNode* OwnerNode = RevisionNode->FindChildNode(Owner))
{
SourceControlRevision->UserName = PlasticSourceControlUtils::UserNameToDisplayName(OwnerNode->GetContent());
}
if (const FXmlNode* DateNode = RevisionNode->FindChildNode(CreationDate))
{
const FString& DateIso = DateNode->GetContent();
FDateTime::ParseIso8601(*DateIso, SourceControlRevision->Date);
}
if (const FXmlNode* BranchNode = RevisionNode->FindChildNode(Branch))
{
SourceControlRevision->Branch = DecodeXmlEntities(BranchNode->GetContent());
}
if (const FXmlNode* SizeNode = RevisionNode->FindChildNode(Size))
{
SourceControlRevision->FileSize = FCString::Atoi(*SizeNode->GetContent());
}

// A negative RevisionHeadChangeset provided by fileinfo mean that the file has been unshelved;
// replace it by the changeset number of the first revision in the history (the more recent)
// Note: workaround to be able to show the history / the diff of a file that has been unshelved
// (but keeps the LocalRevisionChangeset to the negative changeset corresponding to the Shelve Id)
if (InOutState.DepotRevisionChangeset < 0)
{
InOutState.DepotRevisionChangeset = SourceControlRevision->ChangesetNumber;
}
// A negative RevisionHeadChangeset provided by fileinfo mean that the file has been unshelved;
// replace it by the changeset number of the first revision in the history (the more recent)
// Note: workaround to be able to show the history / the diff of a file that has been unshelved
// (but keeps the LocalRevisionChangeset to the negative changeset corresponding to the Shelve Id)
if (InOutState.DepotRevisionChangeset < 0)
{
InOutState.DepotRevisionChangeset = SourceControlRevision->ChangesetNumber;
}

// Detect and skip more recent changesets on other branches (ie above the RevisionHeadChangeset)
// since we usually don't want to display changes from other branches in the History window...
// except in case of a merge conflict, where the Editor expects the tip of the "source (remote)" branch to be at the top of the history!
if ( (SourceControlRevision->ChangesetNumber > InOutState.DepotRevisionChangeset)
// Detect and skip more recent changesets on other branches (ie above the RevisionHeadChangeset)
// since we usually don't want to display changes from other branches in the History window...
// except in case of a merge conflict, where the Editor expects the tip of the "source (remote)" branch to be at the top of the history!
if ( (SourceControlRevision->ChangesetNumber > InOutState.DepotRevisionChangeset)
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3
&& (SourceControlRevision->GetRevision() != InOutState.PendingResolveInfo.RemoteRevision))
&& (SourceControlRevision->GetRevision() != InOutState.PendingResolveInfo.RemoteRevision))
#else
&& (SourceControlRevision->ChangesetNumber != InOutState.PendingMergeSourceChangeset))
&& (SourceControlRevision->ChangesetNumber != InOutState.PendingMergeSourceChangeset))
#endif
{
InOutState.HeadBranch = SourceControlRevision->Branch;
InOutState.HeadAction = SourceControlRevision->Action;
InOutState.HeadChangeList = SourceControlRevision->ChangesetNumber;
InOutState.HeadUserName = SourceControlRevision->UserName;
InOutState.HeadModTime = SourceControlRevision->Date.ToUnixTimestamp();
}
else if (bInUpdateHistory)
{
InOutState.History.Add(SourceControlRevision);
}
{
InOutState.HeadBranch = SourceControlRevision->Branch;
InOutState.HeadAction = SourceControlRevision->Action;
InOutState.HeadChangeList = SourceControlRevision->ChangesetNumber;
InOutState.HeadUserName = SourceControlRevision->UserName;
InOutState.HeadModTime = SourceControlRevision->Date.ToUnixTimestamp();
}
else if (bInUpdateHistory)
{
InOutState.History.Add(SourceControlRevision);
}

// Also grab the UserName of the author of the current depot/head changeset
if ((SourceControlRevision->ChangesetNumber == InOutState.DepotRevisionChangeset) && InOutState.HeadUserName.IsEmpty())
{
InOutState.HeadUserName = SourceControlRevision->UserName;
}
// Also grab the UserName of the author of the current depot/head changeset
if ((SourceControlRevision->ChangesetNumber == InOutState.DepotRevisionChangeset) && InOutState.HeadUserName.IsEmpty())
{
InOutState.HeadUserName = SourceControlRevision->UserName;
}

if (!bInUpdateHistory)
{
break; // if not updating the history, just getting the head of the latest branch is enough
}
if (!bInUpdateHistory)
{
break; // if not updating the history, just getting the head of the latest branch is enough
}
}
}

return true;
}

bool ParseHistoryResults(const bool bInUpdateHistory, const FString& InResults, TArray<FPlasticSourceControlState>& InOutStates)
bool ParseHistoryResults(const bool bInUpdateHistory, const FString& InResultFilename, TArray<FPlasticSourceControlState>& InOutStates)
{
bool bResult = false;

FXmlFile XmlFile;
{
TRACE_CPUPROFILER_EVENT_SCOPE(PlasticSourceControlParsers::ParseHistoryResults::FXmlFile::LoadFile);
bResult = XmlFile.LoadFile(InResults, EConstructMethod::ConstructFromBuffer);
bResult = XmlFile.LoadFile(InResultFilename);
}
if (bResult)
{
Expand Down Expand Up @@ -1313,7 +1357,7 @@ bool ParseShelveDiffResults(const FString InWorkspaceRoot, TArray<FString>&& InR
return State.GetFilename().Equals(AbsoluteFilename);
}))
{
ExistingShelveRevision->Action = FileStateToAction(EWorkspaceState::Moved);
ExistingShelveRevision->Action = SourceControlActionMoved;
continue;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ void ParseDirectoryStatusResult(const FString& InDir, const TArray<FString>& InR

void ParseFileinfoResults(const TArray<FString>& InResults, TArray<FPlasticSourceControlState>& InOutStates);

bool ParseHistoryResults(const bool bInUpdateHistory, const FString& InResults, TArray<FPlasticSourceControlState>& InOutStates);
bool ParseHistoryResults(const bool bInUpdateHistory, const FString& InResultFilename, TArray<FPlasticSourceControlState>& InOutStates);

bool ParseUpdateResults(const FString& InResults, TArray<FString>& OutFiles);
bool ParseUpdateResults(const TArray<FString>& InResults, TArray<FString>& OutFiles);
Expand Down
22 changes: 3 additions & 19 deletions Source/PlasticSourceControl/Private/PlasticSourceControlUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -671,23 +671,6 @@ bool RunGetFile(const FString& InRevSpec, const FString& InDumpFileName)
return bResult;
}

// Convert a file state to a string ala Perforce, see also ParseShelveFileStatus()
FString FileStateToAction(const EWorkspaceState InState)
{
switch (InState)
{
case EWorkspaceState::Added:
return TEXT("add");
case EWorkspaceState::Deleted:
return TEXT("delete");
case EWorkspaceState::Moved:
return TEXT("branch");
case EWorkspaceState::CheckedOutChanged:
default:
return TEXT("edit");
}
}

// Run a Plastic "history" command and parse it's XML result.
bool RunGetHistory(const bool bInUpdateHistory, TArray<FPlasticSourceControlState>& InOutStates, TArray<FString>& OutErrorMessages)
{
Expand All @@ -702,7 +685,8 @@ bool RunGetHistory(const bool bInUpdateHistory, TArray<FPlasticSourceControlStat
{
Parameters.Add(TEXT("--moveddeleted"));
}
Parameters.Add(TEXT("--xml"));
const FScopedTempFile HistoryResultFile;
Parameters.Add(FString::Printf(TEXT("--xml=\"%s\""), *HistoryResultFile.GetFilename()));
Parameters.Add(TEXT("--encoding=\"utf-8\""));
const FPlasticSourceControlProvider& Provider = FPlasticSourceControlModule::Get().GetProvider();
if (Provider.GetPlasticScmVersion() >= PlasticSourceControlVersions::NewHistoryLimit)
Expand Down Expand Up @@ -737,7 +721,7 @@ bool RunGetHistory(const bool bInUpdateHistory, TArray<FPlasticSourceControlStat
bResult = RunCommand(TEXT("history"), Parameters, Files, Results, Errors);
if (bResult)
{
bResult = PlasticSourceControlParsers::ParseHistoryResults(bInUpdateHistory, Results, InOutStates);
bResult = PlasticSourceControlParsers::ParseHistoryResults(bInUpdateHistory, HistoryResultFile.GetFilename(), InOutStates);
}
if (!Errors.IsEmpty())
{
Expand Down