1576 lines
68 KiB
C++
1576 lines
68 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
DiffPackagesCommandlet.cpp: Commandlet used for comparing two packages.
|
|
|
|
=============================================================================*/
|
|
|
|
#include "Commandlets/DiffPackagesCommandlet.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/Package.h"
|
|
#include "Serialization/ObjectWriter.h"
|
|
#include "Serialization/ObjectReader.h"
|
|
#include "Serialization/ArchiveReplaceObjectRef.h"
|
|
#include "UObject/LinkerLoad.h"
|
|
#include "Engine/Level.h"
|
|
#include "Engine/World.h"
|
|
#include "Commandlets/EditorCommandlets.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogDiffPackagesCommandlet, Log, All);
|
|
|
|
// the maximum number of packages that can be compared
|
|
#define MAX_PACKAGECOUNT 3
|
|
|
|
// whether to serialize object recursively when looking for changes (for debugging)
|
|
#define USE_DEEP_RECURSION 0
|
|
|
|
// whether to skip levels when building the initial diff sets (for debugging)
|
|
#define OPTIMIZE_LEVEL_DIFFS 1
|
|
|
|
UDEPRECATED_DiffPackagesCommandlet::UDEPRECATED_DiffPackagesCommandlet(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
LogToConsole = false;
|
|
}
|
|
|
|
static const TCHAR* GetDiffTypeText(EObjectDiff DiffType, const int32 NumPackages)
|
|
{
|
|
if (NumPackages == 2)
|
|
{
|
|
switch (DiffType)
|
|
{
|
|
case OD_None:
|
|
return TEXT("None");
|
|
|
|
case OD_AOnly:
|
|
return TEXT("A only");
|
|
|
|
case OD_BOnly:
|
|
return TEXT("B only");
|
|
|
|
case OD_ABSame:
|
|
return TEXT("Identical");
|
|
|
|
case OD_ABConflict:
|
|
return TEXT("Changed");
|
|
|
|
case OD_Invalid:
|
|
return TEXT("Invalid");
|
|
|
|
default:
|
|
return TEXT("Unknown");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (DiffType)
|
|
{
|
|
case OD_None:
|
|
return TEXT("None");
|
|
|
|
case OD_AOnly:
|
|
return TEXT("A Only");
|
|
|
|
case OD_BOnly:
|
|
return TEXT("B Only");
|
|
|
|
case OD_ABSame:
|
|
return TEXT("Both (resolved)");
|
|
|
|
case OD_ABConflict:
|
|
return TEXT("Both (conflict)");
|
|
|
|
case OD_Invalid:
|
|
return TEXT("Invalid");
|
|
|
|
default:
|
|
return TEXT("Unknown");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Contains the results for a comparison between two values of a single property.
|
|
*/
|
|
struct FPropertyComparison
|
|
{
|
|
/** Constructor */
|
|
FPropertyComparison()
|
|
: Prop(NULL), DiffType(OD_None)
|
|
{}
|
|
|
|
/** the property that was compared */
|
|
FProperty* Prop;
|
|
|
|
/**
|
|
* The comparison result type for this property comparison.
|
|
*/
|
|
EObjectDiff DiffType;
|
|
|
|
/** The name of the property that was compared; only used when comparing native property data (which will have no corresponding FProperty) */
|
|
FString PropText;
|
|
|
|
/**
|
|
* Contains the result of the comparison.
|
|
*/
|
|
FString DiffText;
|
|
};
|
|
|
|
/**
|
|
* Contains information about a comparison of the property values for two object graphs. One FObjectComparison is created for each
|
|
* top-level object in a package (i.e. each object that has the package's LinkerRoot as its Outer), which contains comparison data for
|
|
* the top-level object as well as its subobjects.
|
|
*/
|
|
struct FObjectComparison
|
|
{
|
|
/** Constructor */
|
|
FObjectComparison()
|
|
: OverallDiffType(OD_None)
|
|
{
|
|
FMemory::Memzero(ObjectSets, sizeof(ObjectSets));
|
|
}
|
|
|
|
/**
|
|
* The path name for the top-level object in this FObjectComparison, minus the package portion of the path name.
|
|
*/
|
|
FString RootObjectPath;
|
|
|
|
/**
|
|
* The graph of objects represented by this comparison from each package. The graphs contain the top-level object along with
|
|
* all of its subobjects.
|
|
*/
|
|
FObjectGraph* ObjectSets[MAX_PACKAGECOUNT];
|
|
|
|
/**
|
|
* The list of comparison results for all property values which not identical in all packages.
|
|
*/
|
|
TArray<FPropertyComparison> PropDiffs;
|
|
|
|
/**
|
|
* The cumulative comparison result type for the entire object graph comparison.
|
|
*/
|
|
EObjectDiff OverallDiffType;
|
|
};
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* Populates the PropertyData and PropertyText members if associated with a valid UObject
|
|
*/
|
|
FNativePropertyData::FNativePropertyData(UObject* InObject)
|
|
: Object(InObject)
|
|
{
|
|
SetObject(InObject);
|
|
}
|
|
|
|
namespace DiffPackagesCommandletImpl
|
|
{
|
|
void LoadNativePropertyData(UObject* Object, TArray<uint8>& out_NativePropertyData);
|
|
}
|
|
|
|
/**
|
|
* Changes the UObject associated with this native property data container and re-initializes the
|
|
* PropertyData and PropertyText members
|
|
*/
|
|
void FNativePropertyData::SetObject(UObject* NewObject)
|
|
{
|
|
Object = NewObject;
|
|
PropertyData.Empty();
|
|
PropertyText.Empty();
|
|
|
|
if (Object != NULL)
|
|
{
|
|
DiffPackagesCommandletImpl::LoadNativePropertyData(Object, PropertyData);
|
|
Object->GetNativePropertyValues(PropertyText, PPF_SimpleObjectText);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* Populates the Objects array with RootObject and its subobjects.
|
|
*
|
|
* @param RootObject the top-level object for this object graph
|
|
* @param PackageIndex the index [into the Packages array] for the package that this object graph belongs to
|
|
* @param ObjectsToIgnore optional list of objects to not include in this object graph, even if they are contained within RootObject
|
|
*/
|
|
FObjectGraph::FObjectGraph(UObject* RootObject, int32 PackageIndex, TArray<FObjectComparison>* pObjectsToIgnore /*=NULL*/)
|
|
{
|
|
new (Objects) FObjectReference(RootObject);
|
|
|
|
// start with just looking in the root object, but collect references on everything
|
|
// that is put in to objects, etc
|
|
for (int32 ObjIndex = 0; ObjIndex < Objects.Num(); ObjIndex++)
|
|
{
|
|
FObjectReference& ObjSet = Objects[ObjIndex];
|
|
|
|
// find all objects inside this object that are referenced by properties in the object
|
|
TArray<UObject*> Subobjects;
|
|
|
|
// if we want to ignore certain objects, pre-fill the Subobjects array with the list
|
|
if (pObjectsToIgnore != NULL)
|
|
{
|
|
TArray<FObjectComparison>& ObjectsToIgnore = *pObjectsToIgnore;
|
|
for (int32 IgnoreIndex = 0; IgnoreIndex < ObjectsToIgnore.Num(); IgnoreIndex++)
|
|
{
|
|
FObjectGraph* IgnoreGraph = ObjectsToIgnore[IgnoreIndex].ObjectSets[PackageIndex];
|
|
if (IgnoreGraph != NULL)
|
|
{
|
|
Subobjects.AddUnique(IgnoreGraph->GetRootObject());
|
|
/*
|
|
adding the root object *should* be sufficient (as theoretically, the only object that should have a reference to its subobjects
|
|
is that object....but if that doesn't work correctly, we'll add all of the objects in the graph
|
|
for ( int32 GraphIndex = 1; GraphIndex < IgnoreGraph->Objects.Num(); GraphIndex++ )
|
|
{
|
|
Subobjects.AddUniqueItem(IgnoreGraph->Objects(GraphIndex).Object);
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
const int32 StartIndex = Subobjects.Num();
|
|
FReferenceFinder ObjectReferenceCollector(Subobjects, ObjSet.Object, true, false, USE_DEEP_RECURSION);
|
|
ObjectReferenceCollector.FindReferences(ObjSet.Object);
|
|
|
|
// add all the newly serialized objects to the object set
|
|
for (int32 Index = StartIndex; Index < Subobjects.Num(); Index++)
|
|
{
|
|
new (Objects) FObjectReference(Subobjects[Index]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a simulated path name for the specified object, replacing the name of this object's actual outer-most with the specified package name.
|
|
*
|
|
* @param Object the object to generate a simulated path name for
|
|
* @param OtherPackageName the package name to use in the simulated path name
|
|
*
|
|
* to generate a path name which doesn't include the outermost package's name.
|
|
*/
|
|
FString MakeMatchingPathName(UObject* Object, const TCHAR* OtherPackageName)
|
|
{
|
|
// turn SourceObject's pathname into the pathname for the other object
|
|
FString ObjPath = Object->GetPathName();
|
|
return FString::Printf(TEXT("%s%s"), OtherPackageName, *ObjPath.Right(ObjPath.Len() - ObjPath.Find(TEXT("."))));
|
|
}
|
|
|
|
/**
|
|
* Finds the counterpart object from the specified object set. ObjectSet should be a list of objects from a different package than SourceObject is contained in.
|
|
*
|
|
* @param SourceObject the object to find the counterpart for
|
|
* @param PackageName the name of the package which contains the objects in ObjectSet
|
|
* @param ObjectSet the set of objects to search for SourceObject's counterpart; should be an object set from a different package than SourceObject.
|
|
*
|
|
* @return a pointer to the counterpart object from another package (specified by the ObjectSet) for SourceObject, or NULL if the ObjectSet doesn't contain
|
|
* a counterpart to SourceObject.
|
|
*
|
|
*/
|
|
UObject* FindMatchingObjectInObjectSet(UObject* SourceObject, const TCHAR* PackageName, FObjectGraph* ObjectSet)
|
|
{
|
|
UObject* Result = NULL;
|
|
|
|
// can't look in a NULL objectset!
|
|
if (ObjectSet != NULL)
|
|
{
|
|
// generate the path name that SourceObject would have if it was in the package represented by ObjectSet.
|
|
FString ObjPath = MakeMatchingPathName(SourceObject, PackageName);
|
|
|
|
// the first object in an object set is always the package root, so we skip that object.
|
|
for (int32 Index = 1; Index < ObjectSet->Objects.Num(); Index++)
|
|
{
|
|
// does this object match?
|
|
if (ObjPath == ObjectSet->Objects[Index].ObjectPathName)
|
|
{
|
|
// found it!
|
|
Result = ObjectSet->Objects[Index].Object;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Searches for an object contained within the package which has the specified name, which is the counterpart to SourceObject.
|
|
*
|
|
* @param SourceObject the object to search for a counterpart for
|
|
* @param PackageName the name of the package to search for SourceObject's counterpart in
|
|
*
|
|
* @return a pointer to the object from the specified package which is the counterpart for SourceObject, or NULL if the there is no
|
|
* counterpart for SourceObject in the specified package.
|
|
*/
|
|
UObject* FindMatchingObject(UObject* SourceObject, const TCHAR* PackageName)
|
|
{
|
|
FString ObjPath = MakeMatchingPathName(SourceObject, PackageName);
|
|
UObject* OtherObject = StaticFindObject(SourceObject->GetClass(), NULL, *ObjPath, true);
|
|
|
|
return OtherObject;
|
|
}
|
|
|
|
/**
|
|
* Copies an object into the given package. The new object will have the same
|
|
* group hierarchy as the original object.
|
|
*
|
|
* @param Package The package to copy the object in to
|
|
* @param Object The obhject to copy over.
|
|
*
|
|
* @return The newly copied object
|
|
*/
|
|
UObject* CopyObjectToPackage(UPackage* Package, UObject* Object)
|
|
{
|
|
// if there was no outer, this is the top level package, which we don't weant to copy
|
|
if (Object->GetOuter() == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
UWorld* World = UWorld::FindWorldInPackage(Package);
|
|
|
|
// get the pathname of our outer object
|
|
FString OrigPathName = Object->GetOuter()->GetPathName();
|
|
UObject* NewOuter = NULL;
|
|
|
|
// we are going to replace the outermost package name of the object with the destination package name
|
|
int32 Dot = OrigPathName.Find(TEXT("."));
|
|
|
|
// if there was no dot, then our outer was the apckage, in which case our new will just be the package name
|
|
if (Dot == -1)
|
|
{
|
|
NewOuter = Package;
|
|
}
|
|
else if (Object->GetOuter()->IsA(UPackage::StaticClass()))
|
|
{
|
|
// otherwise, we need to possibly create the whole package group hierarchy
|
|
|
|
// cretate the new pathname from package name and everything after the original package name
|
|
FString NewPathName = FString(*Package->GetName()) + OrigPathName.Right(OrigPathName.Len() - Dot);
|
|
NewOuter = CreatePackage(*NewPathName);
|
|
}
|
|
else
|
|
{
|
|
// the last case is when we are in another object that's not a package. find the
|
|
// corresponding outer in the new package
|
|
// @todo: this is order-dependent, the outer must be copied over first!
|
|
NewOuter = FindMatchingObject(Object->GetOuter(), *Package->GetName());
|
|
|
|
if (!NewOuter)
|
|
{
|
|
UE_LOG(LogDiffPackagesCommandlet, Fatal, TEXT("'%s's outer hasn't been copied yet!"), *Object->GetFullName());
|
|
}
|
|
}
|
|
|
|
// serialize out the original object
|
|
TArray<uint8> Bytes;
|
|
FObjectWriter(Object, Bytes);
|
|
|
|
// make a new object
|
|
UObject* NewUObject = NewObject<UObject>(NewOuter, Object->GetClass(), Object->GetFName(), Object->GetFlags(), Object->GetArchetype(), true);
|
|
|
|
// serialize old objects on top of the new object
|
|
FObjectReader Reader(NewUObject, Bytes);
|
|
|
|
return NewUObject;
|
|
}
|
|
|
|
/**
|
|
* Any properties in any object in Package that point to a key in the ObjectReplacementMap
|
|
* will be replaced with the key in the map.
|
|
* This is so that when we copy objects into the merged package, the refs in the
|
|
* merged package will be fixed up to point to objects inside the merged package.
|
|
*/
|
|
void FixupObjectReferences(UPackage* Package, TMap<UObject*, UObject*>& ObjectReplacementMap)
|
|
{
|
|
for (FThreadSafeObjectIterator It; It; ++It)
|
|
{
|
|
if (It->IsIn(Package))
|
|
{
|
|
FArchiveReplaceObjectRef<UObject> ReplaceAr(*It, ObjectReplacementMap, true, false, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UDEPRECATED_DiffPackagesCommandlet::GenerateObjectComparison(UObject* RootObject, FObjectComparison& out_Comparison, TArray<FObjectComparison>* ObjectsToIgnore /*=NULL*/)
|
|
{
|
|
check(RootObject);
|
|
|
|
bool bFound = false;
|
|
|
|
// mark that it's been put into a diff
|
|
HandledAnnotation.Set(RootObject);
|
|
|
|
// the packages that we need to find a matching object in
|
|
bool bNeedsObjectMatch[MAX_PACKAGECOUNT] = {true, true, true};
|
|
|
|
// put the object and all its subobjects into the proper list
|
|
for (int32 PackageIndex = 0; PackageIndex < NumPackages; PackageIndex++)
|
|
{
|
|
if (RootObject->IsIn(Packages[PackageIndex]))
|
|
{
|
|
bNeedsObjectMatch[PackageIndex] = false;
|
|
bFound = true;
|
|
|
|
FObjectGraph* NewObjectSet = new FObjectGraph(RootObject, PackageIndex, ObjectsToIgnore);
|
|
out_Comparison.ObjectSets[PackageIndex] = NewObjectSet;
|
|
|
|
// get the name of the object without the package name
|
|
out_Comparison.RootObjectPath = RootObject->GetPathName(Packages[PackageIndex]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if this object isn't in any of the packages, then skip it
|
|
if (bFound)
|
|
{
|
|
// find a matching object set in the other 2
|
|
for (int32 PackageIndex = 0; PackageIndex < NumPackages; PackageIndex++)
|
|
{
|
|
if (bNeedsObjectMatch[PackageIndex])
|
|
{
|
|
// look for the root object in this package
|
|
UObject* MatchingObject = FindMatchingObject(RootObject, *Packages[PackageIndex]->GetName());
|
|
if (MatchingObject)
|
|
{
|
|
// mark that it's been put into a diff
|
|
HandledAnnotation.Set(MatchingObject);
|
|
|
|
// make the object set for this object
|
|
FObjectGraph* NewObjectSet = new FObjectGraph(MatchingObject, PackageIndex, ObjectsToIgnore);
|
|
out_Comparison.ObjectSets[PackageIndex] = NewObjectSet;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bFound;
|
|
}
|
|
|
|
bool UDEPRECATED_DiffPackagesCommandlet::Initialize(const TCHAR* Parms)
|
|
{
|
|
bool bResult = false;
|
|
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("#####################################################"));
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("# DiffPackages is deprecated, use DiffAssets instead."));
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("#####################################################"));
|
|
CLEAR_WARN_COLOR();
|
|
|
|
// parse the command line into tokens and switches
|
|
TArray<FString> Tokens, Switches;
|
|
ParseCommandLine(Parms, Tokens, Switches);
|
|
|
|
// if a merge package is specified, the pathname for the destination package
|
|
UPackage* MergePackage = NULL;
|
|
|
|
// find the package files that should be diffed - doesn't need to be a valid package path (i.e. can be a package located in a tmp directory or something)
|
|
for (int32 TokenIndex = 0; TokenIndex < Tokens.Num(); TokenIndex++)
|
|
{
|
|
TArray<FString> FilesInPath;
|
|
|
|
bool bMergePackage = false, bAncestorPackage = false, bFirstPackage = false, bSecondPackage = false;
|
|
FString PackageWildcard = Tokens[TokenIndex];
|
|
if (PackageWildcard.Contains(TEXT("=")))
|
|
{
|
|
FString ParsedFilename;
|
|
if (FParse::Value(*PackageWildcard, TEXT("MERGE="), ParsedFilename))
|
|
{
|
|
bMergePackage = true;
|
|
}
|
|
// look for a common ancestor setting
|
|
else if (FParse::Value(*PackageWildcard, TEXT("ANCESTOR="), ParsedFilename))
|
|
{
|
|
bAncestorPackage = true;
|
|
}
|
|
PackageWildcard = ParsedFilename;
|
|
}
|
|
else
|
|
{
|
|
if (Packages[0] == NULL)
|
|
{
|
|
bFirstPackage = true;
|
|
}
|
|
else if (Packages[1] == NULL)
|
|
{
|
|
bSecondPackage = true;
|
|
}
|
|
else
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("Too many packages specified (only two allowed)! Use 'help DiffPackagesCommandlet' to view correct usage syntax for this commandlet."));
|
|
CLEAR_WARN_COLOR();
|
|
bResult = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (PackageWildcard.Len() == 0)
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("No package specified for parameter %i: %s. Use 'help DiffPackagesCommandlet' to view correct usage syntax for this commandlet."),
|
|
TokenIndex, *Tokens[TokenIndex]);
|
|
|
|
CLEAR_WARN_COLOR();
|
|
bResult = false;
|
|
break;
|
|
}
|
|
|
|
IFileManager::Get().FindFiles((TArray<FString>&)FilesInPath, *PackageWildcard, true, false);
|
|
if (FilesInPath.Num() == 0)
|
|
{
|
|
// if no files were found in the script directory, search all valid package paths
|
|
TArray<FString> Paths;
|
|
if (GConfig->GetArray(TEXT("Core.System"), TEXT("Paths"), Paths, GEngineIni) > 0)
|
|
{
|
|
for (int32 i = 0; i < Paths.Num(); i++)
|
|
{
|
|
IFileManager::Get().FindFiles((TArray<FString>&)FilesInPath, *(Paths[i] / PackageWildcard), 1, 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// re-add the path information so that GetPackageLinker finds the correct version of the file.
|
|
FString WildcardPath = PackageWildcard;
|
|
for (int32 FileIndex = 0; FileIndex < FilesInPath.Num(); FileIndex++)
|
|
{
|
|
FilesInPath[FileIndex] = FPaths::GetPath(WildcardPath) / FilesInPath[FileIndex];
|
|
}
|
|
}
|
|
|
|
if (bMergePackage)
|
|
{
|
|
//@{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("Merging is not currently supported"));
|
|
bResult = false;
|
|
break;
|
|
//@}
|
|
|
|
/*
|
|
if ( FilesInPath.Num() > 0 )
|
|
{
|
|
// this means that the location/package name that the user specified is an existing file...
|
|
// eventually we'll probably allow this but for simplicity's sake, at this point it's an error
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("Merge target file already exists: %s. Overwriting an existing file via merging is now allowed!"), *FilesInPath[0]);
|
|
bResult = false;
|
|
break;
|
|
}
|
|
|
|
MergePackage = CreatePackage( TEXT("MergePackage"));
|
|
// diff all properties if we are merging ?
|
|
// todo: ??
|
|
// bDiffAllProps = true;
|
|
// bDiffNonEditProps = true;
|
|
SET_WARN_COLOR_AND_BACKGROUND(COLOR_RED, COLOR_WHITE);
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("\n"));
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("=============================================================================="));
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("WARNING: Merge functionality is not finished! (It only copies from Package 1!)"));
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("=============================================================================="));
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("\n"));
|
|
CLEAR_WARN_COLOR();
|
|
//@todo - validation
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
// because of the nature of this commandlet, each parameter should correspond to exactly one package -
|
|
if (FilesInPath.Num() == 1)
|
|
{
|
|
const FString& Filename = FilesInPath[0];
|
|
SET_WARN_COLOR(COLOR_DARK_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("Loading %s"), *Filename);
|
|
CLEAR_WARN_COLOR();
|
|
|
|
UPackage* Package = NULL;
|
|
if (bFirstPackage)
|
|
{
|
|
check(Packages[0] == NULL);
|
|
|
|
// to avoid conflicts when loading packages from different locations that have the same name, create a dummy package to contain
|
|
// the file we're about the load - this will prevent the second/third versions of the file from replacing the first version when loaded.
|
|
Package = CreatePackage(TEXT("Package_A"));
|
|
Packages[0] = LoadPackage(Package, *Filename, LOAD_None);
|
|
PackageFilenames[0] = FPaths::GetBaseFilename(Filename);
|
|
NumPackages++;
|
|
}
|
|
else if (bSecondPackage)
|
|
{
|
|
check(Packages[1] == NULL);
|
|
|
|
// to avoid conflicts when loading packages from different locations that have the same name, create a dummy package to contain
|
|
// the file we're about the load - this will prevent the second/third versions of the file from replacing the first version when loaded.
|
|
Package = CreatePackage(TEXT("Package_B"));
|
|
Packages[1] = LoadPackage(Package, *Filename, LOAD_None);
|
|
PackageFilenames[1] = FPaths::GetBaseFilename(Filename);
|
|
NumPackages++;
|
|
}
|
|
else if (bAncestorPackage)
|
|
{
|
|
check(Packages[2] == NULL);
|
|
|
|
// to avoid conflicts when loading packages from different locations that have the same name, create a dummy package to contain
|
|
// the file we're about the load - this will prevent the second/third versions of the file from replacing the first version when loaded.
|
|
Package = CreatePackage(TEXT("Package_C"));
|
|
Packages[2] = LoadPackage(Package, *Filename, LOAD_None);
|
|
PackageFilenames[2] = FPaths::GetBaseFilename(Filename);
|
|
NumPackages++;
|
|
}
|
|
|
|
if (Package == NULL)
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("Error loading package %s!"), *Filename);
|
|
CLEAR_WARN_COLOR();
|
|
bResult = false;
|
|
break;
|
|
}
|
|
|
|
bResult = true;
|
|
}
|
|
else
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
if (FilesInPath.Num() > 0)
|
|
{
|
|
if (PackageWildcard.Contains(TEXT("*")) || PackageWildcard.Contains(TEXT("?")))
|
|
{
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("Wildcards are not allowed when specifying the name of a package to compare: %s"), *Tokens[TokenIndex]);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("Multiple source files found for parameter %i: '%s'. Please use the fully qualified path name for the package to avoid ambiguity."),
|
|
TokenIndex, *Tokens[TokenIndex]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("No files found for parameter %i: '%s'."), TokenIndex, *Tokens[TokenIndex]);
|
|
}
|
|
|
|
CLEAR_WARN_COLOR();
|
|
bResult = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the filenames are identical (different directories) then apply a suffix so we can distinguish them
|
|
if (PackageFilenames[0] == PackageFilenames[2] || PackageFilenames[1] == PackageFilenames[2])
|
|
{
|
|
PackageFilenames[2] += TEXT(" (O)");
|
|
}
|
|
if (PackageFilenames[0] == PackageFilenames[1])
|
|
{
|
|
PackageFilenames[0] += TEXT(" (A)");
|
|
PackageFilenames[1] += TEXT(" (B)");
|
|
}
|
|
// pad the names so that they are all the length of the longest (so the output lines up nicely)
|
|
int32 LongestLen = FMath::Max<int32>(PackageFilenames[2].Len(), FMath::Max<int32>(PackageFilenames[0].Len(), PackageFilenames[1].Len()));
|
|
for (int32 i = 0; i < 3; i++)
|
|
{
|
|
PackageFilenames[i] = PackageFilenames[i].RightPad(LongestLen);
|
|
}
|
|
|
|
// now process the switches
|
|
if (Switches.Contains(TEXT("FULL")))
|
|
{
|
|
bDiffAllProps = true;
|
|
bDiffNonEditProps = true;
|
|
}
|
|
else if (Switches.Contains(TEXT("MOST")))
|
|
{
|
|
bDiffNonEditProps = true;
|
|
}
|
|
|
|
// verify that we got at least two packages
|
|
if (bResult && (Packages[0] == NULL || Packages[1] == NULL))
|
|
{
|
|
SET_WARN_COLOR(COLOR_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Error, TEXT("You must specify at least two packages (not counting the ancestor package) to use this commandlet. Use 'help DiffPackagesCommandlet' to view correct usage syntax for this commandlet."));
|
|
|
|
CLEAR_WARN_COLOR();
|
|
bResult = false;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
int32 UDEPRECATED_DiffPackagesCommandlet::Main(const FString& Params)
|
|
{
|
|
if (!Initialize(*Params))
|
|
{
|
|
// Initialize fails if the command-line parameters were invalid.
|
|
return 1;
|
|
}
|
|
|
|
TArray<UObject*> IdenticalObjects;
|
|
TArray<FObjectComparison> ObjectDiffs;
|
|
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("%sBuilding list of objects to diff...."), LINE_TERMINATOR);
|
|
|
|
TIndirectArray<FObjectGraph> AllObjectSets[MAX_PACKAGECOUNT];
|
|
|
|
#if OPTIMIZE_LEVEL_DIFFS
|
|
// since ULevel objects reference most everything else in the package, we specially handle them so that the diff results don't all appear
|
|
// under the level object
|
|
TArray<ULevel*> Levels;
|
|
#endif
|
|
|
|
// loop through all objects in A
|
|
for (FThreadSafeObjectIterator It; It; ++It)
|
|
{
|
|
UObject* Obj = *It;
|
|
|
|
// skip over package and world objects
|
|
if (It->IsA(UPackage::StaticClass()) || It->IsA(UWorld::StaticClass()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// we only care about high level objects, like objects not inside other objects (actors are inside a level, not a package)
|
|
if (!It->GetOuter()->IsA(UPackage::StaticClass()) && !It->GetOuter()->IsA(ULevel::StaticClass()) && !It->GetOuter()->IsA(UWorld::StaticClass()))
|
|
{
|
|
continue;
|
|
}
|
|
// if we're already created an object comparison for this object, skip it
|
|
if (HandledAnnotation.Get(*It))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
#if OPTIMIZE_LEVEL_DIFFS
|
|
if (It->IsA(ULevel::StaticClass()))
|
|
{
|
|
Levels.AddUnique(Cast<ULevel>(*It));
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// if this object isn't in any of the packages, then skip it
|
|
FObjectComparison Comparison;
|
|
if (GenerateObjectComparison(*It, Comparison))
|
|
{
|
|
for (int32 PackageIndex = 0; PackageIndex < NumPackages; PackageIndex++)
|
|
{
|
|
if (Comparison.ObjectSets[PackageIndex] != NULL)
|
|
{
|
|
AllObjectSets[PackageIndex].Add(Comparison.ObjectSets[PackageIndex]);
|
|
}
|
|
}
|
|
|
|
// add this diff to our global list of diffs
|
|
ObjectDiffs.Add(Comparison);
|
|
}
|
|
}
|
|
|
|
#if OPTIMIZE_LEVEL_DIFFS
|
|
// now process the levels
|
|
for (int32 LevelIndex = 0; LevelIndex < Levels.Num(); LevelIndex++)
|
|
{
|
|
ULevel* Level = Levels[LevelIndex];
|
|
if (!HandledAnnotation.Get(Level))
|
|
{
|
|
FObjectComparison Comparison;
|
|
if (GenerateObjectComparison(Level, Comparison, &ObjectDiffs))
|
|
{
|
|
for (int32 PackageIndex = 0; PackageIndex < NumPackages; PackageIndex++)
|
|
{
|
|
if (Comparison.ObjectSets[PackageIndex] != NULL)
|
|
{
|
|
AllObjectSets[PackageIndex].Add(Comparison.ObjectSets[PackageIndex]);
|
|
}
|
|
}
|
|
|
|
ObjectDiffs.Add(Comparison);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
struct FCompareFObjectComparison
|
|
{
|
|
FORCEINLINE bool operator()(const FObjectComparison& A, const FObjectComparison& B) const
|
|
{
|
|
return A.RootObjectPath < B.RootObjectPath;
|
|
}
|
|
};
|
|
ObjectDiffs.Sort(FCompareFObjectComparison());
|
|
|
|
SET_WARN_COLOR(COLOR_DARK_RED);
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("%sComparing %d objects"), LINE_TERMINATOR, ObjectDiffs.Num());
|
|
for (int32 DiffIndex = 0; DiffIndex < ObjectDiffs.Num(); DiffIndex++)
|
|
{
|
|
// diff all the combination of objects
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("Performing comparison for object %d: %s"), DiffIndex, *ObjectDiffs[DiffIndex].RootObjectPath);
|
|
ProcessDiff(ObjectDiffs[DiffIndex]);
|
|
}
|
|
CLEAR_WARN_COLOR();
|
|
|
|
SET_WARN_COLOR(COLOR_WHITE);
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("\nDifferences Found:"));
|
|
CLEAR_WARN_COLOR();
|
|
|
|
for (int32 DiffIndex = 0; DiffIndex < ObjectDiffs.Num(); DiffIndex++)
|
|
{
|
|
FObjectComparison& Diff = ObjectDiffs[DiffIndex];
|
|
|
|
if (Diff.PropDiffs.Num())
|
|
{
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("------------------------------"));
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("%s [Overall result: %s]:"), *Diff.RootObjectPath, GetDiffTypeText(Diff.OverallDiffType, NumPackages));
|
|
|
|
for (int32 PropDiffIndex = 0; PropDiffIndex < Diff.PropDiffs.Num(); PropDiffIndex++)
|
|
{
|
|
FPropertyComparison& PropDiff = Diff.PropDiffs[PropDiffIndex];
|
|
|
|
SET_WARN_COLOR((PropDiff.DiffType == OD_ABConflict || PropDiff.DiffType == OD_Invalid) ? COLOR_RED : COLOR_YELLOW);
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("%s"), *PropDiff.DiffText);
|
|
CLEAR_WARN_COLOR();
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// disable merging for now....
|
|
|
|
// do we want to merge objects?
|
|
if (MergePackage)
|
|
{
|
|
// map any old object refs to the merged refs
|
|
TMap<UObject*, UObject*> ReplacementMap;
|
|
|
|
// automatically merge over all identical objects
|
|
for (int32 Index = 0; Index < ObjectDiffs.Num(); Index++)
|
|
{
|
|
FObjectComparison& Diff = ObjectDiffs(Index);
|
|
|
|
// if we had no diffs, then just copy the first object
|
|
int32 ObjectToCopy = 0;
|
|
if (Diff.OverallDiffType == OD_BOnly)
|
|
{
|
|
ObjectToCopy = 1;
|
|
}
|
|
// @todo: handle conflict and invalid diffs
|
|
|
|
// if the object we want to copy doesn't exist (ie, a valid deletion), skip it
|
|
if (Diff.ObjectSets[ObjectToCopy] == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UE_LOG(LogDiffPackagesCommandlet, Warning, TEXT("Copying %s from package %d"), *Diff.ObjectSets[ObjectToCopy]->GetRootObject()->GetPathName(), ObjectToCopy);
|
|
//!!!!!!!!!!!!!!! @todo iterate over non-root objects for all 3 properly, etc etc
|
|
for (int32 SetIndex = 0; SetIndex < Diff.ObjectSets[ObjectToCopy]->Objects.Num(); SetIndex++)
|
|
{
|
|
UObject* NewObject = CopyObjectToPackage(MergePackage, Diff.ObjectSets[ObjectToCopy]->Objects(SetIndex).Object);
|
|
if (NewObject)
|
|
{
|
|
// make sure any references to these private objects don't get nuled
|
|
ReplacementMap.Set(NewObject, NewObject);
|
|
|
|
// map any refs to the original packages to the new object
|
|
for (int32 Package = 0; Package < NumPackages; Package++)
|
|
{
|
|
if (Diff.ObjectSets[Package])
|
|
{
|
|
ReplacementMap.Set(Diff.ObjectSets[Package]->Objects(SetIndex).Object, NewObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// fixup our references so we aren't linking to external private objects
|
|
FixupObjectReferences(MergePackage, ReplacementMap);
|
|
|
|
UWorld* World = UWorld::FindWorldInPackage(MergePackage);
|
|
if(World)
|
|
{
|
|
SavePackage(MergePackage, World, 0, *MergePackageFilename, GWarn);
|
|
}
|
|
else
|
|
{
|
|
SavePackage(MergePackage, NULL, RF_Standalone, *MergePackageFilename, GWarn);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for appending a comparison result to an comparison result buffer.
|
|
*/
|
|
void AppendComparisonResultText(FString& ExistingResultText, const FString& NewResultText)
|
|
{
|
|
ExistingResultText += NewResultText + LINE_TERMINATOR;
|
|
}
|
|
|
|
bool UDEPRECATED_DiffPackagesCommandlet::ProcessDiff(FObjectComparison& Diff)
|
|
{
|
|
// always diff the root objects against each other
|
|
Diff.OverallDiffType = DiffObjects(
|
|
Diff.ObjectSets[0] ? Diff.ObjectSets[0]->GetRootObject() : NULL,
|
|
Diff.ObjectSets[1] ? Diff.ObjectSets[1]->GetRootObject() : NULL,
|
|
Diff.ObjectSets[2] ? Diff.ObjectSets[2]->GetRootObject() : NULL,
|
|
Diff);
|
|
|
|
check(NumPackages <= 3);
|
|
for (int32 PackageIndex = 0; PackageIndex < NumPackages; PackageIndex++)
|
|
{
|
|
// its possible we have a NULL object set if the root object isn't in the package
|
|
if (!Diff.ObjectSets[PackageIndex])
|
|
{
|
|
continue;
|
|
}
|
|
// now go through the non-root object sets looking for different objects
|
|
for (int32 ObjectIndex = 1; ObjectIndex < Diff.ObjectSets[PackageIndex]->Objects.Num(); ObjectIndex++)
|
|
{
|
|
UObject* Objects[MAX_PACKAGECOUNT] = {NULL, NULL, NULL};
|
|
|
|
// get the object in the object set
|
|
Objects[PackageIndex] = Diff.ObjectSets[PackageIndex]->Objects[ObjectIndex].Object;
|
|
|
|
// if the object is marked, it's already been diffed against another objectset, no need to do it again
|
|
if (HandledAnnotation.Get(Objects[PackageIndex]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// find matching objects in the other packages
|
|
for (int32 OtherPackageIndex = PackageIndex + 1; OtherPackageIndex < NumPackages; OtherPackageIndex++)
|
|
{
|
|
Objects[OtherPackageIndex] = FindMatchingObjectInObjectSet(Objects[PackageIndex], *Packages[OtherPackageIndex]->GetName(), Diff.ObjectSets[OtherPackageIndex]);
|
|
}
|
|
|
|
// mark that these subobjects have been diffed (this is used for finding unmatched subobjects later)
|
|
for (int32 ObjIndex = 0; ObjIndex < NumPackages; ObjIndex++)
|
|
{
|
|
if (Objects[ObjIndex])
|
|
{
|
|
HandledAnnotation.Set(Objects[ObjIndex]);
|
|
}
|
|
}
|
|
|
|
// diff the 2-3 objects
|
|
EObjectDiff DiffType = DiffObjects(Objects[0], Objects[1], Objects[2], Diff);
|
|
if (DiffType != OD_None)
|
|
{
|
|
if (Diff.OverallDiffType == OD_None || Diff.OverallDiffType == DiffType)
|
|
{
|
|
Diff.OverallDiffType = DiffType;
|
|
}
|
|
else
|
|
{
|
|
Diff.OverallDiffType = OD_ABConflict;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Diff.OverallDiffType != OD_None;
|
|
}
|
|
|
|
EObjectDiff UDEPRECATED_DiffPackagesCommandlet::DiffObjects(UObject* ObjA, UObject* ObjB, UObject* ObjAncestor, FObjectComparison& Diff)
|
|
{
|
|
// if all objects are NULL, there's no difference :)
|
|
if (!ObjA && !ObjB && !ObjAncestor)
|
|
{
|
|
return OD_None;
|
|
}
|
|
|
|
// PVS-Studio does not understand the assumption that we must have an ancestor if we have an ObjA or an ObjB, and is
|
|
// warning about usage of ObjAncestor pointer:
|
|
UClass* ComparisonClass = (ObjA ? ObjA->GetClass() : (ObjB ? ObjB->GetClass() : ObjAncestor->GetClass())); //-V595
|
|
|
|
// complex logic for what kind of differnce this is, if at all
|
|
|
|
// if one of the objects is a different class, just abort this whole thing
|
|
if ((ObjA && ObjA->GetClass() != ComparisonClass) ||
|
|
(ObjB && ObjB->GetClass() != ComparisonClass) ||
|
|
(ObjAncestor && ObjAncestor->GetClass() != ComparisonClass))
|
|
{
|
|
FPropertyComparison InvalidClassDiff;
|
|
AppendComparisonResultText(InvalidClassDiff.DiffText, FString::Printf(TEXT("Incompatible classes ('%s' '%s' '%s'"),
|
|
*ObjA->GetFullName(), *ObjB->GetFullName(), *ObjAncestor->GetFullName()));
|
|
|
|
InvalidClassDiff.DiffType = OD_Invalid;
|
|
|
|
// add this diff to the list of diffs
|
|
Diff.PropDiffs.Add(InvalidClassDiff);
|
|
return OD_Invalid;
|
|
}
|
|
|
|
// @todo: don't make this ongoing, make this one for each different property
|
|
EObjectDiff OverallDiffType = OD_None;
|
|
for (FProperty* Prop = ComparisonClass->PropertyLink; Prop; Prop = Prop->PropertyLinkNext)
|
|
{
|
|
// if this is not an editable property and -most or -full was not specified, then skip this property
|
|
if (!bDiffNonEditProps && (Prop->PropertyFlags & CPF_Edit) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if this is UObject property and -full was not specified, then skip this property
|
|
if (!bDiffAllProps && Prop->IsInContainer(UObject::StaticClass()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int32 Index = 0; Index < Prop->ArrayDim; Index++)
|
|
{
|
|
// friendly property name
|
|
FString PropName = (Prop->ArrayDim > 1) ? FString::Printf(TEXT("%s[%d]"), *Prop->GetName(), Index) : *Prop->GetName();
|
|
|
|
// get the string values for the property
|
|
FString PropTextA, PropTextB, PropTextAncestor;
|
|
if (ObjA)
|
|
{
|
|
Prop->ExportText_InContainer(Index, PropTextA, ObjA, ObjA, ObjA, PPF_SimpleObjectText);
|
|
}
|
|
if (ObjB)
|
|
{
|
|
Prop->ExportText_InContainer(Index, PropTextB, ObjB, ObjB, ObjB, PPF_SimpleObjectText);
|
|
}
|
|
if (ObjAncestor)
|
|
{
|
|
Prop->ExportText_InContainer(Index, PropTextAncestor, ObjAncestor, ObjAncestor, ObjAncestor, PPF_SimpleObjectText);
|
|
}
|
|
|
|
// if the original text is different and the text where we replaced package A with B
|
|
// is different, then the properties are really different
|
|
// we can't just check for the second case for this:
|
|
// PropTextA: I love A.
|
|
// PropTextB: I love A.
|
|
// PropTextPkgReplaedB: I Love B.
|
|
|
|
// @todo: handle the I Love case above when removing text and not replacing it!!
|
|
// @todo: handle empty strings!
|
|
|
|
FPropertyComparison PropDiff;
|
|
PropDiff.Prop = Prop;
|
|
PropDiff.DiffType = OD_None;
|
|
|
|
// check for a change from ancestor, but to the same result
|
|
if (PropTextA.Len() && PropTextA == PropTextB)
|
|
{
|
|
// if we had an ancestor, and it was different, then we have a diff, but same result
|
|
if (ObjAncestor && PropTextA != PropTextAncestor)
|
|
{
|
|
check(ObjA);
|
|
FString FullPath = ObjA->GetFullName(Packages[0]);
|
|
|
|
PropDiff.DiffType = OD_ABSame;
|
|
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType, NumPackages), *FullPath, *PropName));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Now: %s"), *PropTextA));
|
|
|
|
// accumulate diff types
|
|
if (OverallDiffType == OD_None)
|
|
{
|
|
OverallDiffType = OD_ABSame;
|
|
}
|
|
}
|
|
// otherwise, if no ancestor, or ancestor and a is the same as ancestor, then b there is no diff at all!
|
|
// this is hopefully the common case :)
|
|
}
|
|
// okay, if A and B are different, need to compare against ancestor if we have one
|
|
else
|
|
{
|
|
// if we have an ancestor, compare a and b against ancestor
|
|
if (ObjAncestor)
|
|
{
|
|
FString FullPath = ObjAncestor->GetFullName(Packages[2]);
|
|
|
|
// if A == ancestor, then only B changed
|
|
if (PropTextA.Len() && PropTextA == PropTextAncestor)
|
|
{
|
|
PropDiff.DiffType = OD_BOnly;
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType, NumPackages), *FullPath, *PropName));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Now: %s"), *PropTextB));
|
|
|
|
// accumulate diff types
|
|
if (OverallDiffType == OD_None || OverallDiffType == OD_BOnly)
|
|
{
|
|
OverallDiffType = OD_BOnly;
|
|
}
|
|
else
|
|
{
|
|
OverallDiffType = OD_ABConflict;
|
|
}
|
|
}
|
|
// otherwise, if B == ancestor, then only A changed
|
|
else if (PropTextB.Len() && PropTextB == PropTextAncestor)
|
|
{
|
|
PropDiff.DiffType = OD_AOnly;
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType, NumPackages), *FullPath, *PropName));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Now: %s"), *PropTextA));
|
|
|
|
// accumulate diff types
|
|
if (OverallDiffType == OD_None || OverallDiffType == OD_AOnly)
|
|
{
|
|
OverallDiffType = OD_AOnly;
|
|
}
|
|
else
|
|
{
|
|
OverallDiffType = OD_ABConflict;
|
|
}
|
|
}
|
|
// otherwise neither A or B equal ancestor, so we have a conflict!
|
|
else if (PropTextA.Len() && PropTextB.Len())
|
|
{
|
|
PropDiff.DiffType = OD_ABConflict;
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType, NumPackages), *FullPath, *PropName));
|
|
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[0], *PropTextA));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[1], *PropTextB));
|
|
}
|
|
|
|
// accumulate diff types
|
|
OverallDiffType = OD_ABConflict;
|
|
}
|
|
// if we have no ancestor, and they are different, there's no way to know which one is right, so we
|
|
// mark is as a conflict
|
|
else if (PropTextA.Len() && PropTextB.Len())
|
|
{
|
|
check(ObjB);
|
|
PropDiff.DiffType = OD_ABConflict;
|
|
FString FullPath = ObjA ? ObjA->GetFullName(Packages[0]) : ObjB->GetFullName(Packages[1]); //-V595 PVS-Studio does not understand the check(objB) above, and is warning about usage of the objB pointer
|
|
|
|
// recompose the text relative to each other so that when showing differences of structs,
|
|
// only properties within the struct that actually changed are shown
|
|
//@note: this doesn't work for references to other objects within the packages being tested, since they're different instances
|
|
if (ObjA != NULL && ObjB != NULL)
|
|
{
|
|
PropTextA.Empty();
|
|
PropTextB.Empty();
|
|
Prop->ExportText_InContainer(Index, PropTextA, ObjA, ObjB, ObjA, PPF_SimpleObjectText);
|
|
Prop->ExportText_InContainer(Index, PropTextB, ObjB, ObjA, ObjB, PPF_SimpleObjectText);
|
|
}
|
|
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType, NumPackages), *FullPath, *PropName));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[0], *PropTextA));
|
|
AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[1], *PropTextB));
|
|
|
|
// accumulate diff types
|
|
OverallDiffType = OD_ABConflict;
|
|
}
|
|
}
|
|
|
|
// if we actually had a diff, add it to the list
|
|
if (PropDiff.DiffType != OD_None)
|
|
{
|
|
Diff.PropDiffs.Add(PropDiff);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 NumObjects = 0;
|
|
if (ObjA)
|
|
NumObjects++;
|
|
if (ObjB)
|
|
NumObjects++;
|
|
if (ObjAncestor)
|
|
NumObjects++;
|
|
|
|
// if this is a native class and we have at least two objects, include the property values for any natively serialized properties in the property comparison.
|
|
if (ComparisonClass->HasAnyClassFlags(CLASS_Native) && NumObjects > 1)
|
|
{
|
|
EObjectDiff NativePropertyDiffType = CompareNativePropertyValues(ObjA, ObjB, ObjAncestor, Diff);
|
|
if (NativePropertyDiffType != OD_None && OverallDiffType == OD_None)
|
|
{
|
|
OverallDiffType = NativePropertyDiffType;
|
|
}
|
|
}
|
|
|
|
// now that we have done the per-property diffs, we can do whole-object diffs:
|
|
|
|
// this diff isn't actually a property diff, its for missing objects, etc
|
|
FPropertyComparison MissingObjectPropDiff;
|
|
|
|
// if we are missing both a and b, we know that we had an ancestor (otherwise, we early out of this function)
|
|
if (!ObjA && !ObjB)
|
|
{
|
|
FString FullPath = ObjAncestor->GetPathName(Packages[2]) + TEXT(" [") + ObjAncestor->GetClass()->GetName() + TEXT("]");
|
|
|
|
MissingObjectPropDiff.DiffType = OD_ABSame;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Removed %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
// if we are missing object a (we know we have ObjB)
|
|
else if (!ObjA)
|
|
{
|
|
FString FullPath = ObjB->GetPathName(Packages[1]) + TEXT(" [") + ObjB->GetClass()->GetName() + TEXT("]");
|
|
|
|
// if we were have an ancestor package, compare to ancestor
|
|
if (Packages[2])
|
|
{
|
|
// if we have an ancestor
|
|
if (ObjAncestor)
|
|
{
|
|
// if B wasn't different from the ancestor, then we were just deleted from A
|
|
if (OverallDiffType == OD_None)
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_AOnly;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Removed %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
// if B was different from Ancestor, then we were deleted from A and changed in B, conflict!
|
|
else
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_ABConflict;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Removed/Modified %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
}
|
|
// otherwise, if we have an ancestor package, but no ancestor, then is _added_ to B
|
|
else
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_BOnly;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
}
|
|
// if no ancestor package, then we don't know if the object was added or deleted, so mark it as a conflict
|
|
else
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_BOnly;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
}
|
|
// if we are missing object B (we know we have ObjA)
|
|
else if (!ObjB)
|
|
{
|
|
FString FullPath = ObjA->GetPathName(Packages[0]) + TEXT(" [") + ObjA->GetClass()->GetName() + TEXT("]");
|
|
|
|
// if we were have an ancestor package, compare to ancestor
|
|
if (Packages[2])
|
|
{
|
|
// if we have an ancestor
|
|
if (ObjAncestor)
|
|
{
|
|
// if A wasn't different from the ancestor, then we were just deleted from B
|
|
if (OverallDiffType == OD_None)
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_BOnly;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Removed %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
// if A was different from Ancestor, then we were deleted from B and changed in A, conflict!
|
|
else
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_ABConflict;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Modified/Removed %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
}
|
|
// otherwise, if we have an ancestor package, but no ancestor, then is _added_ to A
|
|
else
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_AOnly;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
}
|
|
// if no ancestor package, then we don't know if the object was added or deleted, so mark it as a conflict
|
|
else
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_AOnly;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Removed %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
}
|
|
|
|
// look for objects added to both, but only if an ancestor package was specified
|
|
// ObjAncestor is NULL if no ancestor package was specified or the object didn't exist in the ancestor package, so check for NumPackages == 3
|
|
else if (ObjA && ObjB && !ObjAncestor && NumPackages == 3)
|
|
{
|
|
FString FullPath = ObjA->GetPathName(Packages[0]) + TEXT(" [") + ObjA->GetClass()->GetName() + TEXT("]");
|
|
if (OverallDiffType == OD_None)
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_ABSame;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
else
|
|
{
|
|
MissingObjectPropDiff.DiffType = OD_ABConflict;
|
|
MissingObjectPropDiff.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(MissingObjectPropDiff.DiffType, NumPackages), *FullPath);
|
|
}
|
|
}
|
|
|
|
if (MissingObjectPropDiff.DiffType != OD_None)
|
|
{
|
|
// add this diff to the list of diffs
|
|
Diff.PropDiffs.Add(MissingObjectPropDiff);
|
|
|
|
if (OverallDiffType == OD_None)
|
|
{
|
|
OverallDiffType = MissingObjectPropDiff.DiffType;
|
|
}
|
|
// @todo: accumulate better into overalldifftype?
|
|
}
|
|
|
|
return OverallDiffType;
|
|
}
|
|
|
|
namespace DiffPackagesCommandletImpl
|
|
{
|
|
|
|
void LoadNativePropertyData(UObject* Object, TArray<uint8>& out_NativePropertyData)
|
|
{
|
|
// first, validate our input parameters
|
|
check(Object);
|
|
|
|
auto ObjectLinker = Object->GetLinker();
|
|
check(ObjectLinker);
|
|
|
|
int32 ObjectLinkerIndex = Object->GetLinkerIndex();
|
|
check(ObjectLinker->ExportMap.IsValidIndex(ObjectLinkerIndex));
|
|
|
|
// now begin the process of loading the data for this object's natively serialized properties into the memory archive
|
|
out_NativePropertyData.Empty();
|
|
|
|
FObjectExport& ObjectExport = ObjectLinker->ExportMap[ObjectLinkerIndex];
|
|
const int64 ScriptStartPos = ObjectExport.ScriptSerializationStartOffset;
|
|
const int64 ScriptEndPos = ObjectExport.ScriptSerializationEndOffset;
|
|
|
|
const int64 NativeStartPos = ScriptEndPos;
|
|
const int64 NativeEndPos = ObjectExport.SerialOffset + ObjectExport.SerialSize;
|
|
|
|
const int64 NativePropertySerialSize = NativeEndPos - NativeStartPos;
|
|
if (NativePropertySerialSize > 0)
|
|
{
|
|
checkSlow(NativeStartPos >= ObjectExport.SerialOffset);
|
|
checkSlow(NativeStartPos < NativeEndPos);
|
|
// but this might not be the case - need to make sure we catch any native data that is serialized before the property data
|
|
const int64 SavedPos = ((FArchive*)ObjectLinker)->Tell();
|
|
|
|
((FArchive*)ObjectLinker)->Seek(NativeStartPos);
|
|
((FArchive*)ObjectLinker)->Precache(NativeStartPos, NativePropertySerialSize);
|
|
|
|
// allocate enough space to contain the data we're about to read from disk
|
|
out_NativePropertyData.AddZeroed(NativePropertySerialSize);
|
|
((FArchive*)ObjectLinker)->Serialize(out_NativePropertyData.GetData(), NativePropertySerialSize);
|
|
|
|
// return the linker to its previous position
|
|
((FArchive*)ObjectLinker)->Seek(SavedPos);
|
|
}
|
|
}
|
|
|
|
} // namespace DiffPackagesCommandletImpl
|
|
|
|
EObjectDiff UDEPRECATED_DiffPackagesCommandlet::CompareNativePropertyValues(UObject* ObjA, UObject* ObjB, UObject* ObjAncestor, FObjectComparison& PropertyValueComparisons)
|
|
{
|
|
FNativePropertyData PropertyDataA(ObjA), PropertyDataB(ObjB), PropertyDataAncestor(ObjAncestor);
|
|
|
|
FPropertyComparison NativeDataComparison;
|
|
NativeDataComparison.PropText = TEXT("Native Properties");
|
|
NativeDataComparison.DiffType = OD_None;
|
|
|
|
if (ObjA == NULL)
|
|
{
|
|
check(ObjB);
|
|
check(ObjAncestor);
|
|
|
|
FString ObjectPathName = ObjB->GetPathName(Packages[1]);
|
|
if (PropertyDataAncestor)
|
|
{
|
|
// If the values in ObjB are identical to the values in the common ancestor, then the object was removed from the first package
|
|
if (PropertyDataB == PropertyDataAncestor)
|
|
{
|
|
NativeDataComparison.DiffType = OD_AOnly;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Removed %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
else
|
|
{
|
|
// if the values in ObjB are different from the values in the common ancestor, then the object was removed from the first package
|
|
// but changed in the second package, which is a conflict
|
|
NativeDataComparison.DiffType = OD_ABConflict;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Removed/Modified %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NativeDataComparison.DiffType = OD_BOnly;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
}
|
|
else if (ObjB == NULL)
|
|
{
|
|
check(ObjA);
|
|
check(ObjAncestor);
|
|
|
|
FString ObjectPathName = ObjA->GetPathName(Packages[0]);
|
|
if (PropertyDataAncestor)
|
|
{
|
|
// If the values in ObjA are identical to the values in the common ancestor, then the object was removed from the second package
|
|
if (PropertyDataB == PropertyDataAncestor)
|
|
{
|
|
NativeDataComparison.DiffType = OD_BOnly;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Removed %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
else
|
|
{
|
|
// if the values in ObjA are different from the values in the common ancestor, then the object was removed from the second package
|
|
// but changed in the first package, which is a conflict
|
|
NativeDataComparison.DiffType = OD_ABConflict;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Removed/Modified %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NativeDataComparison.DiffType = OD_AOnly;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
}
|
|
// look for objects added to both, but only if an ancestor package was specified
|
|
// ObjAncestor is NULL if no ancestor package was specified or the object didn't exist in the ancestor package, so check for NumPackages == 3
|
|
else if (ObjA && ObjB && !ObjAncestor && NumPackages == 3)
|
|
{
|
|
FString ObjectPathName = ObjA->GetPathName(Packages[0]) + TEXT(" [") + ObjA->GetClass()->GetName() + TEXT("]");
|
|
if (PropertyDataA == PropertyDataB)
|
|
{
|
|
NativeDataComparison.DiffType = OD_ABSame;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
else
|
|
{
|
|
NativeDataComparison.DiffType = OD_ABConflict;
|
|
NativeDataComparison.DiffText += FString::Printf(TEXT("(%s) Added %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// first, check to see if both packages were changed to the same value
|
|
check(ObjA && ObjB);
|
|
if (PropertyDataA && PropertyDataA == PropertyDataB)
|
|
{
|
|
// if we have an ancestor and its data is different than the data from ObjA, then both packages were changed to the same value
|
|
if (ObjAncestor && PropertyDataA != PropertyDataAncestor)
|
|
{
|
|
FString ObjectPathName = ObjA->GetFullName(Packages[0]);
|
|
// NativePropertyComparison.DiffType = OD_ABSame;
|
|
//
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType,NumPackages), *FullPath, *PropName));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Now: %s"), *PropTextA));
|
|
if (NativeDataComparison.DiffType == OD_None) //-V547
|
|
{
|
|
NativeDataComparison.DiffType = OD_ABSame;
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, FString::Printf(TEXT("(%s) %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName));
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, TEXT(" Unknown native property data"));
|
|
}
|
|
}
|
|
|
|
// otherwise, if there is no ancestor object or ObjA's value is identical to the ancestor's value, then all three packages
|
|
// have the same values for this object
|
|
}
|
|
else
|
|
{
|
|
// if A and B are different, we need to compare against the common ancestor (if we have one)
|
|
if (ObjAncestor)
|
|
{
|
|
FString ObjectPathName = ObjAncestor->GetFullName(Packages[2]);
|
|
|
|
// if the values from ObjA are identical to the values in the common base, then ObjB was changed
|
|
if (PropertyDataA && PropertyDataA == PropertyDataAncestor)
|
|
{
|
|
// CurrentPropertyComparison.DiffType = OD_BOnly;
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType,NumPackages), *FullPath, *PropName));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Now: %s"), *PropTextB));
|
|
|
|
if (NativeDataComparison.DiffType == OD_None) //-V547
|
|
{
|
|
NativeDataComparison.DiffType = OD_BOnly;
|
|
}
|
|
else if (NativeDataComparison.DiffType != OD_BOnly)
|
|
{
|
|
NativeDataComparison.DiffType = OD_ABConflict;
|
|
}
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, FString::Printf(TEXT("(%s) %s"), GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName));
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, TEXT(" Unknown native property data"));
|
|
}
|
|
// Otherwise, if the values from ObjB are identical to the values in the common base, then only ObjA was changed
|
|
else if (PropertyDataB && PropertyDataB == PropertyDataAncestor)
|
|
{
|
|
// CurrentPropertyComparison.DiffType = OD_AOnly;
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType,NumPackages), *FullPath, *PropName));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Now: %s"), *PropTextA));
|
|
|
|
if (NativeDataComparison.DiffType == OD_None) //-V547
|
|
{
|
|
NativeDataComparison.DiffType = OD_AOnly;
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, FString::Printf(TEXT("(%s) %s"),
|
|
GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName));
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, TEXT(" Unknown native property data"));
|
|
}
|
|
else if (NativeDataComparison.DiffType != OD_AOnly)
|
|
{
|
|
NativeDataComparison.DiffType = OD_ABConflict;
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, FString::Printf(TEXT("(%s) %s"),
|
|
GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName));
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, TEXT(" Unknown native property data"));
|
|
}
|
|
}
|
|
// Otherwise, the values from ObjA and ObjB are different from each other as well as from the values in the common base
|
|
else if (PropertyDataA && PropertyDataB)
|
|
{
|
|
// CurrentPropertyComparison.DiffType = OD_ABConflict;
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType,NumPackages), *FullPath, *PropName));
|
|
//
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" Was: %s"), *PropTextAncestor));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[0], *PropTextA));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[1], *PropTextB));
|
|
|
|
NativeDataComparison.DiffType = OD_ABConflict;
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, FString::Printf(TEXT("(%s) %s"),
|
|
GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName));
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, TEXT(" Unknown native property data"));
|
|
}
|
|
}
|
|
// If we have no common base and the values from ObjA & ObjB are different, mark it as a conflict
|
|
else if (PropertyDataA && PropertyDataB)
|
|
{
|
|
FString ObjectPathName = ObjA->GetFullName(Packages[0]);
|
|
// CurrentPropertyComparison.DiffType = OD_ABConflict;
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT("(%s) %s::%s"), GetDiffTypeText(PropDiff.DiffType,NumPackages), *ObjectPathName, *PropName));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[0], *PropTextA));
|
|
// AppendComparisonResultText(PropDiff.DiffText, FString::Printf(TEXT(" %s: %s"), *PackageFilenames[1], *PropTextB));
|
|
|
|
// accumulate diff types
|
|
NativeDataComparison.DiffType = OD_ABConflict;
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, FString::Printf(TEXT("(%s) %s"),
|
|
GetDiffTypeText(NativeDataComparison.DiffType, NumPackages), *ObjectPathName));
|
|
AppendComparisonResultText(NativeDataComparison.DiffText, TEXT(" Unknown native property data"));
|
|
}
|
|
}
|
|
|
|
// // if we actually had a diff, add it to the list
|
|
// if (PropDiff.DiffType != OD_None)
|
|
// {
|
|
// Diff.PropDiffs.AddItem(PropDiff);
|
|
// }
|
|
}
|
|
|
|
// If we had any type of difference between property values for this object, add an entry to the object comparison to indicate that
|
|
// there were differences in the property data for this object's natively serialized properties
|
|
if (NativeDataComparison.DiffType != OD_None)
|
|
{
|
|
PropertyValueComparisons.PropDiffs.Add(NativeDataComparison);
|
|
|
|
// If PropertyValueComparisons.OverallDiffType is still OD_None, it means that the values for this object's script-serialized properties
|
|
// were identical across all packages. If we encountered differences in the native property data, update the object comparison's OverallDiffType so
|
|
// that the differences are reported
|
|
if (PropertyValueComparisons.OverallDiffType == OD_None)
|
|
{
|
|
PropertyValueComparisons.OverallDiffType = NativeDataComparison.DiffType;
|
|
}
|
|
}
|
|
|
|
return NativeDataComparison.DiffType;
|
|
}
|