-
Notifications
You must be signed in to change notification settings - Fork 657
Format no format #4657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Format no format #4657
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,30 @@ assembly-informational-format: "{Major}.{Minor}.{Patch}-{CommitsSinceVersionSour | |
assembly-informational-format: "{SemVer}-{BranchName:l}" | ||
``` | ||
|
||
## Legacy .NET Composite Format Syntax | ||
|
||
GitVersion maintains backward compatibility with legacy .NET composite format syntax using semicolons for positive/negative/zero sections: | ||
|
||
```yaml | ||
# Legacy zero-padded with empty fallback | ||
assembly-informational-format: "{Major}.{Minor}.{Patch}-{CommitsSinceVersionSource:0000;;''}" | ||
# Result: "6.13.54-0002" (or "6.13.54" when CommitsSinceVersionSource is 0) | ||
|
||
# Three-section format: positive;negative;zero | ||
assembly-informational-format: "{Value:positive;negative;zero}" | ||
|
||
# Two-section format: positive;negative | ||
assembly-informational-format: "{Value:pos;neg}" | ||
``` | ||
|
||
**Format Sections:** | ||
- **First section**: Used for positive values | ||
- **Second section**: Used for negative values | ||
- **Third section**: Used for zero values (optional) | ||
- **Empty quotes** (`''` or `""`) create empty output | ||
|
||
**Mixed Syntax:** You can combine legacy semicolon syntax with modern `??` fallback syntax in the same template. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would |
||
|
||
## Examples | ||
|
||
Based on actual test cases from the implementation: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love the added tests! 🤩 Could we please have a test for the combination of |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
namespace GitVersion.Core.Tests.Formatting; | ||
|
||
[TestFixture] | ||
public class LegacyRegexPatternTests | ||
{ | ||
[Test] | ||
public void ExpandTokensRegex_ShouldParseLegacySemicolonSyntax() | ||
{ | ||
const string input = "{CommitsSinceVersionSource:0000;;''}"; | ||
|
||
var matches = RegexPatterns.Common.ExpandTokensRegex().Matches(input); | ||
|
||
matches.Count.ShouldBe(1); | ||
var match = matches[0]; | ||
match.Groups["member"].Value.ShouldBe("CommitsSinceVersionSource"); | ||
match.Groups["format"].Success.ShouldBeTrue(); | ||
} | ||
|
||
[Test] | ||
public void ExpandTokensRegex_ShouldHandleMixedSyntax() | ||
{ | ||
const string input = "{NewStyle:0000 ?? 'fallback'} {OldStyle:pos;neg;zero}"; | ||
|
||
var matches = RegexPatterns.Common.ExpandTokensRegex().Matches(input); | ||
|
||
matches.Count.ShouldBe(2); | ||
|
||
var newMatch = matches[0]; | ||
newMatch.Groups["member"].Value.ShouldBe("NewStyle"); | ||
newMatch.Groups["fallback"].Value.ShouldBe("fallback"); | ||
|
||
var oldMatch = matches[1]; | ||
oldMatch.Groups["member"].Value.ShouldBe("OldStyle"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,7 +1,7 @@ | ||||||
using System.Globalization; | ||||||
using GitVersion.Formatting; | ||||||
|
||||||
namespace GitVersion.Tests.Formatting; | ||||||
namespace GitVersion.Core.Tests.Formatting; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[TestFixture] | ||||||
public class DateFormatterTests | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
using GitVersion.Core.Tests.Helpers; | ||
using GitVersion.Formatting; | ||
|
||
namespace GitVersion.Core.Tests.Formatting; | ||
|
||
[TestFixture] | ||
public class LegacyFormatterProblemTests | ||
{ | ||
private TestEnvironment environment; | ||
|
||
[SetUp] | ||
public void Setup() => environment = new TestEnvironment(); | ||
|
||
// ========================================== | ||
// PROBLEM 1: Non-existent properties | ||
// ========================================== | ||
|
||
[Test] | ||
[Category("Problem2")] | ||
public void Problem2_NullValue_ShouldUseZeroSection() | ||
{ | ||
var testObject = new { Value = (int?)null }; | ||
const string template = "{Value:positive;negative;zero}"; | ||
const string expected = "zero"; | ||
|
||
var actual = template.FormatWith(testObject, environment); | ||
actual.ShouldBe(expected, "Null values should use zero section without transformation"); | ||
} | ||
|
||
[Test] | ||
[Category("Problem1")] | ||
public void Problem1_MissingProperty_ShouldFailGracefully() | ||
{ | ||
// Test tries to use {MajorMinorPatch} on SemanticVersion but that property doesn't exist | ||
var semanticVersion = new SemanticVersion | ||
{ | ||
Major = 1, | ||
Minor = 2, | ||
Patch = 3 | ||
}; | ||
|
||
const string template = "{MajorMinorPatch}"; // This property doesn't exist on SemanticVersion | ||
|
||
// Currently this will throw or behave unexpectedly | ||
// Should either throw meaningful error or handle gracefully | ||
Assert.Throws<ArgumentException>(() => template.FormatWith(semanticVersion, environment)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's a good practice to test the returned |
||
} | ||
|
||
// ========================================== | ||
// PROBLEM 2: Double negative handling | ||
// ========================================== | ||
|
||
[Test] | ||
[Category("Problem2")] | ||
public void Problem2_NegativeValue_ShouldNotDoubleNegative() | ||
{ | ||
var testObject = new { Value = -5 }; | ||
const string template = "{Value:positive;negative;zero}"; | ||
|
||
// EXPECTED: "negative" (just the literal text from section 2) | ||
// ACTUAL: "-negative" (the negative sign from -5 plus the literal "negative") | ||
const string expected = "negative"; | ||
|
||
var actual = template.FormatWith(testObject, environment); | ||
|
||
// This will currently fail - we get "-negative" instead of "negative" | ||
actual.ShouldBe(expected, "Negative values should use section text without the negative sign"); | ||
} | ||
|
||
[Test] | ||
[Category("Problem2")] | ||
public void Problem2_PositiveValue_ShouldFormatCorrectly() | ||
{ | ||
var testObject = new { Value = 5 }; | ||
const string template = "{Value:positive;negative;zero}"; | ||
const string expected = "positive"; | ||
|
||
var actual = template.FormatWith(testObject, environment); | ||
actual.ShouldBe(expected); | ||
} | ||
|
||
[Test] | ||
[Category("Problem2")] | ||
public void Problem2_ZeroValue_ShouldUseZeroSection() | ||
{ | ||
var testObject = new { Value = 0 }; | ||
const string template = "{Value:positive;negative;zero}"; | ||
const string expected = "zero"; | ||
|
||
var actual = template.FormatWith(testObject, environment); | ||
actual.ShouldBe(expected); | ||
} | ||
|
||
// ========================================== | ||
// PROBLEM 3: Insufficient formatting logic | ||
// ========================================== | ||
|
||
[Test] | ||
[Category("Problem3")] | ||
public void Problem3_NumericFormatting_AllSectionsShouldFormat() | ||
{ | ||
// Test that numeric formatting works in ALL sections, not just first | ||
var testObject = new { Value = -42 }; | ||
const string template = "{Value:0000;0000;0000}"; // All sections should pad with zeros | ||
|
||
// EXPECTED: "0042" (absolute value 42, formatted with 0000 in negative section) | ||
// ACTUAL: "0000" (literal text instead of formatted value) | ||
const string expected = "0042"; | ||
|
||
var actual = template.FormatWith(testObject, environment); | ||
actual.ShouldBe(expected, "Negative section should format the absolute value, not return literal"); | ||
} | ||
|
||
[Test] | ||
[Category("Problem3")] | ||
public void Problem3_FirstSectionWorks_OthersDont() | ||
{ | ||
// Demonstrate that first section works but others don't | ||
var positiveObject = new { Value = 42 }; | ||
var negativeObject = new { Value = -42 }; | ||
|
||
const string template = "{Value:0000;WRONG;WRONG}"; | ||
|
||
// First section (positive) should work correctly | ||
var positiveResult = template.FormatWith(positiveObject, environment); | ||
positiveResult.ShouldBe("0042", "First section should format correctly"); | ||
|
||
// Second section (negative) should return literal when invalid format provided | ||
var negativeResult = template.FormatWith(negativeObject, environment); | ||
// Invalid format "WRONG" should return literal to give user feedback about their error | ||
negativeResult.ShouldBe("WRONG", "Invalid format should return literal to indicate user error"); | ||
} | ||
|
||
// ========================================== | ||
// VERIFY #4654 FIX STILL WORKS | ||
// ========================================== | ||
|
||
[Test] | ||
[Category("Issue4654")] | ||
public void Issue4654_LegacySyntax_ShouldStillWork() | ||
{ | ||
// Verify the original #4654 fix still works | ||
var testObject = new { CommitsSinceVersionSource = 2 }; | ||
const string template = "{CommitsSinceVersionSource:0000;;''}"; | ||
const string expected = "0002"; | ||
|
||
var actual = template.FormatWith(testObject, environment); | ||
actual.ShouldBe(expected, "Issue #4654 fix must be preserved"); | ||
} | ||
|
||
[Test] | ||
[Category("Issue4654")] | ||
public void Issue4654_ZeroValue_ShouldUseEmptyString() | ||
{ | ||
// Zero values should use the third section (empty string) | ||
var testObject = new { CommitsSinceVersionSource = 0 }; | ||
const string template = "{CommitsSinceVersionSource:0000;;''}"; | ||
const string expected = ""; | ||
|
||
var actual = template.FormatWith(testObject, environment); | ||
actual.ShouldBe(expected, "Zero values should use third section (empty)"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
using System.Globalization; | ||
using GitVersion.Core.Tests.Helpers; | ||
using GitVersion.Formatting; | ||
|
||
namespace GitVersion.Core.Tests.Formatting; | ||
|
||
[TestFixture] | ||
public class LegacyFormattingSyntaxTests | ||
{ | ||
[Test] | ||
public void FormatWith_LegacyZeroFallbackSyntax_ShouldWork() | ||
{ | ||
var semanticVersion = new SemanticVersion | ||
{ | ||
Major = 6, | ||
Minor = 13, | ||
Patch = 54, | ||
PreReleaseTag = new SemanticVersionPreReleaseTag("gv6", 1, true), | ||
BuildMetaData = new SemanticVersionBuildMetaData() | ||
{ | ||
Branch = "feature/gv6", | ||
VersionSourceSha = "versionSourceSha", | ||
Sha = "489a0c0ab425214def918e36399f3cc3c9a9c42d", | ||
ShortSha = "489a0c0", | ||
CommitsSinceVersionSource = 2, | ||
CommitDate = DateTimeOffset.Parse("2025-08-12", CultureInfo.InvariantCulture) | ||
} | ||
}; | ||
|
||
const string template = "{MajorMinorPatch}{PreReleaseLabelWithDash}{CommitsSinceVersionSource:0000;;''}"; | ||
const string expected = "6.13.54-gv60002"; | ||
|
||
var actual = template.FormatWith(semanticVersion, new TestEnvironment()); | ||
|
||
actual.ShouldBe(expected); | ||
} | ||
|
||
[Test] | ||
public void FormatWith_LegacyThreeSectionSyntax_ShouldWork() | ||
{ | ||
var testObject = new { Value = -5 }; | ||
const string template = "{Value:positive;negative;zero}"; | ||
const string expected = "negative"; | ||
|
||
var actual = template.FormatWith(testObject, new TestEnvironment()); | ||
|
||
actual.ShouldBe(expected); | ||
} | ||
|
||
[Test] | ||
public void FormatWith_LegacyTwoSectionSyntax_ShouldWork() | ||
{ | ||
var testObject = new { Value = -10 }; | ||
const string template = "{Value:positive;negative}"; | ||
const string expected = "negative"; | ||
|
||
var actual = template.FormatWith(testObject, new TestEnvironment()); | ||
|
||
actual.ShouldBe(expected); | ||
} | ||
|
||
[Test] | ||
public void FormatWith_LegacyZeroValue_ShouldUseThirdSection() | ||
{ | ||
var testObject = new { Value = 0 }; | ||
const string template = "{Value:pos;neg;ZERO}"; | ||
const string expected = "ZERO"; | ||
|
||
var actual = template.FormatWith(testObject, new TestEnvironment()); | ||
|
||
actual.ShouldBe(expected); | ||
} | ||
|
||
[Test] | ||
public void FormatWith_MixedLegacyAndNewSyntax_ShouldWork() | ||
{ | ||
var testObject = new | ||
{ | ||
OldStyle = 0, | ||
NewStyle = 42, | ||
RegularProp = "test" | ||
}; | ||
const string template = "{OldStyle:pos;neg;''}{NewStyle:0000 ?? 'fallback'}{RegularProp}"; | ||
const string expected = "0042test"; | ||
|
||
var actual = template.FormatWith(testObject, new TestEnvironment()); | ||
|
||
actual.ShouldBe(expected); | ||
} | ||
|
||
[Test] | ||
public void FormatWith_LegacyWithStandardFormatSpecifiers_ShouldWork() | ||
{ | ||
var testObject = new { Amount = 1234.56 }; | ||
const string template = "{Amount:C2;(C2);'No Amount'}"; | ||
const string expected = "¤1,234.56"; | ||
|
||
var actual = template.FormatWith(testObject, new TestEnvironment()); | ||
|
||
actual.ShouldBe(expected); | ||
} | ||
|
||
[Test] | ||
public void FormatWith_Issue4654ExactCase_ShouldWork() | ||
{ | ||
var semanticVersion = new SemanticVersion | ||
{ | ||
Major = 6, | ||
Minor = 13, | ||
Patch = 54, | ||
PreReleaseTag = new SemanticVersionPreReleaseTag("gv6", 1, true), | ||
BuildMetaData = new SemanticVersionBuildMetaData("Branch.feature-gv6") | ||
{ | ||
CommitsSinceVersionSource = 2 | ||
} | ||
}; | ||
|
||
var mainBranchVersion = new SemanticVersion | ||
{ | ||
Major = 6, | ||
Minor = 13, | ||
Patch = 54, | ||
PreReleaseTag = new SemanticVersionPreReleaseTag(string.Empty, 0, true), | ||
BuildMetaData = new SemanticVersionBuildMetaData() | ||
{ | ||
CommitsSinceVersionSource = 0 | ||
} | ||
}; | ||
|
||
const string template = "{MajorMinorPatch}{PreReleaseLabelWithDash}{CommitsSinceVersionSource:0000;;''}"; | ||
|
||
var featureResult = template.FormatWith(semanticVersion, new TestEnvironment()); | ||
featureResult.ShouldBe("6.13.54-gv60002"); | ||
|
||
var mainResult = template.FormatWith(mainBranchVersion, new TestEnvironment()); | ||
mainResult.ShouldBe("6.13.54"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,6 @@ | ||||||
using GitVersion.Formatting; | ||||||
|
||||||
namespace GitVersion.Tests.Formatting; | ||||||
namespace GitVersion.Core.Tests.Formatting; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[TestFixture] | ||||||
public class StringFormatterTests | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,7 +1,7 @@ | ||||||
using System.Globalization; | ||||||
using GitVersion.Formatting; | ||||||
|
||||||
namespace GitVersion.Tests.Formatting; | ||||||
namespace GitVersion.Core.Tests.Formatting; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[TestFixture] | ||||||
public class ValueFormatterTests | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could these last two examples be modified to be more practical and real-world relevant, with a line showing the result, as in the first code example?