Prepared release v26.11.1 (#1173)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-07 13:14:58 +01:00
1 parent 9d1768e2f4
commit 520758771b
11 files changed
+339 -66

No files matched your search

+95 -17
View File
@@ -270,12 +270,35 @@ public sealed partial class UpdateMetadataCommands
throw new CommandExitedException(1);
}
// The previous release created the changelog with a placeholder in its first line, which
// reserved the build number of this release. We fill in the build time below, so check now,
// before anything is written, that the placeholder is still there:
var changelogPlaceholderHeader = GetFirstLine(await File.ReadAllTextAsync(changelogPath, Encoding.UTF8));
var expectedBuildNumber = await ReadBuildNumber() + 1;
if (!IsChangelogPlaceholderHeader(changelogPlaceholderHeader, appVersion.VersionText, expectedBuildNumber))
{
Console.WriteLine($"- Error: The first line of '{Path.GetFileName(changelogPath)}' must be the placeholder '# v{appVersion.VersionText}, build {expectedBuildNumber} (YYYY-MM-xx xx:xx UTC)', but it is '{changelogPlaceholderHeader}'.");
throw new CommandExitedException(1);
}
try
{
await EnsureMetainfoReleaseCanBeWritten(appVersion.VersionText);
}
catch (InvalidOperationException exception)
{
Console.WriteLine($"- Error: {exception.Message}");
throw new CommandExitedException(1);
}
await this.UpdateAppVersion(appVersion);
var buildNumber = await this.IncreaseBuildNumber();
var buildTime = await this.UpdateBuildTime();
await this.UpdateChangelog(buildNumber, appVersion.VersionText, buildTime);
var buildTimeText = await this.UpdateBuildTime();
var buildTime = ParseMetadataBuildTime(buildTimeText);
await UpdateChangelogHeader(changelogPath, changelogPlaceholderHeader, appVersion.VersionText, buildNumber, buildTime);
await this.UpdateChangelog(buildNumber, appVersion.VersionText, buildTimeText);
await this.CreateNextChangelog(buildNumber, appVersion);
await WriteMetainfoRelease(appVersion.VersionText, ParseMetadataBuildTime(buildTime));
await WriteMetainfoRelease(appVersion.VersionText, buildTime);
await this.UpdateProjectCommitHash();
await this.UpdateReleaseDependenciesAndLicence();
Console.WriteLine();
@@ -647,13 +670,8 @@ public sealed partial class UpdateMetadataCommands
var nextChangelog = nextChangelogCandidates[0];
// The release entry itself is written by ApplyRebuildReleaseState, which adds it when it is
// missing and moves it to the top otherwise. Here, we only ensure that there is a file to write to:
var metainfoPath = GetMetainfoPath();
if (!File.Exists(metainfoPath))
throw new InvalidOperationException("The AppStream metainfo file does not exist.");
if (!ReleasesStartRegex().IsMatch(await File.ReadAllTextAsync(metainfoPath, Encoding.UTF8)))
throw new InvalidOperationException("The AppStream metainfo does not contain a <releases> element.");
// missing and moves it to the top otherwise. Here, we only ensure that it can be written:
await EnsureMetainfoReleaseCanBeWritten(appVersion);
var headCommitHash = (await this.ReadCommandOutput(Environment.GetAIStudioDirectory(), "git", "rev-parse HEAD")).Text.Trim();
if (!GitCommitHashRegex().IsMatch(headCommitHash))
@@ -731,13 +749,7 @@ public sealed partial class UpdateMetadataCommands
const string RELEASE_INDENT = " ";
var metainfoPath = GetMetainfoPath();
if (!File.Exists(metainfoPath))
throw new InvalidOperationException("The AppStream metainfo file does not exist.");
var metainfo = await File.ReadAllTextAsync(metainfoPath, Encoding.UTF8);
if (!ReleasesStartRegex().IsMatch(metainfo))
throw new InvalidOperationException("The AppStream metainfo does not contain a <releases> element.");
var metainfo = await ReadMetainfo();
var changelogEntries = await ReadChangelogEntries(appVersion);
// Drop any earlier entry of this version, so that the version stays unique and moves to the top.
@@ -771,6 +783,34 @@ public sealed partial class UpdateMetadataCommands
Console.WriteLine($"- Updated the AppStream metainfo for v{appVersion}, released on {releaseDate}, with {changelogEntries.Count} changelog entries.");
}
/// <summary>
/// Checks everything WriteMetainfoRelease needs for the given version, without writing anything.
/// </summary>
/// <remarks>
/// Preparing a release writes the AppStream entry last, after the metadata and the changelogs were changed. When
/// it failed only there, e.g., because the changelog has no entry yet, it would leave a half-prepared release
/// behind. Hence, the commands call this before they write anything.
/// </remarks>
/// <param name="appVersion">The version whose release entry will be written.</param>
private static async Task EnsureMetainfoReleaseCanBeWritten(string appVersion)
{
await ReadMetainfo();
await ReadChangelogEntries(appVersion);
}
private static async Task<string> ReadMetainfo()
{
var metainfoPath = GetMetainfoPath();
if (!File.Exists(metainfoPath))
throw new InvalidOperationException("The AppStream metainfo file does not exist.");
var metainfo = await File.ReadAllTextAsync(metainfoPath, Encoding.UTF8);
if (!ReleasesStartRegex().IsMatch(metainfo))
throw new InvalidOperationException("The AppStream metainfo does not contain a <releases> element.");
return metainfo;
}
private static async Task<IReadOnlyList<string>> ReadChangelogEntries(string appVersion)
{
var changelogPath = GetChangelogPath(appVersion);
@@ -853,6 +893,37 @@ public sealed partial class UpdateMetadataCommands
return $"# v{appVersion}, build {buildNumber} ({buildTime.ToUniversalTime():yyyy-MM-dd HH:mm} UTC)";
}
/// <summary>
/// Whether a changelog header is the placeholder which the previous release created for this version and build.
/// </summary>
/// <remarks>
/// The month of the placeholder is only a guess of when the release would happen, so any month matches.
/// </remarks>
/// <param name="changelogHeader">The first line of the changelog.</param>
/// <param name="appVersion">The version of the release.</param>
/// <param name="buildNumber">The build number of the release.</param>
/// <returns>True, when the header is the placeholder.</returns>
private static bool IsChangelogPlaceholderHeader(string changelogHeader, string appVersion, int buildNumber)
{
return Regex.IsMatch(changelogHeader, $"^# v{Regex.Escape(appVersion)}, build {buildNumber} \\(20[0-9]{{2}}-[0-9]{{2}}-xx xx:xx UTC\\)$");
}
/// <summary>
/// Replaces the placeholder in the first line of a changelog with the header of the release.
/// </summary>
/// <param name="changelogPath">The path of the changelog.</param>
/// <param name="placeholderHeader">The placeholder, as checked before anything was written.</param>
/// <param name="appVersion">The version of the release.</param>
/// <param name="buildNumber">The build number of the release.</param>
/// <param name="buildTime">The build time of the release.</param>
private static async Task UpdateChangelogHeader(string changelogPath, string placeholderHeader, string appVersion, int buildNumber, DateTime buildTime)
{
var changelogContent = await File.ReadAllTextAsync(changelogPath, Encoding.UTF8);
var updatedChangelog = ReplaceExactlyOnce(changelogContent, placeholderHeader, FormatChangelogHeader(appVersion, buildNumber, buildTime));
await File.WriteAllTextAsync(changelogPath, updatedChangelog, Environment.UTF8_NO_BOM);
Console.WriteLine($"- Updated the header of '{Path.GetFileName(changelogPath)}'.");
}
private static string FormatChangelogLogEntry(string appVersion, int buildNumber, DateTime buildTime, string changelogFilename)
{
return $"new ({buildNumber}, \"v{appVersion}, build {buildNumber} ({buildTime.ToUniversalTime():yyyy-MM-dd HH:mm} UTC)\", \"{changelogFilename}\"),";
@@ -1263,6 +1334,13 @@ public sealed partial class UpdateMetadataCommands
return new(sb.ToString(), process.ExitCode);
}
private static async Task<int> ReadBuildNumber()
{
const int BUILD_NUMBER_INDEX = 2;
var lines = await File.ReadAllLinesAsync(Environment.GetMetadataPath(), Encoding.UTF8);
return int.Parse(lines[BUILD_NUMBER_INDEX]);
}
private async Task<int> IncreaseBuildNumber()
{
const int BUILD_NUMBER_INDEX = 2;