From 6395be55dec1be2f656018a321a64bcb24c11ae8 Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Sat, 9 Jan 2021 16:43:20 +0100 Subject: [PATCH 1/9] Additional options for command-line switch `/overrideconfig`. `GitVersion.yml` options with simple prperty type (string, enum, int, bool) are now supported - 21 in total. --- .../ArgumentParserTests.cs | 243 ++++++++++++++++-- .../QuotedStringHelpersTests.cs | 130 ++++++++++ src/GitVersion.App/ArgumentParser.cs | 30 +-- .../OverrideConfigOptionParser.cs | 114 ++++++++ src/GitVersion.App/QuotedStringHelpers.cs | 84 ++++++ 5 files changed, 568 insertions(+), 33 deletions(-) create mode 100644 src/GitVersion.App.Tests/QuotedStringHelpersTests.cs create mode 100644 src/GitVersion.App/OverrideConfigOptionParser.cs create mode 100644 src/GitVersion.App/QuotedStringHelpers.cs diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index 6e7382f749..f9eaa9212c 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -1,8 +1,11 @@ +using System.Collections.Generic; using System.IO; -using GitTools.Testing; +using GitVersion.Core.Tests.Helpers; +using GitVersion.Extensions; using GitVersion.Logging; using GitVersion.Model; -using GitVersion.Core.Tests.Helpers; +using GitVersion.Model.Configuration; +using GitTools.Testing; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Shouldly; @@ -364,26 +367,232 @@ public void OverrideconfigWithNoOptions() arguments.OverrideConfig.ShouldBeNull(); } - [Test] - public void OverrideconfigWithSingleTagprefixOption() - { - var arguments = argumentParser.ParseArguments("/overrideconfig tag-prefix=sample"); - arguments.OverrideConfig.TagPrefix.ShouldBe("sample"); - } - - [TestCase("tag-prefix=sample;tag-prefix=other")] - [TestCase("tag-prefix=sample;param2=other")] - public void OverrideconfigWithSeveralOptions(string options) + [TestCaseSource(nameof(OverrideconfigWithInvalidOptionTestData))] + public string OverrideconfigWithInvalidOption(string options) { var exception = Assert.Throws(() => argumentParser.ParseArguments($"/overrideconfig {options}")); - exception.Message.ShouldContain("Can't specify multiple /overrideconfig options"); + return exception.Message; } - [TestCase("tag-prefix=sample=asdf")] - public void OverrideconfigWithInvalidOption(string options) + private static IEnumerable OverrideconfigWithInvalidOptionTestData() { - var exception = Assert.Throws(() => argumentParser.ParseArguments($"/overrideconfig {options}")); - exception.Message.ShouldContain("Could not parse /overrideconfig option"); + yield return new TestCaseData("tag-prefix=sample=asdf") + { + ExpectedResult = "Could not parse /overrideconfig option: tag-prefix=sample=asdf. Ensure it is in format 'key=value'." + }; + yield return new TestCaseData("unknown-option=25") + { + ExpectedResult = "Could not parse /overrideconfig option: unknown-option=25. Unsuported 'key'." + }; + yield return new TestCaseData("update-build-number=1") + { + ExpectedResult = "Could not parse /overrideconfig option: update-build-number=1. Ensure that 'value' is 'true' or 'false'." + }; + yield return new TestCaseData("tag-pre-release-weight=invalid-value") + { + ExpectedResult = "Could not parse /overrideconfig option: tag-pre-release-weight=invalid-value. Ensure that 'value' is valid integer number." + }; + yield return new TestCaseData("assembly-versioning-scheme=WrongEnumValue") + { + ExpectedResult = "Could not parse /overrideconfig option: assembly-versioning-scheme=WrongEnumValue. Ensure that 'value' is valid for specified 'key' enumeration: \r\nMajorMinorPatchTag\r\nMajorMinorPatch\r\nMajorMinor\r\nMajor\r\nNone\r\n" + }; + } + + [TestCaseSource(nameof(OverrideconfigWithSingleOptionTestData))] + public void OverrideconfigWithSingleOptions(string options, Config expected) + { + var arguments = argumentParser.ParseArguments($"/overrideconfig {options}"); + arguments.OverrideConfig.ShouldBeEquivalentTo(expected); + } + + private static IEnumerable OverrideconfigWithSingleOptionTestData() + { + yield return new TestCaseData( + "assembly-versioning-scheme=MajorMinor", + new Config + { + AssemblyVersioningScheme = AssemblyVersioningScheme.MajorMinor, + } + ); + yield return new TestCaseData( + "assembly-file-versioning-scheme=\"MajorMinorPatch\"", + new Config + { + AssemblyFileVersioningScheme = AssemblyFileVersioningScheme.MajorMinorPatch, + } + ); + yield return new TestCaseData( + "assembly-informational-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", + new Config + { + AssemblyInformationalFormat = "{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}", + } + ); + yield return new TestCaseData( + "assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", + new Config + { + AssemblyVersioningFormat = "{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}", + } + ); + yield return new TestCaseData( + "assembly-file-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", + new Config + { + AssemblyFileVersioningFormat = "{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}", + } + ); + yield return new TestCaseData( + "mode=ContinuousDelivery", + new Config + { + VersioningMode = GitVersion.VersionCalculation.VersioningMode.ContinuousDelivery + } + ); + yield return new TestCaseData( + "tag-prefix=sample", + new Config + { + TagPrefix = "sample" + } + ); + yield return new TestCaseData( + "continuous-delivery-fallback-tag=cd-tag", + new Config + { + ContinuousDeploymentFallbackTag = "cd-tag" + } + ); + yield return new TestCaseData( + "next-version=1", + new Config + { + NextVersion = "1" + } + ); + yield return new TestCaseData( + "major-version-bump-message=\"This is major version bump message.\"", + new Config + { + MajorVersionBumpMessage = "This is major version bump message." + } + ); + yield return new TestCaseData( + "minor-version-bump-message=\"This is minor version bump message.\"", + new Config + { + MinorVersionBumpMessage = "This is minor version bump message." + } + ); + yield return new TestCaseData( + "patch-version-bump-message=\"This is patch version bump message.\"", + new Config + { + PatchVersionBumpMessage = "This is patch version bump message." + } + ); + yield return new TestCaseData( + "no-bump-message=\"This is no bump message.\"", + new Config + { + NoBumpMessage = "This is no bump message." + } + ); + yield return new TestCaseData( + "legacy-semver-padding=99", + new Config + { + LegacySemVerPadding = 99 + } + ); + yield return new TestCaseData( + "build-metadata-padding=30", + new Config + { + BuildMetaDataPadding = 30 + } + ); + yield return new TestCaseData( + "commits-since-version-source-padding=5", + new Config + { + CommitsSinceVersionSourcePadding = 5 + } + ); + yield return new TestCaseData( + "tag-pre-release-weight=2", + new Config + { + TagPreReleaseWeight = 2 + } + ); + yield return new TestCaseData( + "commit-message-incrementing=MergeMessageOnly", + new Config + { + CommitMessageIncrementing = CommitMessageIncrementMode.MergeMessageOnly + } + ); + yield return new TestCaseData( + "increment=Minor", + new Config + { + Increment = IncrementStrategy.Minor + } + ); + yield return new TestCaseData( + "commit-date-format=\"MM/dd/yyyy h:mm tt\"", + new Config + { + CommitDateFormat = "MM/dd/yyyy h:mm tt" + } + ); + yield return new TestCaseData( + "update-build-number=true", + new Config + { + UpdateBuildNumber = true + } + ); + } + + [TestCaseSource(nameof(OverrideconfigWithMultipleOptionsTestData))] + public void OverrideconfigWithMultipleOptions(string options, Config expected) + { + var arguments = argumentParser.ParseArguments($"/overrideconfig {options}"); + arguments.OverrideConfig.ShouldBeEquivalentTo(expected); + } + + private static IEnumerable OverrideconfigWithMultipleOptionsTestData() + { + yield return new TestCaseData( + "tag-prefix=sample;assembly-versioning-scheme=MajorMinor", + new Config + { + TagPrefix = "sample", + AssemblyVersioningScheme = AssemblyVersioningScheme.MajorMinor, + } + ); + yield return new TestCaseData( + "tag-prefix=sample;assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", + new Config + { + TagPrefix = "sample", + AssemblyVersioningFormat = "{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}" + } + ); + yield return new TestCaseData( + "tag-prefix=sample;assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\";update-build-number=true;assembly-versioning-scheme=MajorMinorPatchTag;mode=ContinuousDelivery;tag-pre-release-weight=4", + new Config + { + TagPrefix = "sample", + AssemblyVersioningFormat = "{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}", + UpdateBuildNumber = true, + AssemblyVersioningScheme = AssemblyVersioningScheme.MajorMinorPatchTag, + VersioningMode = GitVersion.VersionCalculation.VersioningMode.ContinuousDelivery, + TagPreReleaseWeight = 4 + } + ); } [Test] diff --git a/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs b/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs new file mode 100644 index 0000000000..3764c9ef9d --- /dev/null +++ b/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs @@ -0,0 +1,130 @@ +using GitVersion; +using NUnit.Framework; +using System.Collections.Generic; + +namespace GitVersionExe.Tests +{ + [TestFixture] + public class QuotedStringHelpersTests + { + [TestCaseSource(nameof(SplitUnquotedTestData))] + public string[] SplitUnquotedTests(string input, char splitChar) + { + return QuotedStringHelpers.SplitUnquoted(input, splitChar); + } + + private static IEnumerable SplitUnquotedTestData() + { + yield return new TestCaseData(null, ' ') + { + ExpectedResult = System.Array.Empty() + }; + yield return new TestCaseData("one two three", ' ') + { + ExpectedResult = new []{ "one", "two", "three" } + }; + yield return new TestCaseData("one \"two three\"", ' ') + { + ExpectedResult = new[] { "one", "\"two three\"" } + }; + yield return new TestCaseData("one \"two three", ' ') + { + ExpectedResult = new[] { "one", "\"two three" } + }; + yield return new TestCaseData("/overrideconfig tag-prefix=Sample", ' ') + { + ExpectedResult = new[] + { + "/overrideconfig", + "tag-prefix=Sample" + } + }; + yield return new TestCaseData("/overrideconfig tag-prefix=Sample 2", ' ') + { + ExpectedResult = new[] + { + "/overrideconfig", + "tag-prefix=Sample", + "2" + } + }; + yield return new TestCaseData("/overrideconfig tag-prefix=\"Sample 2\"", ' ') + { + ExpectedResult = new[] + { + "/overrideconfig", + "tag-prefix=\"Sample 2\"" + } + }; + yield return new TestCaseData("/overrideconfig tag-prefix=\"Sample \\\"quoted\\\"\"", ' ') + { + ExpectedResult = new[] + { + "/overrideconfig", + "tag-prefix=\"Sample \\\"quoted\\\"\"" + } + }; + yield return new TestCaseData("/overrideconfig tag-prefix=sample;assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", ' ') + { + ExpectedResult = new[] + { + "/overrideconfig", + "tag-prefix=sample;assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"" + } + }; + yield return new TestCaseData("tag-prefix=sample;assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", ';') + { + ExpectedResult = new[] + { + "tag-prefix=sample", + "assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"" + } + }; + yield return new TestCaseData("assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", '=') + { + ExpectedResult = new[] + { + "assembly-versioning-format", + "\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"" + } + }; + } + + [TestCaseSource(nameof(RemoveEmptyEntriesTestData))] + public string[] SplitUnquotedRemovesEmptyEntries(string input, char splitChar) + { + return QuotedStringHelpers.SplitUnquoted(input, splitChar); + } + + private static IEnumerable RemoveEmptyEntriesTestData() + { + yield return new TestCaseData(" /switch1 value1 /switch2 ", ' ') + { + ExpectedResult = new[] + { + "/switch1", + "value1", + "/switch2" + } + }; + } + + [TestCaseSource(nameof(UnquoteTextTestData))] + public string UnquoteTextTextTests(string input) + { + return QuotedStringHelpers.UnquoteText(input); + } + + private static IEnumerable UnquoteTextTestData() + { + yield return new TestCaseData("\"sample\"") + { + ExpectedResult = "sample" + }; + yield return new TestCaseData("\"escaped \\\"quote\"") + { + ExpectedResult = "escaped \"quote" + }; + } + } +} diff --git a/src/GitVersion.App/ArgumentParser.cs b/src/GitVersion.App/ArgumentParser.cs index af202f234b..fa60902270 100644 --- a/src/GitVersion.App/ArgumentParser.cs +++ b/src/GitVersion.App/ArgumentParser.cs @@ -30,9 +30,7 @@ public ArgumentParser(IEnvironment environment, ICurrentBuildAgent buildAgent, I public Arguments ParseArguments(string commandLineArguments) { - var arguments = commandLineArguments - .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) - .ToArray(); + var arguments = QuotedStringHelpers.SplitUnquoted(commandLineArguments, ' '); return ParseArguments(arguments); } @@ -427,35 +425,35 @@ private static void ParseVerbosity(Arguments arguments, string value) private static void ParseOverrideConfig(Arguments arguments, string value) { - var keyValueOptions = (value ?? "").Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + // TODO: Split valu with regex so that double quotes are taken in consideration as well as escaped double quotes. + var keyValueOptions = QuotedStringHelpers.SplitUnquoted(value, ';'); if (keyValueOptions.Length == 0) { return; } - arguments.OverrideConfig = new Config(); - - if (keyValueOptions.Length > 1) - { - throw new WarningException("Can't specify multiple /overrideconfig options: currently supported only 'tag-prefix' option"); - } + var parser = new OverrideConfigOptionParser(); // key=value foreach (var keyValueOption in keyValueOptions) { - var keyAndValue = keyValueOption.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + var keyAndValue = QuotedStringHelpers.SplitUnquoted(keyValueOption, '='); if (keyAndValue.Length != 2) { - throw new WarningException($"Could not parse /overrideconfig option: {keyValueOption}. Ensure it is in format 'key=value'"); + throw new WarningException($"Could not parse /overrideconfig option: {keyValueOption}. Ensure it is in format 'key=value'."); } var optionKey = keyAndValue[0].ToLowerInvariant(); - arguments.OverrideConfig.TagPrefix = optionKey switch + if (!parser.SupportedProperties.Contains(optionKey)) { - "tag-prefix" => keyAndValue[1], - _ => throw new WarningException($"Could not parse /overrideconfig option: {optionKey}. Currently supported only 'tag-prefix' option") - }; + throw new WarningException($"Could not parse /overrideconfig option: {keyValueOption}. Unsuported 'key'."); + } + else + { + parser.SetValue(optionKey, keyAndValue[1]); + } } + arguments.OverrideConfig = parser.GetConfig(); } private static void ParseUpdateAssemblyInfo(Arguments arguments, string value, string[] values) diff --git a/src/GitVersion.App/OverrideConfigOptionParser.cs b/src/GitVersion.App/OverrideConfigOptionParser.cs new file mode 100644 index 0000000000..b39707ba99 --- /dev/null +++ b/src/GitVersion.App/OverrideConfigOptionParser.cs @@ -0,0 +1,114 @@ +using GitVersion.Model.Configuration; +using System; +using System.Linq; +using System.Reflection; + +namespace GitVersion +{ + internal class OverrideConfigOptionParser + { + private static readonly Lazy> _lazySupportedProperties = + new Lazy>(GetSupportedProperties, true); + + private Lazy _lazyConfig = new Lazy(); + + internal ILookup SupportedProperties => _lazySupportedProperties.Value; + + /// + /// Dynamically creates of + /// properties supported as a part of command line '/overrideconfig' option. + /// + /// + /// + /// Lookup keys are created from to match 'GitVersion.yml' + /// options as close as possible. + /// + private static ILookup GetSupportedProperties() + { + return typeof(Config).GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where( + pi => IsSupportedPropertyType(pi.PropertyType) + && pi.CanWrite + && pi.GetCustomAttributes(typeof(YamlDotNet.Serialization.YamlMemberAttribute), false).Length > 0 + ) + .ToLookup( + pi => (pi.GetCustomAttributes(typeof(YamlDotNet.Serialization.YamlMemberAttribute), false)[0] as YamlDotNet.Serialization.YamlMemberAttribute).Alias, + pi => pi + ); + } + + /// + /// Checks if property of + /// is supported as a part of command line '/overrideconfig' option. + /// + /// Type we want to check. + /// True, if type is supported. + /// Only simple types are supported + private static bool IsSupportedPropertyType(Type propertyType) + { + Type unwrappedType = Nullable.GetUnderlyingType(propertyType); + if (unwrappedType == null) + unwrappedType = propertyType; + + return unwrappedType == typeof(string) + || unwrappedType.IsEnum + || unwrappedType == typeof(int) + || unwrappedType == typeof(bool); + } + + internal void SetValue(string key, string value) + { + if (!SupportedProperties.Contains(key)) + return; + + var unwrappedText = QuotedStringHelpers.UnquoteText(value); + foreach (var pi in SupportedProperties[key]) + { + Type unwrapped = Nullable.GetUnderlyingType(pi.PropertyType); + if (unwrapped == null) + unwrapped = pi.PropertyType; + + if (unwrapped == typeof(string)) + pi.SetValue(_lazyConfig.Value, unwrappedText); + else if (unwrapped.IsEnum) + { + try + { + var parsedEnum = Enum.Parse(unwrapped, unwrappedText); + pi.SetValue(_lazyConfig.Value, parsedEnum); + } + catch (ArgumentException) + { + var sb = new System.Text.StringBuilder(); + + sb.Append($"Could not parse /overrideconfig option: {key}={value}."); + sb.AppendLine(" Ensure that 'value' is valid for specified 'key' enumeration: "); + foreach (var name in Enum.GetNames(unwrapped)) + sb.AppendLine(name); + + throw new WarningException(sb.ToString()); + } + } + else if (unwrapped == typeof(int)) + { + if (int.TryParse(unwrappedText, out int parsedInt)) + pi.SetValue(_lazyConfig.Value, parsedInt); + else + throw new WarningException($"Could not parse /overrideconfig option: {key}={value}. Ensure that 'value' is valid integer number."); + } + else if (unwrapped == typeof(bool)) + { + if (bool.TryParse(unwrappedText, out bool parsedBool)) + pi.SetValue(_lazyConfig.Value, parsedBool); + else + throw new WarningException($"Could not parse /overrideconfig option: {key}={value}. Ensure that 'value' is 'true' or 'false'."); + } + } + } + + internal Config GetConfig() + { + return _lazyConfig.IsValueCreated ? _lazyConfig.Value : null; + } + } +} diff --git a/src/GitVersion.App/QuotedStringHelpers.cs b/src/GitVersion.App/QuotedStringHelpers.cs new file mode 100644 index 0000000000..4a4ebcb8db --- /dev/null +++ b/src/GitVersion.App/QuotedStringHelpers.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace GitVersion +{ + public static class QuotedStringHelpers + { + /// + /// Splits input string based on split-character, ignoring split-character in + /// quoted part of the string. + /// + /// String we want to split. + /// Character used for splitting. + /// Array of splitted string parts + /// + /// If there is opening quotes character without closing quotes, + /// closing quotes are implicitly assumed at the end of the input string. + /// + /// + /// "one two three" -> {"one", "two", "three"} + /// "one \"two three\"" -> {"one", "\"two three\""} + /// "one \"two three" -> {"one", "\"two three"} // implicit closing quote. + /// + public static string[] SplitUnquoted(string input, char splitChar) + { + if (input == null) + return System.Array.Empty(); + + var splitted = new List(); + bool isPreviousCharBackslash = false; + bool isInsideQuotes = false; + + int startIndex = 0; + for (int i = 0; i < input.Length; i++) + { + char current = input[i]; + switch (current) + { + case '"': + if (!isPreviousCharBackslash) + isInsideQuotes = !isInsideQuotes; + break; + default: + if (current == splitChar && !isInsideQuotes) + { + splitted.Add(input.Substring(startIndex, i - startIndex)); + startIndex = i + 1; + } + break; + } + isPreviousCharBackslash = current == '\\'; + } + + splitted.Add(input.Substring(startIndex, input.Length - startIndex)); + + return splitted.Where(argument => !string.IsNullOrEmpty(argument)).ToArray(); + } + + /// + /// Removes enclosing quotes around input string and unescapes quote characters + /// inside of string. + /// + /// Input string to unescape. + /// Unescaped string. + /// + /// "\"one \\\"two\\\"\"" -> "one \"two\"" + /// + public static string UnquoteText(string input) + { + var sb = new StringBuilder(input); + + if (sb[0] == '"') + sb.Remove(0, 1); + + if (sb[sb.Length - 1] == '"' && sb[sb.Length - 2] != '\\') + sb.Remove(sb.Length - 1, 1); + + sb.Replace("\\\"", "\""); // unescape quotes. + + return sb.ToString(); + } + } +} From 1f818811f090b2b0a58988d2dce40fa1524ba70f Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Sat, 9 Jan 2021 17:16:43 +0100 Subject: [PATCH 2/9] Fix formatting --- src/GitVersion.App.Tests/QuotedStringHelpersTests.cs | 2 +- src/GitVersion.App/OverrideConfigOptionParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs b/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs index 3764c9ef9d..d79a656b36 100644 --- a/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs +++ b/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs @@ -21,7 +21,7 @@ private static IEnumerable SplitUnquotedTestData() }; yield return new TestCaseData("one two three", ' ') { - ExpectedResult = new []{ "one", "two", "three" } + ExpectedResult = new[] { "one", "two", "three" } }; yield return new TestCaseData("one \"two three\"", ' ') { diff --git a/src/GitVersion.App/OverrideConfigOptionParser.cs b/src/GitVersion.App/OverrideConfigOptionParser.cs index b39707ba99..e5878c8c77 100644 --- a/src/GitVersion.App/OverrideConfigOptionParser.cs +++ b/src/GitVersion.App/OverrideConfigOptionParser.cs @@ -73,7 +73,7 @@ internal void SetValue(string key, string value) else if (unwrapped.IsEnum) { try - { + { var parsedEnum = Enum.Parse(unwrapped, unwrappedText); pi.SetValue(_lazyConfig.Value, parsedEnum); } From e7961cf4b676ef504d31d93e66eebf8657233539 Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Sat, 9 Jan 2021 17:31:10 +0100 Subject: [PATCH 3/9] Fix unit-test for Mac and Ubuntu (new-line issue) --- src/GitVersion.App.Tests/ArgumentParserTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index f9eaa9212c..d334b9e921 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -394,7 +394,7 @@ private static IEnumerable OverrideconfigWithInvalidOptionTestData }; yield return new TestCaseData("assembly-versioning-scheme=WrongEnumValue") { - ExpectedResult = "Could not parse /overrideconfig option: assembly-versioning-scheme=WrongEnumValue. Ensure that 'value' is valid for specified 'key' enumeration: \r\nMajorMinorPatchTag\r\nMajorMinorPatch\r\nMajorMinor\r\nMajor\r\nNone\r\n" + ExpectedResult = $"Could not parse /overrideconfig option: assembly-versioning-scheme=WrongEnumValue. Ensure that 'value' is valid for specified 'key' enumeration: {Environment.NewLine}MajorMinorPatchTag{Environment.NewLine}MajorMinorPatch{Environment.NewLine}MajorMinor{Environment.NewLine}Major{Environment.NewLine}None{Environment.NewLine}" }; } From 4935d29a5eac09426ba75a4ec703756361c9ab91 Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Sat, 9 Jan 2021 19:09:33 +0100 Subject: [PATCH 4/9] Extend the documentation --- docs/input/docs/usage/cli/arguments.md | 64 ++++++++++++++++++++++++-- src/GitVersion.App/ArgumentParser.cs | 1 - 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/docs/input/docs/usage/cli/arguments.md b/docs/input/docs/usage/cli/arguments.md index 245ab30d33..2f4de4a041 100644 --- a/docs/input/docs/usage/cli/arguments.md +++ b/docs/input/docs/usage/cli/arguments.md @@ -99,9 +99,63 @@ gitversion init Configuration utility for gitversion ## Override config -`/overrideconfig [key=value]` will override appropriate key from -`GitVersion.yml`. +`/overrideconfig [key=value]` will override appropriate key from 'GitVersion.yml'. -At the moment only `tag-prefix` option is supported. Read more about -[Configuration](/docs/reference/configuration). It will not change config file -`GitVersion.yml`. +Multiple options are separated by semicolon: `key1=value1;key2=value2`. + +To have **space characters** as a part of `value`, `value` has be enclosed with double quotes - `key="My value"`. + +Double quote character inside of the double quoted `value` has to be be escaped with a backslash '\\' - `key="My \"escaped-quotes\""`. + +If closing double-qoute character is missing, it will be implicitily added at the very end of the command line argument - `key="My Value;key2=2(")`. + +Following options are supported: +1. `assembly-file-versioning-format` +1. `assembly-file-versioning-scheme` +1. `assembly-informational-format` +1. `assembly-versioning-format` +1. `assembly-versioning-scheme` +1. `build-metadata-padding` +1. `commit-date-format` +1. `commit-message-incrementing` +1. `commits-since-version-source-padding` +1. `continuous-delivery-fallback-tag` +1. `increment` +1. `legacy-semver-padding` +1. `major-version-bump-message` +1. `minor-version-bump-message` +1. `mode` +1. `next-version` +1. `no-bump-message` +1. `patch-version-bump-message` +1. `tag-prefix` +1. `tag-pre-release-weight` +1. `update-build-number` + +Read more about [Configuration](docs/reference/configuration). + +It will not change config file 'GitVersion.yml'. + +### Example: How to override configuration option 'tag-prefix' to use prefix 'custom' + +`GitVersion.exe /output json /overrideconfig tag-prefix=custom` + +### Example: How to override configuration option 'assembly-versioning-format' + +`GitVersion.exe /output json /overrideconfig assembly-versioning-format="{Major}.{Minor}.{Patch}.{env:BUILD_NUMBER ?? 0}"` + +Will pickup up environment variable `BUILD_NUMBER` or fallback to zero for assembly revision number. + +### Example: How to override configuration option 'assembly-versioning-scheme' + +`GitVersion.exe /output json /overrideconfig assembly-versioning-scheme=MajorMinor` + +Will use only major and minor version numbers for assembly version. Assembly build and revision numbers will be 0 (e.g. `1.2.0.0`) + +### Example: How to override configuration option 'update-build-number' + +`GitVersion.exe /output json /overrideconfig update-build-number=true` + +### Example: How to override configuration option 'next-version' + +`GitVersion.exe /output json /overrideconfig next-version=6` diff --git a/src/GitVersion.App/ArgumentParser.cs b/src/GitVersion.App/ArgumentParser.cs index fa60902270..cb6acfeace 100644 --- a/src/GitVersion.App/ArgumentParser.cs +++ b/src/GitVersion.App/ArgumentParser.cs @@ -7,7 +7,6 @@ using GitVersion.Extensions; using GitVersion.Logging; using GitVersion.Model; -using GitVersion.Model.Configuration; using GitVersion.OutputVariables; namespace GitVersion From 406a7d3bd5835dbd8cee70350a8aa6135fd0856c Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Sat, 9 Jan 2021 19:33:57 +0100 Subject: [PATCH 5/9] Fix compiler warnings CS1584 and CS1658. --- src/GitVersion.App/OverrideConfigOptionParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersion.App/OverrideConfigOptionParser.cs b/src/GitVersion.App/OverrideConfigOptionParser.cs index e5878c8c77..c0e312a1e4 100644 --- a/src/GitVersion.App/OverrideConfigOptionParser.cs +++ b/src/GitVersion.App/OverrideConfigOptionParser.cs @@ -15,7 +15,7 @@ internal class OverrideConfigOptionParser internal ILookup SupportedProperties => _lazySupportedProperties.Value; /// - /// Dynamically creates of + /// Dynamically creates of /// properties supported as a part of command line '/overrideconfig' option. /// /// From 8c273017f819b349c9c2755e668215c5317d015a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20Vi=C4=87enti=C4=87?= Date: Wed, 27 Jan 2021 14:02:40 +0100 Subject: [PATCH 6/9] Update docs/input/docs/usage/command-line.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Asbjørn Ulsberg --- docs/input/docs/usage/cli/arguments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/input/docs/usage/cli/arguments.md b/docs/input/docs/usage/cli/arguments.md index 2f4de4a041..6f846f9178 100644 --- a/docs/input/docs/usage/cli/arguments.md +++ b/docs/input/docs/usage/cli/arguments.md @@ -134,7 +134,7 @@ Following options are supported: Read more about [Configuration](docs/reference/configuration). -It will not change config file 'GitVersion.yml'. +Using `override-config` on the command line will not change the contents of the config file `GitVersion.yml`. ### Example: How to override configuration option 'tag-prefix' to use prefix 'custom' From 85a7cff36440f39138cb94c24e6b4eefa769a323 Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Sun, 11 Apr 2021 11:34:10 +0200 Subject: [PATCH 7/9] Support configuring multiple `/overrideconfig` options by adding multiple instances of `/overrideconfig` (eg. `/overrideconfig tag-prefix=custom /overrideconfig assembly-versioning-scheme=MajorMinor`) --- docs/input/docs/usage/cli/arguments.md | 11 +++++++---- src/GitVersion.App.Tests/ArgumentParserTests.cs | 10 +++++----- src/GitVersion.App.Tests/QuotedStringHelpersTests.cs | 2 +- src/GitVersion.App/ArgumentParser.cs | 12 ++++-------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/input/docs/usage/cli/arguments.md b/docs/input/docs/usage/cli/arguments.md index 6f846f9178..4785ff5753 100644 --- a/docs/input/docs/usage/cli/arguments.md +++ b/docs/input/docs/usage/cli/arguments.md @@ -99,16 +99,15 @@ gitversion init Configuration utility for gitversion ## Override config -`/overrideconfig [key=value]` will override appropriate key from 'GitVersion.yml'. +`/overrideconfig [key=value]` will override appropriate `key` from 'GitVersion.yml'. -Multiple options are separated by semicolon: `key1=value1;key2=value2`. +To specify multiple options add multiple `/overrideconfig [key=value]` entries: +`/overrideconfig key1=value1 /overrideconfig key2=value2`. To have **space characters** as a part of `value`, `value` has be enclosed with double quotes - `key="My value"`. Double quote character inside of the double quoted `value` has to be be escaped with a backslash '\\' - `key="My \"escaped-quotes\""`. -If closing double-qoute character is missing, it will be implicitily added at the very end of the command line argument - `key="My Value;key2=2(")`. - Following options are supported: 1. `assembly-file-versioning-format` 1. `assembly-file-versioning-scheme` @@ -152,6 +151,10 @@ Will pickup up environment variable `BUILD_NUMBER` or fallback to zero for assem Will use only major and minor version numbers for assembly version. Assembly build and revision numbers will be 0 (e.g. `1.2.0.0`) +### Example: How to override multiple configuration options + +`GitVersion.exe /output json /overrideconfig tag-prefix=custom /overrideconfig assembly-versioning-scheme=MajorMinor` + ### Example: How to override configuration option 'update-build-number' `GitVersion.exe /output json /overrideconfig update-build-number=true` diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index d334b9e921..bb5416b12a 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -559,22 +559,22 @@ private static IEnumerable OverrideconfigWithSingleOptionTestData( [TestCaseSource(nameof(OverrideconfigWithMultipleOptionsTestData))] public void OverrideconfigWithMultipleOptions(string options, Config expected) { - var arguments = argumentParser.ParseArguments($"/overrideconfig {options}"); + var arguments = argumentParser.ParseArguments(options); arguments.OverrideConfig.ShouldBeEquivalentTo(expected); } private static IEnumerable OverrideconfigWithMultipleOptionsTestData() { yield return new TestCaseData( - "tag-prefix=sample;assembly-versioning-scheme=MajorMinor", + "/overrideconfig tag-prefix=sample /overrideconfig assembly-versioning-scheme=MajorMinor", new Config { TagPrefix = "sample", AssemblyVersioningScheme = AssemblyVersioningScheme.MajorMinor, } - ); + ); yield return new TestCaseData( - "tag-prefix=sample;assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", + "/overrideconfig tag-prefix=sample /overrideconfig assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\"", new Config { TagPrefix = "sample", @@ -582,7 +582,7 @@ private static IEnumerable OverrideconfigWithMultipleOptionsTestDa } ); yield return new TestCaseData( - "tag-prefix=sample;assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\";update-build-number=true;assembly-versioning-scheme=MajorMinorPatchTag;mode=ContinuousDelivery;tag-pre-release-weight=4", + "/overrideconfig tag-prefix=sample /overrideconfig assembly-versioning-format=\"{Major}.{Minor}.{Patch}.{env:CI_JOB_ID ?? 0}\" /overrideconfig update-build-number=true /overrideconfig assembly-versioning-scheme=MajorMinorPatchTag /overrideconfig mode=ContinuousDelivery /overrideconfig tag-pre-release-weight=4", new Config { TagPrefix = "sample", diff --git a/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs b/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs index d79a656b36..3ee272cd81 100644 --- a/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs +++ b/src/GitVersion.App.Tests/QuotedStringHelpersTests.cs @@ -110,7 +110,7 @@ private static IEnumerable RemoveEmptyEntriesTestData() } [TestCaseSource(nameof(UnquoteTextTestData))] - public string UnquoteTextTextTests(string input) + public string UnquoteTextTests(string input) { return QuotedStringHelpers.UnquoteText(input); } diff --git a/src/GitVersion.App/ArgumentParser.cs b/src/GitVersion.App/ArgumentParser.cs index cb6acfeace..3b373ca225 100644 --- a/src/GitVersion.App/ArgumentParser.cs +++ b/src/GitVersion.App/ArgumentParser.cs @@ -298,7 +298,7 @@ private static bool ParseConfigArguments(Arguments arguments, string name, strin if (name.IsSwitch("overrideconfig")) { - ParseOverrideConfig(arguments, value); + ParseOverrideConfig(arguments, values); return true; } @@ -422,19 +422,15 @@ private static void ParseVerbosity(Arguments arguments, string value) } } - private static void ParseOverrideConfig(Arguments arguments, string value) + private static void ParseOverrideConfig(Arguments arguments, string[] values) { - // TODO: Split valu with regex so that double quotes are taken in consideration as well as escaped double quotes. - var keyValueOptions = QuotedStringHelpers.SplitUnquoted(value, ';'); - if (keyValueOptions.Length == 0) - { + if (values == null || values.Length == 0) return; - } var parser = new OverrideConfigOptionParser(); // key=value - foreach (var keyValueOption in keyValueOptions) + foreach (var keyValueOption in values) { var keyAndValue = QuotedStringHelpers.SplitUnquoted(keyValueOption, '='); if (keyAndValue.Length != 2) From e703f3a306ecc02141bc1d35ea45b0454e800aed Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Mon, 12 Apr 2021 11:51:13 +0200 Subject: [PATCH 8/9] Fix: add `System` prefix to `System.Environment.NewLine`, to avoid mixing it with `GitVersion.Environment` class. --- src/GitVersion.App.Tests/ArgumentParserTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/GitVersion.App.Tests/ArgumentParserTests.cs b/src/GitVersion.App.Tests/ArgumentParserTests.cs index bb5416b12a..4208d4c013 100644 --- a/src/GitVersion.App.Tests/ArgumentParserTests.cs +++ b/src/GitVersion.App.Tests/ArgumentParserTests.cs @@ -394,7 +394,12 @@ private static IEnumerable OverrideconfigWithInvalidOptionTestData }; yield return new TestCaseData("assembly-versioning-scheme=WrongEnumValue") { - ExpectedResult = $"Could not parse /overrideconfig option: assembly-versioning-scheme=WrongEnumValue. Ensure that 'value' is valid for specified 'key' enumeration: {Environment.NewLine}MajorMinorPatchTag{Environment.NewLine}MajorMinorPatch{Environment.NewLine}MajorMinor{Environment.NewLine}Major{Environment.NewLine}None{Environment.NewLine}" + ExpectedResult = $"Could not parse /overrideconfig option: assembly-versioning-scheme=WrongEnumValue. Ensure that 'value' is valid for specified 'key' enumeration: {System.Environment.NewLine}" + + $"MajorMinorPatchTag{System.Environment.NewLine}" + + $"MajorMinorPatch{System.Environment.NewLine}" + + $"MajorMinor{System.Environment.NewLine}" + + $"Major{System.Environment.NewLine}" + + $"None{System.Environment.NewLine}" }; } From f3ce30e7e82bb43779f27e33b1126d20f1e12344 Mon Sep 17 00:00:00 2001 From: Nenad Vicentic Date: Mon, 12 Apr 2021 16:59:50 +0200 Subject: [PATCH 9/9] Fix: "Configuration" document reference --- docs/input/docs/usage/cli/arguments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/input/docs/usage/cli/arguments.md b/docs/input/docs/usage/cli/arguments.md index 4785ff5753..f0ac0a8ad1 100644 --- a/docs/input/docs/usage/cli/arguments.md +++ b/docs/input/docs/usage/cli/arguments.md @@ -131,7 +131,7 @@ Following options are supported: 1. `tag-pre-release-weight` 1. `update-build-number` -Read more about [Configuration](docs/reference/configuration). +Read more about [Configuration](/docs/reference/configuration). Using `override-config` on the command line will not change the contents of the config file `GitVersion.yml`.