Skip to content

Commit

Permalink
Add initial framework for experimental three way merge (#188)
Browse files Browse the repository at this point in the history
* Start smartmerge

* Testable

* paths

* foo

* Apply add

* control deltas addremove

* Merging!

* Test fix

* Clear entropy on merge

* start clone

* clone

* Remove rename logic

* Cleanup

* Copyright header, warning in Program.cs

* PR Feedback, IClonable, moved ControlPath
  • Loading branch information
lesaltzm authored Mar 18, 2021
1 parent 4efef13 commit 503a6f8
Show file tree
Hide file tree
Showing 18 changed files with 798 additions and 10 deletions.
50 changes: 48 additions & 2 deletions src/PAModel/CanvasDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public class CanvasDocument
// Save for roundtripping.
internal Entropy _entropy = new Entropy();

// checksum from existin msapp.
// checksum from existing msapp.
internal ChecksumJson _checksum;

// Track all asset files, key is file name
Expand Down Expand Up @@ -197,13 +197,59 @@ private static void Wrapper(Action worker, ErrorContainer errors)
}
}
}

internal CanvasDocument()
{
_editorStateStore = new EditorStateStore();
_templateStore = new TemplateStore();
}

internal CanvasDocument(CanvasDocument other)
{
foreach (var kvp in other._unknownFiles)
{
_unknownFiles.Add(kvp.Key, new FileEntry(kvp.Value));
}

foreach (var kvp in other._assetFiles)
{
_assetFiles.Add(kvp.Key, new FileEntry(kvp.Value));
}

foreach (var kvp in other._screens)
{
_screens.Add(kvp.Key, kvp.Value.Clone());
}

foreach (var kvp in other._components)
{
_components.Add(kvp.Key, kvp.Value.Clone());
}

_editorStateStore = new EditorStateStore(other._editorStateStore);
_templateStore = new TemplateStore(other._templateStore);

_dataSources = other._dataSources.JsonClone();
_screenOrder = new List<string>(other._screenOrder);

_header = other._header.JsonClone();
_properties = other._properties.JsonClone();
_publishInfo = other._publishInfo.JsonClone();
_templates = other._templates.JsonClone();
_themes = other._themes.JsonClone();
_resourcesJson = other._resourcesJson.JsonClone();
_appCheckerResultJson = other._appCheckerResultJson.JsonClone();

_connections = other._connections.JsonClone();

_dataSourceReferences = other._dataSourceReferences.JsonClone();
_libraryReferences = other._libraryReferences.JsonClone();

_logoFile = new FileEntry(other._logoFile);
_entropy = other._entropy.JsonClone();
_checksum = other._checksum.JsonClone();
}

// iOrder is used to preserve ordering value for round-tripping.
internal void AddDataSourceForLoad(DataSourceEntry ds, int? order = null)
{
Expand Down
10 changes: 10 additions & 0 deletions src/PAModel/EditorState/EditorStateStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ public EditorStateStore()
_controls = new Dictionary<string, ControlState>(StringComparer.Ordinal);
}

public EditorStateStore(EditorStateStore other)
{
_controls = other._controls.JsonClone();
}

public bool ContainsControl(string name)
{
return _controls.ContainsKey(name);
}

public bool TryAddControl(ControlState control)
{
if (_controls.ContainsKey(control.Name))
Expand Down
5 changes: 5 additions & 0 deletions src/PAModel/EditorState/TemplateStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public TemplateStore()
Contents = new Dictionary<string, CombinedTemplateState>();
}

public TemplateStore(TemplateStore other)
{
Contents = other.Contents.JsonClone();
}

public bool AddTemplate(string name, CombinedTemplateState template)
{
if (Contents.ContainsKey(name))
Expand Down
81 changes: 74 additions & 7 deletions src/PAModel/IR/IRNode.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.PowerPlatform.Formulas.Tools.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Microsoft.PowerPlatform.Formulas.Tools.IR
Expand All @@ -22,7 +24,7 @@ internal abstract class IRNode
/// Represents block construct, may have 0-N child blocks and 0-N properties
/// </summary>
[DebuggerDisplay("{Name}: {Properties.Count} props")]
internal class BlockNode : IRNode
internal class BlockNode : IRNode, ICloneable<BlockNode>
{
public TypedNameNode Name;
public IList<PropertyNode> Properties = new List<PropertyNode>();
Expand All @@ -33,6 +35,17 @@ public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context con
{
visitor.Visit(this, context);
}

public BlockNode Clone()
{
return new BlockNode()
{
Name = Name.Clone(),
Properties = Properties.Clone(),
Functions = Functions.Clone(),
Children = Children.Clone(),
};
}
}

/// <summary>
Expand All @@ -44,7 +57,7 @@ public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context con
/// Kind = label
/// </summary>
[DebuggerDisplay("{Identifier} as {Kind}")]
internal class TypedNameNode : IRNode
internal class TypedNameNode : IRNode, ICloneable<TypedNameNode>
{
public string Identifier;

Expand All @@ -57,13 +70,22 @@ public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context con
{
visitor.Visit(this, context);
}

public TypedNameNode Clone()
{
return new TypedNameNode()
{
Identifier = Identifier,
Kind = Kind.Clone()
};
}
}

/// <summary>
/// Represents a template like `label` or `gallery.HorizontalGallery`
/// </summary>
[DebuggerDisplay("{TypeName}.{OptionalVariant}")]
internal class TypeNode : IRNode
internal class TypeNode : IRNode, ICloneable<TypeNode>
{
public string TypeName;
public string OptionalVariant;
Expand All @@ -72,10 +94,19 @@ public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context con
{
visitor.Visit(this, context);
}

public TypeNode Clone()
{
return new TypeNode()
{
TypeName = TypeName,
OptionalVariant = OptionalVariant
};
}
}

[DebuggerDisplay("{Identifier}: ={Expression}")]
internal class PropertyNode : IRNode
internal class PropertyNode : IRNode, ICloneable<PropertyNode>
{
public string Identifier;
public ExpressionNode Expression;
Expand All @@ -84,10 +115,19 @@ public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context con
{
visitor.Visit(this, context);
}

public PropertyNode Clone()
{
return new PropertyNode()
{
Identifier = Identifier,
Expression = Expression.Clone()
};
}
}

[DebuggerDisplay("{Identifier}({string.Join(',', Args)}):")]
internal class FunctionNode : IRNode
internal class FunctionNode : IRNode, ICloneable<FunctionNode>
{
public string Identifier;
public IList<TypedNameNode> Args = new List<TypedNameNode>();
Expand All @@ -97,10 +137,20 @@ public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context con
{
visitor.Visit(this, context);
}

public FunctionNode Clone()
{
return new FunctionNode()
{
Identifier = Identifier,
Args = Args.Clone(),
Metadata = Metadata.Clone(),
};
}
}

[DebuggerDisplay("{Identifier}: ={Default}")]
internal class ArgMetadataBlockNode : IRNode
internal class ArgMetadataBlockNode : IRNode, ICloneable<ArgMetadataBlockNode>
{
public string Identifier;
public ExpressionNode Default;
Expand All @@ -109,15 +159,32 @@ public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context con
{
visitor.Visit(this, context);
}

public ArgMetadataBlockNode Clone()
{
return new ArgMetadataBlockNode()
{
Identifier = Identifier,
Default = Default.Clone(),
};
}
}

[DebuggerDisplay("{Expression}")]
internal class ExpressionNode : IRNode
internal class ExpressionNode : IRNode, ICloneable<ExpressionNode>
{
public string Expression;
public override void Accept<Context>(IRNodeVisitor<Context> visitor, Context context)
{
visitor.Visit(this, context);
}

public ExpressionNode Clone()
{
return new ExpressionNode()
{
Expression = Expression
};
}
}
}
38 changes: 38 additions & 0 deletions src/PAModel/IR/IRNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,42 @@ internal abstract class IRNodeVisitor<Context>
public abstract void Visit(ExpressionNode node, Context context);
public abstract void Visit(ArgMetadataBlockNode node, Context context);
}

internal class DefaultVisitor<Context> : IRNodeVisitor<Context>
{
public override void Visit(BlockNode node, Context context)
{
throw new NotImplementedException();
}

public override void Visit(TypedNameNode node, Context context)
{
throw new NotImplementedException();
}

public override void Visit(TypeNode node, Context context)
{
throw new NotImplementedException();
}

public override void Visit(PropertyNode node, Context context)
{
throw new NotImplementedException();
}

public override void Visit(FunctionNode node, Context context)
{
throw new NotImplementedException();
}

public override void Visit(ExpressionNode node, Context context)
{
throw new NotImplementedException();
}

public override void Visit(ArgMetadataBlockNode node, Context context)
{
throw new NotImplementedException();
}
}
}
61 changes: 61 additions & 0 deletions src/PAModel/MergeTool/CanvasMerger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.PowerPlatform.Formulas.Tools.MergeTool;
using Microsoft.PowerPlatform.Formulas.Tools.MergeTool.Deltas;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.PowerPlatform.Formulas.Tools
{
public static class CanvasMerger
{
public static CanvasDocument Merge(CanvasDocument ours, CanvasDocument theirs, CanvasDocument commonParent)
{
var ourDeltas = Diff.ComputeDelta(commonParent, ours);
var theirDeltas = Diff.ComputeDelta(commonParent, theirs);

var resultDelta = UnionDelta(ourDeltas, theirDeltas);

return ApplyDelta(commonParent, resultDelta);
}

// fix these to be hashsets eventually?
private static IEnumerable<IDelta> UnionDelta(IEnumerable<IDelta> ours, IEnumerable<IDelta> theirs)
{
var resultDeltas = new List<IDelta>();

// If we apply the removes before the adds, adds will fail if they're in a tree that's been removed
// This is intended but we should talk about it
resultDeltas.AddRange(ours.OfType<RemoveControl>());
resultDeltas.AddRange(theirs.OfType<RemoveControl>());

resultDeltas.AddRange(ours.OfType<AddControl>());
resultDeltas.AddRange(theirs.OfType<AddControl>());

var ourPropChanges = ours.OfType<ChangeProperty>();
var theirPropChanges = theirs.OfType<ChangeProperty>();
resultDeltas.AddRange(ourPropChanges);
resultDeltas.AddRange(theirPropChanges.Where(change => !ourPropChanges.Any(ourChange => ourChange.PropertyName == change.PropertyName && ourChange.ControlPath.Equals(change.ControlPath))));
return resultDeltas;
}


private static CanvasDocument ApplyDelta(CanvasDocument parent, IEnumerable<IDelta> delta)
{
var result = new CanvasDocument(parent);
foreach (var change in delta)
{
change.Apply(result);
}

if (delta.Any())
{
result._entropy = new Entropy();
}
return result;
}
}
}
Loading

0 comments on commit 503a6f8

Please sign in to comment.