Skip to content

Commit

Permalink
Add Specified Ordering File Locator
Browse files Browse the repository at this point in the history
Add SpecifiedOrder File Locator and added a /scriptorder option which
allows you to select between the traverse, recurse or scriptorder
options. The /searchallinsteadoftraverse option also controls the
/scriptorder so RoundHouse stays backwards compatibile.

The SpecifiedOrder Locator looks at file (set by the /SpecifiedOrderFile
option, otherwise scriptorder.txt) in the migration folder and
orders each file in that migration folder and it's subdirectories based
on the position that file has in the specified order file.

Files that aren't inside the specified order file are returned after the
files that are and are sorted via the culture invariant case-insensitive
comparer on the filename relative to the migration directory.

Use Cases:

1. Using the scriptorder file allows to specify the script order in your
integration branch as you merge different feature branches into it. This
means that feature branch scripts can always rely on the existing
scripts in the integration branch being run first.

2. Stored Procedure A depends on Stored Procedure B.

Notes:

The script order file considers lines starting with # as comment lines
and are ignored.
  • Loading branch information
icedtoast committed Sep 7, 2012
1 parent dd9afa2 commit 8251f7a
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 9 deletions.
14 changes: 13 additions & 1 deletion product/roundhouse.console/Program.cs
Expand Up @@ -292,7 +292,19 @@ private static void parse_arguments_and_set_up_configuration(ConfigurationProper
option => configuration.DryRun = option != null)
.Add("searchallinsteadoftraverse=|searchallsubdirectoriesinsteadoftraverse=",
"SearchAllSubdirectoriesInsteadOfTraverse - Each Migration folder's subdirectories are traversed by default. This option pulls back scripts from the main directory and all subdirectories at once. Defaults to 'false'",
option => configuration.SearchAllSubdirectoriesInsteadOfTraverse = option != null)
option => configuration.ScriptOrder = option != null ? "recurse" : "traverse")
.Add("scriptorder=",
string.Format(
"ScriptOrder is used to tell RoundHouse which script ordering method to use, can be: traverse, recurse or specified, defaults to: {0}",
ApplicationParameters.default_script_order),
option => configuration.ScriptOrder = option)
.Add("specifiedorderfile=",
string.Format(
"SpecifiedOrderFile - The file name relative to the migration folder of the ordering file using by the specified scriptorder type, defaults to: {0}",
ApplicationParameters.default_specified_order_file),
option => configuration.SpecifiedOrderFile = option
);

;

try
Expand Down
10 changes: 9 additions & 1 deletion product/roundhouse.tasks/Roundhouse.cs
Expand Up @@ -129,10 +129,18 @@ bool ITask.Execute()

public bool DisableTokenReplacement { get; set; }

public bool SearchAllSubdirectoriesInsteadOfTraverse { get; set; }
public bool SearchAllSubdirectoriesInsteadOfTraverse
{
get { return ScriptOrder == "recurse"; }
set { ScriptOrder = value ? "recurse" : "traverse"; }
}

public bool DisableOutput { get; set; }

public string ScriptOrder { get; set; }

public string SpecifiedOrderFile { get; set; }

#endregion

public void run_the_task()
Expand Down
3 changes: 2 additions & 1 deletion product/roundhouse/consoles/DefaultConfiguration.cs
Expand Up @@ -54,7 +54,8 @@ public sealed class DefaultConfiguration : ConfigurationPropertyHolder
public bool Baseline { get; set; }
public bool RunAllAnyTimeScripts { get; set; }
public bool DisableTokenReplacement { get; set; }
public bool SearchAllSubdirectoriesInsteadOfTraverse { get; set; }
public bool DisableOutput { get; set; }
public string ScriptOrder { get; set; }
public string SpecifiedOrderFile { get; set; }
}
}
31 changes: 27 additions & 4 deletions product/roundhouse/infrastructure.app/ApplicationConfiguraton.cs
Expand Up @@ -137,6 +137,14 @@ public static void set_defaults_if_properties_are_not_set(ConfigurationPropertyH
{
configuration_property_holder.RecoveryMode = RecoveryMode.Simple;
}
if(string.IsNullOrEmpty(configuration_property_holder.ScriptOrder))
{
configuration_property_holder.ScriptOrder = ApplicationParameters.default_script_order;
}
if(string.IsNullOrEmpty(configuration_property_holder.SpecifiedOrderFile))
{
configuration_property_holder.SpecifiedOrderFile = ApplicationParameters.default_specified_order_file;
}
}

private static void set_up_current_mappings(ConfigurationPropertyHolder configuration_property_holder)
Expand Down Expand Up @@ -177,10 +185,25 @@ private static InversionContainer build_items_for_container(ConfigurationPropert
cfg.For<VersionResolver>().Singleton().Use(
context => VersionResolverBuilder.build(context.GetInstance<FileSystemAccess>(), configuration_property_holder));
cfg.For<Environment>().Singleton().Use(new DefaultEnvironment(configuration_property_holder));
cfg.For<FileLocator>().Singleton().Use(
configuration_property_holder.SearchAllSubdirectoriesInsteadOfTraverse
? (FileLocator)new Recurse()
: new Traverse());
cfg.For<FileLocator>().Singleton().Use(() =>
{
switch (configuration_property_holder.ScriptOrder)
{
case "traverse":
return new Traverse();
case "recurse":
return new Recurse();
case "specified":
return
new SpecifiedOrdering (
configuration_property_holder.SpecifiedOrderFile);
}
throw new ArgumentOutOfRangeException
("ScriptOrder",
configuration_property_holder
.ScriptOrder,
"Unknown ScriptOrder");
});
});

// forcing a build of database to initialize connections so we can be sure server/database have values
Expand Down
Expand Up @@ -54,7 +54,9 @@ public interface ConfigurationPropertyHolder
bool Baseline { get; set; }
bool RunAllAnyTimeScripts { get; set; }
bool DisableTokenReplacement { get; set; }
bool SearchAllSubdirectoriesInsteadOfTraverse { get; set; }
bool DisableOutput { get; set; }
string ScriptOrder { get; set; }

string SpecifiedOrderFile { get; set; }
}
}
2 changes: 2 additions & 0 deletions product/roundhouse/infrastructure/ApplicationParameters.cs
Expand Up @@ -41,6 +41,8 @@ public static class ApplicationParameters
public static readonly int default_admin_command_timeout = 300;
public static readonly int default_restore_timeout = 900;
public static readonly bool default_disable_output = false;
public static readonly string default_script_order = "traverse";
public static readonly string default_specified_order_file = "scriptorder.txt";

public static string get_merged_assembly_name()
{
Expand Down
@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace roundhouse.infrastructure.filesystem.filelocators
{
/// <summary>
/// Supports the use of a specific ordering in a directory, via
/// the use of a file which specifies the order of each file, using
/// a relative path.
///
/// Files in the directory or subdirectories that are not in the specified order file
/// are sorted according to their relative path.
///
/// The Script Ordering File:
/// - Lines starting with # are ignored
/// - Each relative file path are on it's own line, without the leading directory seperator
/// </summary>
public class SpecifiedOrdering : FileLocator
{
/// <summary>
/// Construct a SpecifiedOrdering FileLocator.
/// </summary>
/// <param name="ordering_file_name">The path to the specific ordering file relative to the directory.</param>
public SpecifiedOrdering(string ordering_file_name)
{
this.ordering_file_name = ordering_file_name;
}

private readonly string ordering_file_name;

/// <summary>
/// Return the files in this directory and all subdirectories,
/// the order is as follows:
/// 1. Files specified in the ordering file, in the order of their entry in that file
/// 2. Files not specified in the ordering, in the culture invariant case-insensitive order
/// of the relative file name.
/// </summary>
/// <param name="directory">The directory to search</param>
/// <param name="pattern">The file pattern you are searching for</param>
/// <returns>The ordered list of files.</returns>
public string[] locate_all_files_in(string directory, string pattern)
{
var ordering_file = Path.Combine(directory, ordering_file_name);
// Load the specified order file and create a dictionary of the filenames and their positions.
var specifiedOrder = File.Exists(ordering_file)
? File.ReadAllLines(ordering_file)
// skip comment lines.
.Where(line => !line.StartsWith("#"))
.Select((relativeFileName, order) => new KeyValuePair<string, int>(relativeFileName, order))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.InvariantCultureIgnoreCase)
: new Dictionary<string, int>();

var files = Directory.GetFiles(directory, pattern, SearchOption.AllDirectories);
// Sort the files according to their position in the ordering file
// or failing that their relative file paths.
Array.Sort(files, (lhsRaw, rhsRaw) =>
{
// obtain the file names relative to the search directory.
var lhs =
lhsRaw.Substring(directory.Length).TrimStart(
Path.DirectorySeparatorChar);
var rhs =
rhsRaw.Substring(directory.Length).TrimStart(
Path.DirectorySeparatorChar);
int lhsPosition;
int rhsPosition;
// Figure out whether the files are in the specified order file.
bool lhsIsSpecified = specifiedOrder.TryGetValue(lhs, out lhsPosition);
bool rhsIsSpecified = specifiedOrder.TryGetValue(rhs, out rhsPosition);
if(lhsIsSpecified && rhsIsSpecified)
{
// Both in specified ordering file,
// Order them by their location in that file.
return Comparer<int>.Default.Compare(lhsPosition, rhsPosition);
} else if(lhsIsSpecified)
{
// Left hand side is in the specified file,
// so it goes before the right hand side.
return -1;
} else if (rhsIsSpecified)
{
// Right hand side is in the specified file,
// so it goes before the left hand side.
return 1;
}
// neither side is in the ordering file,
// use the string comparer against the the relative path to
// each file.
return StringComparer.InvariantCultureIgnoreCase.Compare(lhs, rhs);
}
);

return files;
}
}
}
1 change: 1 addition & 0 deletions product/roundhouse/roundhouse.csproj
Expand Up @@ -118,6 +118,7 @@
<Compile Include="infrastructure.app\persistence\NHibernateMigrationSessionFactory.cs" />
<Compile Include="infrastructure\extensions\ObjectExtensions.cs" />
<Compile Include="infrastructure\filesystem\filelocators\Recurse.cs" />
<Compile Include="infrastructure\filesystem\filelocators\SpecifiedOrdering.cs" />
<Compile Include="infrastructure\filesystem\filelocators\Traverse.cs" />
<Compile Include="infrastructure\logging\custom\ConsoleLogger.cs" />
<Compile Include="infrastructure\persistence\AuditEventListener.cs" />
Expand Down
2 changes: 1 addition & 1 deletion product/roundhouse/runners/RoundhouseMigrationRunner.cs
Expand Up @@ -91,7 +91,7 @@ public void run()

create_change_drop_folder();
Log.bound_to(this).log_a_debug_event_containing("The change_drop (output) folder is: {0}", known_folders.change_drop.folder_full_path);
Log.bound_to(this).log_a_debug_event_containing("Using SearchAllSubdirectoriesInsteadOfTraverse execution: {0}", configuration.SearchAllSubdirectoriesInsteadOfTraverse);
Log.bound_to(this).log_a_debug_event_containing("Using ScriptOrder execution: {0}", configuration.ScriptOrder);

try
{
Expand Down

0 comments on commit 8251f7a

Please sign in to comment.