Skip to content

Commit 31ee802

Browse files
authored
Fix changelog bundle encoding characters (#2565)
1 parent 02b21f0 commit 31ee802

File tree

7 files changed

+98
-8
lines changed

7 files changed

+98
-8
lines changed

src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@ private async Task WriteBundleFileAsync(Bundle bundledData, string outputPath, C
199199
_logger.LogInformation("Output file already exists, using unique filename: {OutputPath}", outputPath);
200200
}
201201

202-
// Write bundled file
203-
await _fileSystem.File.WriteAllTextAsync(outputPath, bundledYaml, ctx);
202+
// Write bundled file with explicit UTF-8 encoding to ensure proper character handling
203+
await _fileSystem.File.WriteAllTextAsync(outputPath, bundledYaml, Encoding.UTF8, ctx);
204204
_logger.LogInformation("Created bundled changelog: {OutputPath}", outputPath);
205205
}
206206

src/services/Elastic.Changelog/Creation/ChangelogFileWriter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.IO.Abstractions;
6+
using System.Text;
67
using Elastic.Changelog.Configuration;
78
using Elastic.Changelog.Serialization;
89
using Elastic.Documentation;
@@ -43,8 +44,8 @@ public async Task<bool> WriteChangelogAsync(
4344
var filename = GenerateFilename(collector, input, prUrl);
4445
var filePath = fileSystem.Path.Combine(outputDir, filename);
4546

46-
// Write file
47-
await fileSystem.File.WriteAllTextAsync(filePath, yamlContent, ctx);
47+
// Write file with explicit UTF-8 encoding to ensure proper character handling
48+
await fileSystem.File.WriteAllTextAsync(filePath, yamlContent, Encoding.UTF8, ctx);
4849
logger.LogInformation("Created changelog fragment: {FilePath}", filePath);
4950

5051
return true;

src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ private async Task<bool> ProcessPrReference(
273273
var slug = ChangelogTextUtilities.GenerateSlug(title);
274274
var filename = $"{prRef.PrNumber}-{finalType.ToStringFast(true)}-{slug}.yaml";
275275
var filePath = _fileSystem.Path.Combine(outputDir, filename);
276-
await _fileSystem.File.WriteAllTextAsync(filePath, yamlContent, ctx);
276+
await _fileSystem.File.WriteAllTextAsync(filePath, yamlContent, Encoding.UTF8, ctx);
277277

278278
createdFiles.Add(filename);
279279
_logger.LogDebug("Created changelog: {FilePath}", filePath);
@@ -324,7 +324,7 @@ private async Task<string> CreateBundleFile(
324324
// Name format: <version>-<product>-bundle.yml
325325
var bundleFilename = $"{productInfo.Target}-{productInfo.Product}-bundle.yml";
326326
var bundlePath = _fileSystem.Path.Combine(bundlesDir, bundleFilename);
327-
await _fileSystem.File.WriteAllTextAsync(bundlePath, yamlContent, ctx);
327+
await _fileSystem.File.WriteAllTextAsync(bundlePath, yamlContent, Encoding.UTF8, ctx);
328328

329329
return bundlePath;
330330
}

src/services/Elastic.Changelog/Rendering/Asciidoc/ChangelogAsciidocRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public async Task RenderAsciidoc(ChangelogRenderContext context, Cancel ctx)
123123
if (!string.IsNullOrWhiteSpace(asciidocDir) && !fileSystem.Directory.Exists(asciidocDir))
124124
_ = fileSystem.Directory.CreateDirectory(asciidocDir);
125125

126-
await fileSystem.File.WriteAllTextAsync(asciidocPath, sb.ToString(), ctx);
126+
await fileSystem.File.WriteAllTextAsync(asciidocPath, sb.ToString(), Encoding.UTF8, ctx);
127127
}
128128

129129
private static void RenderSectionHeader(StringBuilder sb, string anchorPrefix, string titleSlug, string title)

src/services/Elastic.Changelog/Rendering/Markdown/MarkdownRendererBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ protected async Task WriteOutputFileAsync(string outputDir, string titleSlug, st
3131
if (!string.IsNullOrWhiteSpace(outputDirectory) && !FileSystem.Directory.Exists(outputDirectory))
3232
_ = FileSystem.Directory.CreateDirectory(outputDirectory);
3333

34-
await FileSystem.File.WriteAllTextAsync(outputPath, content, ctx);
34+
await FileSystem.File.WriteAllTextAsync(outputPath, content, Encoding.UTF8, ctx);
3535
}
3636

3737
/// <summary>

src/services/Elastic.Changelog/Serialization/ChangelogYamlSerialization.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public static class ChangelogYamlSerialization
3535
new StaticSerializerBuilder(new ChangelogYamlStaticContext())
3636
.WithNamingConvention(UnderscoredNamingConvention.Instance)
3737
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitEmptyCollections)
38+
.WithQuotingNecessaryStrings()
39+
.DisableAliases()
3840
.Build();
3941

4042
/// <summary>

tests/Elastic.Changelog.Tests/Changelogs/BundleChangelogsTests.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,93 @@ public async Task BundleChangelogs_WithResolve_CopiesChangelogContents()
12921292
bundleContent.Should().Contain("description: This is a test feature");
12931293
}
12941294

1295+
[Fact]
1296+
public async Task BundleChangelogs_WithResolve_PreservesSpecialCharactersInUtf8()
1297+
{
1298+
// Arrange - Create changelog with special characters that could be corrupted
1299+
// These characters were reported as being corrupted to "&o0" and "*o0" in the original issue
1300+
1301+
// language=yaml
1302+
var changelog1 =
1303+
"""
1304+
title: Feature with special characters & symbols
1305+
type: feature
1306+
products:
1307+
- product: elasticsearch
1308+
target: 9.3.0
1309+
lifecycle: ga
1310+
pr: https://github.com/elastic/elasticsearch/pull/100
1311+
description: |
1312+
This feature includes special characters:
1313+
- Ampersand: & symbol
1314+
- Asterisk: * symbol
1315+
- Other special chars: < > " ' / \
1316+
- Unicode: © ® ™ € £ ¥
1317+
""";
1318+
1319+
var file1 = FileSystem.Path.Combine(_changelogDir, "1755268130-special-chars.yaml");
1320+
await FileSystem.File.WriteAllTextAsync(file1, changelog1, System.Text.Encoding.UTF8, TestContext.Current.CancellationToken);
1321+
1322+
var outputPath = FileSystem.Path.Combine(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml");
1323+
var input = new BundleChangelogsArguments
1324+
{
1325+
Directory = _changelogDir,
1326+
All = true,
1327+
Resolve = true,
1328+
Output = outputPath
1329+
};
1330+
1331+
// Act
1332+
var result = await Service.BundleChangelogs(Collector, input, TestContext.Current.CancellationToken);
1333+
1334+
// Assert
1335+
result.Should().BeTrue();
1336+
Collector.Errors.Should().Be(0);
1337+
1338+
// Read the bundle file with explicit UTF-8 encoding
1339+
var bundleContent = await FileSystem.File.ReadAllTextAsync(input.Output, System.Text.Encoding.UTF8, TestContext.Current.CancellationToken);
1340+
1341+
// Verify special characters are preserved correctly (not corrupted)
1342+
// The original issue reported "&o0" and "*o0" corruption, so we verify the characters are correct
1343+
bundleContent.Should().Contain("&"); // Ampersand should be preserved
1344+
bundleContent.Should().Contain("Feature with special characters & symbols"); // Ampersand in title
1345+
bundleContent.Should().Contain("Ampersand: & symbol"); // Ampersand in description
1346+
1347+
// Check that asterisk appears correctly (not corrupted to "*o0")
1348+
bundleContent.Should().Contain("*"); // Asterisk should be preserved
1349+
bundleContent.Should().Contain("Asterisk: * symbol"); // Asterisk in description
1350+
1351+
// Verify the ampersand and asterisk are not corrupted
1352+
// The corruption pattern would be "&o0" or "*o0" appearing where we expect "&" or "*"
1353+
// We check that the title contains the correct pattern, not the corrupted one
1354+
var titleLine = bundleContent.Split('\n').FirstOrDefault(l => l.Contains("title:"));
1355+
titleLine.Should().NotBeNull();
1356+
titleLine.Should().Contain("&");
1357+
titleLine.Should().NotContain("&o0"); // Should not be corrupted in title
1358+
1359+
// Verify no corruption patterns exist (these would indicate encoding issues)
1360+
bundleContent.Should().NotContain("&o0"); // Should not contain corrupted ampersand
1361+
bundleContent.Should().NotContain("*o0"); // Should not contain corrupted asterisk
1362+
1363+
// Verify other special characters are preserved
1364+
bundleContent.Should().Contain("<");
1365+
bundleContent.Should().Contain(">");
1366+
bundleContent.Should().Contain("\"");
1367+
1368+
// Verify Unicode characters are preserved
1369+
bundleContent.Should().Contain("©");
1370+
bundleContent.Should().Contain("®");
1371+
bundleContent.Should().Contain("™");
1372+
bundleContent.Should().Contain("€");
1373+
1374+
// Verify the content structure is correct
1375+
bundleContent.Should().Contain("title: Feature with special characters & symbols");
1376+
bundleContent.Should().Contain("type: feature");
1377+
bundleContent.Should().Contain("product: elasticsearch");
1378+
bundleContent.Should().Contain("target: 9.3.0");
1379+
bundleContent.Should().Contain("lifecycle: ga");
1380+
}
1381+
12951382
[Fact]
12961383
public async Task BundleChangelogs_WithDirectoryOutputPath_CreatesDefaultFilename()
12971384
{

0 commit comments

Comments
 (0)