Simplify migrations
This commit is contained in:
89
src/Umbraco.Core/Migrations/MergeBuilder.cs
Normal file
89
src/Umbraco.Core/Migrations/MergeBuilder.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a migration plan builder for merges.
|
||||
/// </summary>
|
||||
public class MergeBuilder
|
||||
{
|
||||
private readonly MigrationPlan _plan;
|
||||
private readonly List<Type> _migrations = new List<Type>();
|
||||
private string _withLast;
|
||||
private bool _with;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MergeBuilder"/> class.
|
||||
/// </summary>
|
||||
internal MergeBuilder(MigrationPlan plan)
|
||||
{
|
||||
_plan = plan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a transition to a target state through an empty migration.
|
||||
/// </summary>
|
||||
public MergeBuilder To(string targetState)
|
||||
=> To<NoopMigration>(targetState);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a transition to a target state through a migration.
|
||||
/// </summary>
|
||||
public MergeBuilder To<TMigration>(string targetState)
|
||||
where TMigration : IMigration
|
||||
=> To(targetState, typeof(TMigration));
|
||||
|
||||
/// <summary>
|
||||
/// Adds a transition to a target state through a migration.
|
||||
/// </summary>
|
||||
public MergeBuilder To(string targetState, Type migration)
|
||||
{
|
||||
if (_with)
|
||||
{
|
||||
_withLast = targetState;
|
||||
targetState = _plan.CreateRandomState();
|
||||
}
|
||||
else
|
||||
{
|
||||
_migrations.Add(migration);
|
||||
}
|
||||
|
||||
_plan.To(targetState, migration);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins the second branch of the merge.
|
||||
/// </summary>
|
||||
public MergeBuilder With()
|
||||
{
|
||||
if (_with)
|
||||
throw new InvalidOperationException("Cannot invoke With() twice.");
|
||||
_with = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the merge.
|
||||
/// </summary>
|
||||
public MigrationPlan As(string targetState)
|
||||
{
|
||||
if (!_with)
|
||||
throw new InvalidOperationException("Cannot invoke As() without invoking With() first.");
|
||||
|
||||
// reach final state
|
||||
_plan.To(targetState);
|
||||
|
||||
// restart at former end of branch2
|
||||
_plan.From(_withLast);
|
||||
// and replay all branch1 migrations
|
||||
foreach (var migration in _migrations)
|
||||
_plan.To(_plan.CreateRandomState(), migration);
|
||||
// reaching final state
|
||||
_plan.To(targetState);
|
||||
|
||||
return _plan;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,8 @@ namespace Umbraco.Core.Migrations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a transition to a target state through an empty migration.
|
||||
/// </summary>
|
||||
/// Adds a transition to a target state through an empty migration.
|
||||
/// </summary>
|
||||
public MigrationPlan To(string targetState)
|
||||
=> To<NoopMigration>(targetState);
|
||||
|
||||
@@ -100,9 +100,39 @@ namespace Umbraco.Core.Migrations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds transitions to a target state by copying transitions from a start state to an end state.
|
||||
/// Adds a transition to a target state through a migration, replacing a previous migration.
|
||||
/// </summary>
|
||||
public MigrationPlan To(string targetState, string startState, string endState)
|
||||
/// <typeparam name="TMigrationNew">The new migration.</typeparam>
|
||||
/// <typeparam name="TMigrationRecover">The migration to use to recover from the previous target state.</typeparam>
|
||||
/// <param name="recoverState">The previous target state, which we need to recover from through <typeparamref name="TMigrationRecover"/>.</param>
|
||||
/// <param name="targetState">The new target state.</param>
|
||||
public MigrationPlan ToWithReplace<TMigrationNew, TMigrationRecover>(string recoverState, string targetState)
|
||||
where TMigrationNew: IMigration
|
||||
where TMigrationRecover : IMigration
|
||||
{
|
||||
To<TMigrationNew>(targetState);
|
||||
From(recoverState).To<TMigrationRecover>(targetState);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a transition to a target state through a migration, replacing a previous migration.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMigrationNew">The new migration.</typeparam>
|
||||
/// <param name="recoverState">The previous target state, which we can recover from directly.</param>
|
||||
/// <param name="targetState">The new target state.</param>
|
||||
public MigrationPlan ToWithReplace<TMigrationNew>(string recoverState, string targetState)
|
||||
where TMigrationNew : IMigration
|
||||
{
|
||||
To<TMigrationNew>(targetState);
|
||||
From(recoverState).To(targetState);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds transitions to a target state by cloning transitions from a start state to an end state.
|
||||
/// </summary>
|
||||
public MigrationPlan ToWithClone(string startState, string endState, string targetState)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(startState)) throw new ArgumentNullOrEmptyException(nameof(startState));
|
||||
if (string.IsNullOrWhiteSpace(endState)) throw new ArgumentNullOrEmptyException(nameof(endState));
|
||||
@@ -128,7 +158,7 @@ namespace Umbraco.Core.Migrations
|
||||
|
||||
var newTargetState = transition.TargetState == endState
|
||||
? targetState
|
||||
: Guid.NewGuid().ToString("B").ToUpper();
|
||||
: CreateRandomState();
|
||||
To(newTargetState, transition.MigrationType);
|
||||
state = transition.TargetState;
|
||||
}
|
||||
@@ -136,6 +166,17 @@ namespace Umbraco.Core.Migrations
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a random, unique state.
|
||||
/// </summary>
|
||||
public virtual string CreateRandomState()
|
||||
=> Guid.NewGuid().ToString("B").ToUpper();
|
||||
|
||||
/// <summary>
|
||||
/// Begins a merge.
|
||||
/// </summary>
|
||||
public MergeBuilder Merge() => new MergeBuilder(this);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initial state.
|
||||
/// </summary>
|
||||
@@ -257,13 +298,14 @@ namespace Umbraco.Core.Migrations
|
||||
/// Follows a path (for tests and debugging).
|
||||
/// </summary>
|
||||
/// <remarks>Does the same thing Execute does, but does not actually execute migrations.</remarks>
|
||||
internal string FollowPath(string fromState, string toState = null)
|
||||
internal IReadOnlyList<string> FollowPath(string fromState = null, string toState = null)
|
||||
{
|
||||
toState = toState.NullOrWhiteSpaceAsNull();
|
||||
|
||||
Validate();
|
||||
|
||||
var origState = fromState ?? string.Empty;
|
||||
var states = new List<string> { origState };
|
||||
|
||||
if (!_transitions.TryGetValue(origState, out var transition))
|
||||
throw new Exception($"Unknown state \"{origState}\".");
|
||||
@@ -272,6 +314,7 @@ namespace Umbraco.Core.Migrations
|
||||
{
|
||||
var nextState = transition.TargetState;
|
||||
origState = nextState;
|
||||
states.Add(origState);
|
||||
|
||||
if (nextState == toState)
|
||||
{
|
||||
@@ -287,7 +330,7 @@ namespace Umbraco.Core.Migrations
|
||||
if (origState != (toState ?? _finalState))
|
||||
throw new Exception($"Internal error, reached state {origState} which is not state {toState ?? _finalState}");
|
||||
|
||||
return origState;
|
||||
return states;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -89,55 +89,27 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
To<SuperZero>("{9DF05B77-11D1-475C-A00A-B656AF7E0908}");
|
||||
To<PropertyEditorsMigration>("{6FE3EF34-44A0-4992-B379-B40BC4EF1C4D}");
|
||||
To<LanguageColumns>("{7F59355A-0EC9-4438-8157-EB517E6D2727}");
|
||||
//To<AddVariationTables1>("{941B2ABA-2D06-4E04-81F5-74224F1DB037}"); // AddVariationTables1 has been superseded by AddVariationTables2
|
||||
To<AddVariationTables2>("{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}");
|
||||
|
||||
// way out of the commented state
|
||||
From("{941B2ABA-2D06-4E04-81F5-74224F1DB037}");
|
||||
To<AddVariationTables1A>("{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}");
|
||||
// resume at {76DF5CD7-A884-41A5-8DC6-7860D95B1DF5} ...
|
||||
|
||||
ToWithReplace<AddVariationTables2, AddVariationTables1A>("{941B2ABA-2D06-4E04-81F5-74224F1DB037}", "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}"); // kill AddVariationTable1
|
||||
To<RefactorMacroColumns>("{A7540C58-171D-462A-91C5-7A9AA5CB8BFD}");
|
||||
To<UserForeignKeys>("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}"); // shannon added that one - let's keep it as the default path
|
||||
//To<AddTypedLabels>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one = merge conflict, remove
|
||||
To<AddTypedLabels>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); // but add it after shannon's, with a new target state
|
||||
|
||||
// way out of the commented state
|
||||
From("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}");
|
||||
To<UserForeignKeys>("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}");
|
||||
// resume at {4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4} ...
|
||||
Merge().To<UserForeignKeys>("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}") // shannon added that one
|
||||
.With().To<AddTypedLabels>("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}") // stephan added that one
|
||||
.As("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}");
|
||||
|
||||
To<ContentVariationMigration>("{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
To<UpdateUmbracoConsent>("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0
|
||||
//To<FallbackLanguage>("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); // andy added that one = merge conflict, remove
|
||||
To<AddRelationTypeForMediaFolderOnDelete>("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}"); // from 7.12.0
|
||||
To<IncreaseLanguageIsoCodeColumnLength>("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0
|
||||
To<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
|
||||
To<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
|
||||
//To<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); // stephan added that one = merge conflict, remove
|
||||
|
||||
To<FallbackLanguage>("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); // add andy's after others, with a new target state
|
||||
|
||||
// way out of andy's
|
||||
From("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}");
|
||||
To("{8B14CEBD-EE47-4AAD-A841-93551D917F11}", "{39E5B1F7-A50B-437E-B768-1723AEC45B65}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}");
|
||||
// resume at {8B14CEBD-EE47-4AAD-A841-93551D917F11} ...
|
||||
|
||||
To<UpdateDefaultMandatoryLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // add stephan's after others, with a new target state
|
||||
|
||||
// way out of the commented state
|
||||
From("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}");
|
||||
To<FallbackLanguage>("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // to next
|
||||
// resume at {5F4597F4-A4E0-4AFE-90B5-6D2F896830EB} ...
|
||||
|
||||
//To<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
To<RefactorVariantsModel>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
|
||||
// way out of the commented state
|
||||
From("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
|
||||
To<FallbackLanguage>("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}");
|
||||
// resume at {290C18EE-B3DE-4769-84F1-1F467F3F76DA}...
|
||||
Merge()
|
||||
.To<AddRelationTypeForMediaFolderOnDelete>("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}") // from 7.12.0
|
||||
.To<IncreaseLanguageIsoCodeColumnLength>("{EB34B5DC-BB87-4005-985E-D983EA496C38}") // from 7.12.0
|
||||
.To<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}") // from 7.12.0
|
||||
.To<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}") // from 7.12.0
|
||||
.With()
|
||||
.To<FallbackLanguage>("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // andy added that one
|
||||
.As("{8B14CEBD-EE47-4AAD-A841-93551D917F11}");
|
||||
|
||||
ToWithReplace<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}", "{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // merge
|
||||
ToWithReplace<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}", "{290C18EE-B3DE-4769-84F1-1F467F3F76DA}"); // merge
|
||||
To<DropTaskTables>("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}");
|
||||
To<FixLockTablePrimaryKey>("{77874C77-93E5-4488-A404-A630907CEEF0}");
|
||||
To<AddLogTableColumns>("{8804D8E8-FE62-4E3A-B8A2-C047C2118C38}");
|
||||
@@ -159,16 +131,22 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
From("{init-7.10.2}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.3}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.4}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.10.5}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.11.0}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.11.1}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
From("{init-7.11.2}").To("{init-7.10.0}"); // same as 7.10.0
|
||||
|
||||
// 7.12.0 has migrations, define a custom chain which copies the chain
|
||||
// going from {init-7.10.0} to former final (1350617A) , and then goes straight to
|
||||
// main chain, skipping the migrations
|
||||
//
|
||||
From("{init-7.12.0}");
|
||||
// target copy from copy to (former final)
|
||||
To("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}", "{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
// start stop target
|
||||
ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}");
|
||||
|
||||
From("{init-7.12.1}").To("{init-7.10.0}"); // same as 7.12.0
|
||||
From("{init-7.12.2}").To("{init-7.10.0}"); // same as 7.12.0
|
||||
From("{init-7.12.3}").To("{init-7.10.0}"); // same as 7.12.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +339,7 @@
|
||||
<Compile Include="Manifest\ManifestContentAppFactory.cs" />
|
||||
<Compile Include="Manifest\ManifestDashboardDefinition.cs" />
|
||||
<Compile Include="Migrations\IncompleteMigrationExpressionException.cs" />
|
||||
<Compile Include="Migrations\MergeBuilder.cs" />
|
||||
<Compile Include="Migrations\MigrationBase_Extra.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_7_10_0\RenamePreviewFolder.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_7_12_0\AddRelationTypeForMediaFolderOnDelete.cs" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NPoco;
|
||||
@@ -68,7 +69,6 @@ namespace Umbraco.Tests.Migrations
|
||||
// save new state
|
||||
kvs.SetValue("Umbraco.Tests.MigrationPlan", sourceState, state);
|
||||
|
||||
// fixme - what about post-migrations?
|
||||
s.Complete();
|
||||
}
|
||||
|
||||
@@ -140,11 +140,11 @@ namespace Umbraco.Tests.Migrations
|
||||
var plan = new UmbracoPlan();
|
||||
plan.Validate();
|
||||
Console.WriteLine(plan.FinalState);
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(plan.FinalState));
|
||||
Assert.IsFalse(plan.FinalState.IsNullOrWhiteSpace());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanCopyChain()
|
||||
public void CanClone()
|
||||
{
|
||||
var plan = new MigrationPlan("default");
|
||||
plan
|
||||
@@ -157,14 +157,46 @@ namespace Umbraco.Tests.Migrations
|
||||
|
||||
plan
|
||||
.From("xxx")
|
||||
.To("yyy", "bbb", "ddd")
|
||||
.ToWithClone("bbb", "ddd", "yyy")
|
||||
.To("eee");
|
||||
|
||||
WritePlanToConsole(plan);
|
||||
|
||||
plan.Validate();
|
||||
Assert.AreEqual("eee", plan.FollowPath("xxx"));
|
||||
Assert.AreEqual("yyy", plan.FollowPath("xxx", "yyy"));
|
||||
Assert.AreEqual("eee", plan.FollowPath("xxx").Last());
|
||||
Assert.AreEqual("yyy", plan.FollowPath("xxx", "yyy").Last());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanMerge()
|
||||
{
|
||||
var plan = new MigrationPlan("default");
|
||||
plan
|
||||
.From(string.Empty)
|
||||
.To("aaa")
|
||||
.Merge()
|
||||
.To("bbb")
|
||||
.To("ccc")
|
||||
.With()
|
||||
.To("ddd")
|
||||
.To("eee")
|
||||
.As("fff")
|
||||
.To("ggg");
|
||||
|
||||
WritePlanToConsole(plan);
|
||||
|
||||
plan.Validate();
|
||||
AssertList(plan.FollowPath(), "", "aaa", "bbb", "ccc", "*", "*", "fff", "ggg");
|
||||
AssertList(plan.FollowPath("ccc"), "ccc", "*", "*", "fff", "ggg");
|
||||
AssertList(plan.FollowPath("eee"), "eee", "*", "*", "fff", "ggg");
|
||||
}
|
||||
|
||||
private void AssertList(IReadOnlyList<string> states, params string[] expected)
|
||||
{
|
||||
Assert.AreEqual(expected.Length, states.Count, string.Join(", ", states));
|
||||
for (var i = 0; i < expected.Length; i++)
|
||||
if (expected[i] != "*")
|
||||
Assert.AreEqual(expected[i], states[i], "at:" + i);
|
||||
}
|
||||
|
||||
private void WritePlanToConsole(MigrationPlan plan)
|
||||
|
||||
Reference in New Issue
Block a user