Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 101 additions & 26 deletions src/NerdBank.GitVersioning/ReleaseManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,52 @@ public enum ReleaseManagerOutputMode
/// <param name="unformattedCommitMessage">
/// An optional, custom message to use for the commit that sets the new version number. May use <c>{0}</c> to substitute the new version number.
/// </param>
public void PrepareRelease(string projectDirectory, string releaseUnstableTag = null, Version nextVersion = null, VersionOptions.ReleaseVersionIncrement? versionIncrement = null, ReleaseManagerOutputMode outputMode = default, string unformattedCommitMessage = null)
/// <param name="whatIf">
/// If true, simulates the prepare-release operation and returns the versions that would be set without making any changes.
/// </param>
/// <returns>
/// A <see cref="ReleaseInfo"/> object containing information about the release when <paramref name="whatIf"/> is true; otherwise null.
/// </returns>
public ReleaseInfo PrepareRelease(string projectDirectory, string releaseUnstableTag = null, Version nextVersion = null, VersionOptions.ReleaseVersionIncrement? versionIncrement = null, ReleaseManagerOutputMode outputMode = default, string unformattedCommitMessage = null, bool whatIf = false)
{
return this.PrepareReleaseCore(projectDirectory, releaseUnstableTag, nextVersion, versionIncrement, outputMode, unformattedCommitMessage, whatIf);
}

/// <summary>
/// Core implementation of prepare-release functionality that can either simulate or execute the operation.
/// </summary>
/// <param name="projectDirectory">
/// The path to the directory that may (or its ancestors may) define the version file.
/// </param>
/// <param name="releaseUnstableTag">
/// An optional prerelease tag to apply on the release branch.
/// If not specified, any existing prerelease tag will be removed from the release.
/// The preceding hyphen may be omitted.
/// </param>
/// <param name="nextVersion">
/// The version to use for the next release.
/// If not specified, the next version will be determined automatically by incrementing the current
/// version based on the current version and the <paramref name="versionIncrement"/> setting in <c>version.json</c>.
/// Parameter will be ignored if the current branch is a release branch.
/// </param>
/// <param name="versionIncrement">
/// The increment to apply in order to determine the next version on the current branch.
/// If specified, value will be used instead of the increment specified in <c>version.json</c>.
/// Parameter will be ignored if the current branch is a release branch.
/// </param>
/// <param name="outputMode">
/// The output format to use for writing to stdout.
/// </param>
/// <param name="unformattedCommitMessage">
/// An optional, custom message to use for the commit that sets the new version number. May use <c>{0}</c> to substitute the new version number.
/// </param>
/// <param name="whatIf">
/// If true, simulates the prepare-release operation and returns the versions that would be set without making any changes.
/// </param>
/// <returns>
/// A <see cref="ReleaseInfo"/> object containing information about the release when <paramref name="whatIf"/> is true; otherwise null.
/// </returns>
private ReleaseInfo PrepareReleaseCore(string projectDirectory, string releaseUnstableTag, Version nextVersion, VersionOptions.ReleaseVersionIncrement? versionIncrement, ReleaseManagerOutputMode outputMode, string unformattedCommitMessage, bool whatIf)
{
Requires.NotNull(projectDirectory, nameof(projectDirectory));

Expand Down Expand Up @@ -164,16 +209,30 @@ public void PrepareRelease(string projectDirectory, string releaseUnstableTag =
{
if (outputMode == ReleaseManagerOutputMode.Text)
{
this.stdout.WriteLine($"{releaseBranchName} branch advanced from {versionOptions.Version} to {releaseVersion}.");
if (whatIf)
{
this.stdout.WriteLine($"What-if: {releaseBranchName} branch would be advanced from {versionOptions.Version} to {releaseVersion}.");
}
else
{
this.stdout.WriteLine($"{releaseBranchName} branch advanced from {versionOptions.Version} to {releaseVersion}.");
}
}
else

var releaseInfo = new ReleaseInfo(new ReleaseBranchInfo(releaseBranchName, repository.Head.Tip.Id.ToString(), releaseVersion));

if (whatIf)
{
return releaseInfo;
}

if (outputMode == ReleaseManagerOutputMode.Json)
{
var releaseInfo = new ReleaseInfo(new ReleaseBranchInfo(releaseBranchName, repository.Head.Tip.Id.ToString(), releaseVersion));
this.WriteToOutput(releaseInfo);
}

this.UpdateVersion(context, versionOptions.Version, releaseVersion, unformattedCommitMessage);
return;
return null;
}

SemanticVersion nextDevVersion = this.GetNextDevVersion(versionOptions, nextVersion, versionIncrement);
Expand All @@ -193,25 +252,38 @@ public void PrepareRelease(string projectDirectory, string releaseUnstableTag =
throw new ReleasePreparationException(ReleasePreparationError.BranchAlreadyExists);
}

if (outputMode == ReleaseManagerOutputMode.Text)
{
if (whatIf)
{
this.stdout.WriteLine($"What-if: {releaseBranchName} branch would track v{releaseVersion} stabilization and release.");
this.stdout.WriteLine($"What-if: {originalBranchName} branch would track v{nextDevVersion} development.");
}
else
{
this.stdout.WriteLine($"{releaseBranchName} branch now tracks v{releaseVersion} stabilization and release.");
this.stdout.WriteLine($"{originalBranchName} branch now tracks v{nextDevVersion} development.");
}
}

var originalBranchInfo = new ReleaseBranchInfo(originalBranchName, repository.Head.Tip.Sha, nextDevVersion);
var releaseBranchInfo = new ReleaseBranchInfo(releaseBranchName, repository.Head.Tip.Id.ToString(), releaseVersion);
var releaseInfoResult = new ReleaseInfo(originalBranchInfo, releaseBranchInfo);

if (whatIf)
{
return releaseInfoResult;
}

// create release branch and update version
Branch releaseBranch = repository.CreateBranch(releaseBranchName);
global::LibGit2Sharp.Commands.Checkout(repository, releaseBranch);
this.UpdateVersion(context, versionOptions.Version, releaseVersion, unformattedCommitMessage);

if (outputMode == ReleaseManagerOutputMode.Text)
{
this.stdout.WriteLine($"{releaseBranchName} branch now tracks v{releaseVersion} stabilization and release.");
}

// update version on main branch
global::LibGit2Sharp.Commands.Checkout(repository, originalBranchName);
this.UpdateVersion(context, versionOptions.Version, nextDevVersion, unformattedCommitMessage);

if (outputMode == ReleaseManagerOutputMode.Text)
{
this.stdout.WriteLine($"{originalBranchName} branch now tracks v{nextDevVersion} development.");
}

// Merge release branch back to main branch
var mergeOptions = new MergeOptions()
{
Expand All @@ -222,12 +294,21 @@ public void PrepareRelease(string projectDirectory, string releaseUnstableTag =

if (outputMode == ReleaseManagerOutputMode.Json)
{
var originalBranchInfo = new ReleaseBranchInfo(originalBranchName, repository.Head.Tip.Sha, nextDevVersion);
var releaseBranchInfo = new ReleaseBranchInfo(releaseBranchName, repository.Branches[releaseBranchName].Tip.Id.ToString(), releaseVersion);
var releaseInfo = new ReleaseInfo(originalBranchInfo, releaseBranchInfo);

this.WriteToOutput(releaseInfo);
// Update the commit IDs with the actual final commit IDs after all operations
var finalOriginalBranchInfo = new ReleaseBranchInfo(originalBranchName, repository.Head.Tip.Sha, nextDevVersion);
var finalReleaseBranchInfo = new ReleaseBranchInfo(releaseBranchName, repository.Branches[releaseBranchName].Tip.Id.ToString(), releaseVersion);
var finalReleaseInfo = new ReleaseInfo(finalOriginalBranchInfo, finalReleaseBranchInfo);

this.WriteToOutput(finalReleaseInfo);
}

return null;
}

public void WriteToOutput(ReleaseInfo releaseInfo)
{
string json = JsonConvert.SerializeObject(releaseInfo, Formatting.Indented, new SemanticVersionJsonConverter());
this.stdout.WriteLine(json);
}

private static bool IsVersionDecrement(SemanticVersion oldVersion, SemanticVersion newVersion)
Expand Down Expand Up @@ -384,12 +465,6 @@ private SemanticVersion GetNextDevVersion(VersionOptions versionOptions, Version
return nextDevVersion.SetFirstPrereleaseTag(versionOptions.ReleaseOrDefault.FirstUnstableTagOrDefault);
}

private void WriteToOutput(ReleaseInfo releaseInfo)
{
string json = JsonConvert.SerializeObject(releaseInfo, Formatting.Indented, new SemanticVersionJsonConverter());
this.stdout.WriteLine(json);
}

/// <summary>
/// Exception indicating an error during preparation of a release.
/// </summary>
Expand Down
35 changes: 23 additions & 12 deletions src/nbgv/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ private static RootCommand BuildCommandLine()
var source = new Option<string[]>("--source", ["-s"])
{
Description = $"The URI(s) of the NuGet package source(s) used to determine the latest stable version of the {PackageId} package. This setting overrides all of the sources specified in the NuGet.Config files.",
DefaultValueFactory = _ => Array.Empty<string>(),
Arity = ArgumentArity.OneOrMore,
AllowMultipleArgumentsPerToken = true,
};
Expand All @@ -132,7 +131,7 @@ private static RootCommand BuildCommandLine()
{
Description = "The path to the project or project directory. The default is the current directory.",
};
var metadata = new Option<string[]>("--metadata", Array.Empty<string>())
var metadata = new Option<string[]>("--metadata")
{
Description = "Adds an identifier to the build metadata part of a semantic version.",
Arity = ArgumentArity.OneOrMore,
Expand Down Expand Up @@ -258,7 +257,7 @@ private static RootCommand BuildCommandLine()
{
Description = "The path to the project or project directory used to calculate the version. The default is the current directory. Ignored if the -v option is specified.",
};
var metadata = new Option<string[]>("--metadata", Array.Empty<string>())
var metadata = new Option<string[]>("--metadata")
{
Description = "Adds an identifier to the build metadata part of a semantic version.",
Arity = ArgumentArity.OneOrMore,
Expand All @@ -280,14 +279,13 @@ private static RootCommand BuildCommandLine()
{
Description = "Defines a few common version variables as cloud build variables, with a \"Git\" prefix (e.g. GitBuildVersion, GitBuildVersionSimple, GitAssemblyInformationalVersion).",
};
var skipCloudBuildNumber = new Option<bool>("--skip-cloud-build-number", Array.Empty<string>())
var skipCloudBuildNumber = new Option<bool>("--skip-cloud-build-number")
{
Description = "Do not emit the cloud build variable to set the build number. This is useful when you want to set other cloud build variables but not the build number.",
};
var define = new Option<string[]>("--define", ["-d"])
{
Description = "Additional cloud build variables to define. Each should be in the NAME=VALUE syntax.",
DefaultValueFactory = _ => Array.Empty<string>(),
Arity = ArgumentArity.OneOrMore,
AllowMultipleArgumentsPerToken = true,
};
Expand Down Expand Up @@ -323,22 +321,26 @@ private static RootCommand BuildCommandLine()
{
Description = "The path to the project or project directory. The default is the current directory.",
};
var nextVersion = new Option<string>("--nextVersion", Array.Empty<string>())
var nextVersion = new Option<string>("--nextVersion")
{
Description = "The version to set for the current branch. If omitted, the next version is determined automatically by incrementing the current version.",
};
var versionIncrement = new Option<string>("--versionIncrement", Array.Empty<string>())
var versionIncrement = new Option<string>("--versionIncrement")
{
Description = "Overrides the 'versionIncrement' setting set in version.json for determining the next version of the current branch.",
};
var format = new Option<string>("--format", ["-f"])
{
Description = $"The format to write information about the release. Allowed values are: {string.Join(", ", SupportedFormats)}. The default is {DefaultOutputFormat}.",
};
var unformattedCommitMessage = new Option<string>("--commit-message-pattern", Array.Empty<string>())
var unformattedCommitMessage = new Option<string>("--commit-message-pattern")
{
Description = "A custom message to use for the commit that changes the version number. May include {0} for the version number. If not specified, the default is \"Set version to '{0}'\".",
};
var whatIf = new Option<bool>("--what-if")
{
Description = "Simulates the prepare-release operation and prints the new version that would be set, but does not actually make any changes.",
};
var tagArgument = new Argument<string>("tag")
{
Description = "The prerelease tag to apply on the release branch (if any). If not specified, any existing prerelease tag will be removed. The preceding hyphen may be omitted.",
Expand All @@ -351,6 +353,7 @@ private static RootCommand BuildCommandLine()
versionIncrement,
format,
unformattedCommitMessage,
whatIf,
tagArgument,
};

Expand All @@ -362,7 +365,8 @@ private static RootCommand BuildCommandLine()
var formatValue = parseResult.GetValue(format);
var tagArgumentValue = parseResult.GetValue(tagArgument);
var unformattedCommitMessageValue = parseResult.GetValue(unformattedCommitMessage);
return await OnPrepareReleaseCommand(projectValue, nextVersionValue, versionIncrementValue, formatValue, tagArgumentValue, unformattedCommitMessageValue);
var whatIfValue = parseResult.GetValue(whatIf);
return await OnPrepareReleaseCommand(projectValue, nextVersionValue, versionIncrementValue, formatValue, tagArgumentValue, unformattedCommitMessageValue, whatIfValue);
});
}

Expand Down Expand Up @@ -883,7 +887,7 @@ private static Task<int> OnCloudCommand(string project, string[] metadata, strin
return Task.FromResult((int)ExitCodes.OK);
}

private static Task<int> OnPrepareReleaseCommand(string project, string nextVersion, string versionIncrement, string format, string tag, string unformattedCommitMessage)
private static Task<int> OnPrepareReleaseCommand(string project, string nextVersion, string versionIncrement, string format, string tag, string unformattedCommitMessage, bool whatIf)
{
// validate project path property
string searchPath = GetSpecifiedOrCurrentDirectoryPath(project);
Expand Down Expand Up @@ -949,11 +953,18 @@ private static Task<int> OnPrepareReleaseCommand(string project, string nextVers
}
}

// run prepare-release
// run prepare-release or simulate
try
{
var releaseManager = new ReleaseManager(Console.Out, Console.Error);
releaseManager.PrepareRelease(searchPath, tag, nextVersionParsed, versionIncrementParsed, outputMode, unformattedCommitMessage);

ReleaseManager.ReleaseInfo releaseInfo = releaseManager.PrepareRelease(searchPath, tag, nextVersionParsed, versionIncrementParsed, outputMode, unformattedCommitMessage, whatIf);

if (whatIf && outputMode == ReleaseManager.ReleaseManagerOutputMode.Json)
{
releaseManager.WriteToOutput(releaseInfo);
}

return Task.FromResult((int)ExitCodes.OK);
}
catch (ReleaseManager.ReleasePreparationException ex)
Expand Down
Loading