private void FinalizeRelease(GitHubClient gitHubClient, GitClient gitClient, Repository gitHubRepo, string gitRepoName, string repoUrl) { // This uploads the commit hashes of the merge into release. // When run against UnrealGDK, the UnrealEngine hashes are used to update the unreal-engine.version file to include the UnrealEngine release commits. BuildkiteAgent.SetMetaData(options.ReleaseBranch, gitClient.GetHeadCommit().Sha); // TODO: UNR-3615 - Fix this so it does not throw Octokit.ApiValidationException: Reference does not exist. // Delete candidate branch. //gitHubClient.DeleteBranch(gitHubClient.GetRepositoryFromUrl(repoUrl), options.CandidateBranch); var release = CreateRelease(gitHubClient, gitHubRepo, gitClient, gitRepoName); BuildkiteAgent.Annotate(AnnotationLevel.Info, "draft-releases", string.Format(releaseAnnotationTemplate, release.HtmlUrl, gitRepoName), true); Logger.Info("Release Successful!"); Logger.Info("Release hash: {0}", gitClient.GetHeadCommit().Sha); Logger.Info("Draft release: {0}", release.HtmlUrl); CreatePRFromReleaseToSource(gitHubClient, gitHubRepo, repoUrl, gitRepoName, gitClient); }
/* * This tool is designed to execute most of the git operations required when releasing: * 1. Merge the RC PR into the release branch. * 2. Draft a GitHub release using the changelog notes. * 3. Open a PR from the release-branch into source-branch. */ public int Run() { Common.VerifySemanticVersioningFormat(options.Version); var(repoName, pullRequestId) = ExtractPullRequestInfo(options.PullRequestUrl); var gitHubClient = new GitHubClient(options); var repoUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, repoName); var gitHubRepo = gitHubClient.GetRepositoryFromUrl(repoUrl); // Check if the PR has been merged already. // If it has, log the PR URL and move on. // This ensures the idempotence of the pipeline. if (gitHubClient.GetMergeState(gitHubRepo, pullRequestId) == GitHubClient.MergeState.AlreadyMerged) { Logger.Info("Candidate branch has already merged into release branch. No merge operation will be attempted."); // Check if a PR has already been opened from release branch into source branch. // If it has, log the PR URL and move on. // This ensures the idempotence of the pipeline. var githubOrg = options.GithubOrgName; var branchFrom = $"{options.CandidateBranch}-cleanup"; var branchTo = options.SourceBranch; if (!gitHubClient.TryGetPullRequest(gitHubRepo, githubOrg, branchFrom, branchTo, out var pullRequest)) { try { using (var gitClient = GitClient.FromRemote(repoUrl)) { gitClient.CheckoutRemoteBranch(options.ReleaseBranch); gitClient.ForcePush(branchFrom); } pullRequest = gitHubClient.CreatePullRequest(gitHubRepo, branchFrom, branchTo, string.Format(PullRequestNameTemplate, options.Version, options.ReleaseBranch, options.SourceBranch), string.Format(pullRequestBody, options.ReleaseBranch, options.SourceBranch)); } catch (Octokit.ApiValidationException e) { // Handles the case where source-branch (default master) and release-branch (default release) are identical, so there is no need to merge source-branch back into release-branch. if (e.ApiError.Errors.Count > 0 && e.ApiError.Errors[0].Message.Contains("No commits between")) { Logger.Info(e.ApiError.Errors[0].Message); Logger.Info("No PR will be created."); return(0); } throw; } } else { Logger.Info("A PR has already been opened from release branch into source branch: {0}", pullRequest.HtmlUrl); } var prAnnotation = string.Format(prAnnotationTemplate, pullRequest.HtmlUrl, repoName, options.ReleaseBranch, options.SourceBranch); BuildkiteAgent.Annotate(AnnotationLevel.Info, "release-into-source-prs", prAnnotation, true); Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl); Logger.Info("Successfully created PR from release branch into source branch."); Logger.Info("Merge hash: {0}", pullRequest.MergeCommitSha); return(0); } var remoteUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, repoName); try { // 1. Clones the source repo. using (var gitClient = GitClient.FromRemote(remoteUrl)) { // 2. Checks out the candidate branch, which defaults to 4.xx-SpatialOSUnrealGDK-x.y.z-rc in UnrealEngine and x.y.z-rc in all other repos. gitClient.CheckoutRemoteBranch(options.CandidateBranch); // 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). switch (repoName) { case "UnrealEngine": UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); UpdateVersionFile(gitClient, options.Version, UnrealGDKExampleProjectVersionFile); break; case "UnrealGDK": UpdateChangeLog(ChangeLogFilename, options, gitClient); var releaseHashes = options.EngineVersions.Split(" ") .Select(version => $"{version.Trim()}-release") .Select(BuildkiteAgent.GetMetadata) .Select(hash => $"UnrealEngine-{hash}") .ToList(); UpdateUnrealEngineVersionFile(releaseHashes, gitClient); break; case "UnrealGDKExampleProject": UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); break; case "UnrealGDKTestGyms": UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); break; case "UnrealGDKEngineNetTest": UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); break; case "TestGymBuildKite": UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile); break; } // 4. Commit changes and push them to a remote candidate branch. gitClient.Commit(string.Format(CandidateCommitMessageTemplate, options.Version)); gitClient.ForcePush(options.CandidateBranch); } // Since we've pushed changes, we need to wait for all checks to pass before attempting to merge it. var startTime = DateTime.Now; while (true) { if (DateTime.Now.Subtract(startTime) > TimeSpan.FromHours(12)) { throw new Exception($"Exceeded timeout waiting for PR to be mergeable: {options.PullRequestUrl}"); } if (gitHubClient.GetMergeState(gitHubRepo, pullRequestId) == GitHubClient.MergeState.ReadyToMerge) { Logger.Info($"{options.PullRequestUrl} is mergeable. Attempting to merge."); break; } Logger.Info($"{options.PullRequestUrl} is not in a mergeable state, will query mergeability again in one minute."); Thread.Sleep(TimeSpan.FromMinutes(1)); } PullRequestMerge mergeResult = null; while (true) { // Merge into release try { mergeResult = gitHubClient.MergePullRequest(gitHubRepo, pullRequestId, PullRequestMergeMethod.Merge); } catch (Octokit.PullRequestNotMergeableException e) {} // Will be covered by log below if (DateTime.Now.Subtract(startTime) > TimeSpan.FromHours(12)) { throw new Exception($"Exceeded timeout waiting for PR to be mergeable: {options.PullRequestUrl}"); } if (!mergeResult.Merged) { Logger.Info($"Was unable to merge pull request at: {options.PullRequestUrl}. Received error: {mergeResult.Message}"); Logger.Info($"{options.PullRequestUrl} is not in a mergeable state, will query mergeability again in one minute."); Thread.Sleep(TimeSpan.FromMinutes(1)); } else { break; } } Logger.Info($"{options.PullRequestUrl} had been merged."); // This uploads the commit hashes of the merge into release. // When run against UnrealGDK, the UnrealEngine hashes are used to update the unreal-engine.version file to include the UnrealEngine release commits. BuildkiteAgent.SetMetaData(options.ReleaseBranch, mergeResult.Sha); //TODO: UNR-3615 - Fix this so it does not throw Octokit.ApiValidationException: Reference does not exist. // Delete candidate branch. //gitHubClient.DeleteBranch(gitHubClient.GetRepositoryFromUrl(repoUrl), options.CandidateBranch); using (var gitClient = GitClient.FromRemote(repoUrl)) { // Create GitHub release in the repo gitClient.Fetch(); gitClient.CheckoutRemoteBranch(options.ReleaseBranch); var release = CreateRelease(gitHubClient, gitHubRepo, gitClient, repoName); BuildkiteAgent.Annotate(AnnotationLevel.Info, "draft-releases", string.Format(releaseAnnotationTemplate, release.HtmlUrl, repoName), true); Logger.Info("Release Successful!"); Logger.Info("Release hash: {0}", gitClient.GetHeadCommit().Sha); Logger.Info("Draft release: {0}", release.HtmlUrl); } // Check if a PR has already been opened from release branch into source branch. // If it has, log the PR URL and move on. // This ensures the idempotence of the pipeline. var githubOrg = options.GithubOrgName; var branchFrom = $"{options.CandidateBranch}-cleanup"; var branchTo = options.SourceBranch; if (!gitHubClient.TryGetPullRequest(gitHubRepo, githubOrg, branchFrom, branchTo, out var pullRequest)) { try { using (var gitClient = GitClient.FromRemote(repoUrl)) { gitClient.CheckoutRemoteBranch(options.ReleaseBranch); gitClient.ForcePush(branchFrom); } pullRequest = gitHubClient.CreatePullRequest(gitHubRepo, branchFrom, branchTo, string.Format(PullRequestNameTemplate, options.Version, options.ReleaseBranch, options.SourceBranch), string.Format(pullRequestBody, options.ReleaseBranch, options.SourceBranch)); } catch (Octokit.ApiValidationException e) { // Handles the case where source-branch (default master) and release-branch (default release) are identical, so there is no need to merge source-branch back into release-branch. if (e.ApiError.Errors.Count > 0 && e.ApiError.Errors[0].Message.Contains("No commits between")) { Logger.Info(e.ApiError.Errors[0].Message); Logger.Info("No PR will be created."); return(0); } throw; } } else { Logger.Info("A PR has already been opened from release branch into source branch: {0}", pullRequest.HtmlUrl); } var prAnnotation = string.Format(prAnnotationTemplate, pullRequest.HtmlUrl, repoName, options.ReleaseBranch, options.SourceBranch); BuildkiteAgent.Annotate(AnnotationLevel.Info, "release-into-source-prs", prAnnotation, true); Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl); Logger.Info($"Successfully created PR for merging {options.ReleaseBranch} into {options.SourceBranch}."); } catch (Exception e) { Logger.Error(e, $"ERROR: Unable to merge {options.CandidateBranch} into {options.ReleaseBranch} and/or clean up by merging {options.ReleaseBranch} into {options.SourceBranch}. Error: {0}", e); return(1); } return(0); }
/* * This tool is designed to be used with a robot Github account. When we prep a release: * 1. Clones the source repo. * 2. Checks out the source branch, which defaults to 4.xx-SpatialOSUnrealGDK in UnrealEngine and master in all other repos. * 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). * 4. Commit changes and push them to a remote candidate branch. * 5. IF the release branch does not exist, creates it from the source branch and pushes it to the remote. * 6. Opens a PR for merging the RC branch into the release branch. */ public int Run() { Common.VerifySemanticVersioningFormat(options.Version); var gitRepoName = options.GitRepoName; var remoteUrl = Common.makeRepoUrl(options.GithubOrgName, gitRepoName); try { // 1. Clones the source repo. using (var gitClient = GitClient.FromRemote(remoteUrl)) { if (!gitClient.LocalBranchExists($"origin/{options.CandidateBranch}")) { // 2. Checks out the source branch, which defaults to 4.xx-SpatialOSUnrealGDK in UnrealEngine and master in all other repos. gitClient.CheckoutRemoteBranch(options.SourceBranch); // 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). // UpdateVersionFilesWithEngine returns a bool to indicate if anything has changed, we could use this to only push when // version files etc have changed which may be reasonable but might have side-effects as our github ci interactions are fragile Common.UpdateVersionFilesWithEngine(gitClient, gitRepoName, options.Version, options.EngineVersions, Logger, "-rc"); // 4. Commit changes and push them to a remote candidate branch. gitClient.Commit(string.Format(CandidateCommitMessageTemplate, options.Version)); gitClient.ForcePush(options.CandidateBranch); Logger.Info($"Updated branch '{options.CandidateBranch}' for the release candidate release."); } // 5. IF the release branch does not exist, creates it from the source branch and pushes it to the remote. if (!gitClient.LocalBranchExists($"origin/{options.ReleaseBranch}")) { Logger.Info("The release branch {0} does not exist! Going ahead with the PR-less release process.", options.ReleaseBranch); Logger.Info("Release candidate head hash: {0}", gitClient.GetHeadCommit().Sha); var branchAnnotation = string.Format(branchAnnotationTemplate, $"https://github.com/{options.GithubOrgName}/{options.GitRepoName}/tree/{options.CandidateBranch}", options.GitRepoName, options.ReleaseBranch); BuildkiteAgent.Annotate(AnnotationLevel.Info, "candidate-into-release-prs", branchAnnotation, true); return(0); } // 6. Opens a PR for merging the RC branch into the release branch. var gitHubClient = new GitHubClient(options); var gitHubRepo = gitHubClient.GetRepositoryFromUrl(remoteUrl); var githubOrg = options.GithubOrgName; var branchFrom = options.CandidateBranch; var branchTo = options.ReleaseBranch; // Only open a PR if one does not exist yet. if (!gitHubClient.TryGetPullRequest(gitHubRepo, githubOrg, branchFrom, branchTo, out var pullRequest)) { Logger.Info("No PR exists. Attempting to open a new PR"); pullRequest = gitHubClient.CreatePullRequest(gitHubRepo, branchFrom, branchTo, string.Format(PullRequestTemplate, options.Version), GetPullRequestBody(options.GitRepoName, options.CandidateBranch, options.ReleaseBranch)); } BuildkiteAgent.SetMetaData($"{options.GitRepoName}-{options.SourceBranch}-pr-url", pullRequest.HtmlUrl); var prAnnotation = string.Format(prAnnotationTemplate, pullRequest.HtmlUrl, options.GitRepoName, options.CandidateBranch, options.ReleaseBranch); BuildkiteAgent.Annotate(AnnotationLevel.Info, "candidate-into-release-prs", prAnnotation, true); Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl); Logger.Info("Successfully created pull request for the release!"); Logger.Info("PR head hash: {0}", gitClient.GetHeadCommit().Sha); } } catch (Exception e) { Logger.Error(e, "ERROR: Unable to prep release candidate branch. Error: {0}", e); return(1); } return(0); }
/* * This tool is designed to be used with a robot Github account. When we prep a release: * 1. Clones the source repo. * 2. Checks out the source branch, which defaults to 4.xx-SpatialOSUnrealGDK in UnrealEngine and master in all other repos. * 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). * 4. Commit changes and push them to a remote candidate branch. * 5. IF the release branch does not exist, creates it from the source branch and pushes it to the remote. * 6. Opens a PR for merging the RC branch into the release branch. */ public int Run() { Common.VerifySemanticVersioningFormat(options.Version); var remoteUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, options.GitRepoName); try { var gitHubClient = new GitHubClient(options); // 1. Clones the source repo. using (var gitClient = GitClient.FromRemote(remoteUrl)) { if (!gitClient.LocalBranchExists($"origin/{options.CandidateBranch}")) { // 2. Checks out the source branch, which defaults to 4.xx-SpatialOSUnrealGDK in UnrealEngine and master in all other repos. gitClient.CheckoutRemoteBranch(options.SourceBranch); // 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). switch (options.GitRepoName) { case "UnrealGDK": UpdateChangeLog(ChangeLogFilename, options, gitClient); UpdatePluginFile(pluginFileName, gitClient); var engineCandidateBranches = options.EngineVersions.Split(" ") .Select(engineVersion => $"HEAD {engineVersion.Trim()}-{options.Version}-rc") .ToList(); UpdateUnrealEngineVersionFile(engineCandidateBranches, gitClient); break; case "UnrealEngine": UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKExampleProjectVersionFile); break; case "UnrealGDKExampleProject": UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); break; case "UnrealGDKTestGyms": UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); break; case "UnrealGDKEngineNetTest": UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); break; case "TestGymBuildKite": UpdateVersionFile(gitClient, $"{options.Version}-rc", UnrealGDKVersionFile); break; } // 4. Commit changes and push them to a remote candidate branch. gitClient.Commit(string.Format(CandidateCommitMessageTemplate, options.Version)); gitClient.ForcePush(options.CandidateBranch); } // 5. IF the release branch does not exist, creates it from the source branch and pushes it to the remote. if (!gitClient.LocalBranchExists($"origin/{options.ReleaseBranch}")) { gitClient.Fetch(); gitClient.CheckoutRemoteBranch(options.SourceBranch); gitClient.ForcePush(options.ReleaseBranch); } // 6. Opens a PR for merging the RC branch into the release branch. var gitHubRepo = gitHubClient.GetRepositoryFromUrl(remoteUrl); var githubOrg = options.GithubOrgName; var branchFrom = options.CandidateBranch; var branchTo = options.ReleaseBranch; // Only open a PR if one does not exist yet. if (!gitHubClient.TryGetPullRequest(gitHubRepo, githubOrg, branchFrom, branchTo, out var pullRequest)) { Logger.Info("No PR exists. Attempting to open a new PR"); pullRequest = gitHubClient.CreatePullRequest(gitHubRepo, branchFrom, branchTo, string.Format(PullRequestTemplate, options.Version), GetPullRequestBody(options.GitRepoName, options.CandidateBranch, options.ReleaseBranch)); } BuildkiteAgent.SetMetaData($"{options.GitRepoName}-{options.SourceBranch}-pr-url", pullRequest.HtmlUrl); var prAnnotation = string.Format(prAnnotationTemplate, pullRequest.HtmlUrl, options.GitRepoName, options.CandidateBranch, options.ReleaseBranch); BuildkiteAgent.Annotate(AnnotationLevel.Info, "candidate-into-release-prs", prAnnotation, true); Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl); Logger.Info("Successfully created pull request for the release!"); Logger.Info("PR head hash: {0}", gitClient.GetHeadCommit().Sha); } } catch (Exception e) { Logger.Error(e, "ERROR: Unable to prep release candidate branch. Error: {0}", e); return(1); } return(0); }
/* * This tool is designed to execute most of the git operations required when releasing: * 1. Merge the RC PR into the release branch. * 2. Draft a GitHub release using the changelog notes. * 3. Open a PR from the release-branch into source-branch. */ public int Run() { Common.VerifySemanticVersioningFormat(options.Version); var(repoName, pullRequestId) = Common.ExtractPullRequestInfo(options.PullRequestUrl); var gitHubClient = new GitHubClient(options); var repoUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, repoName); var gitHubRepo = gitHubClient.GetRepositoryFromUrl(repoUrl); // Check if the PR has been merged already. // If it has, log the PR URL and move on. // This ensures the idempotence of the pipeline. if (gitHubClient.GetMergeState(gitHubRepo, pullRequestId) == GitHubClient.MergeState.AlreadyMerged) { Logger.Info("Candidate branch has already merged into release branch. No merge operation will be attempted."); // null for GitClient will let it create one if necessary CreatePRFromReleaseToSource(gitHubClient, gitHubRepo, repoUrl, repoName, null); return(0); } var remoteUrl = string.Format(Common.RepoUrlTemplate, options.GithubOrgName, repoName); try { // Only do something for the UnrealGDK, since the other repos should have been prepped by the PrepFullReleaseCommand. if (repoName == "UnrealGDK") { // 1. Clones the source repo. using (var gitClient = GitClient.FromRemote(remoteUrl)) { // 2. Checks out the candidate branch, which defaults to 4.xx-SpatialOSUnrealGDK-x.y.z-rc in UnrealEngine and x.y.z-rc in all other repos. gitClient.CheckoutRemoteBranch(options.CandidateBranch); // 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG). Common.UpdateChangeLog(ChangeLogFilename, options.Version, gitClient, ChangeLogReleaseHeadingTemplate); var releaseHashes = options.EngineVersions.Split(" ") .Select(version => $"{version.Trim()}-release") .Select(BuildkiteAgent.GetMetadata) .Select(hash => $"UnrealEngine-{hash}") .ToList(); UpdateUnrealEngineVersionFile(releaseHashes, gitClient); // 4. Commit changes and push them to a remote candidate branch. gitClient.Commit(string.Format(CandidateCommitMessageTemplate, options.Version)); gitClient.ForcePush(options.CandidateBranch); } } // Since we've (maybe) pushed changes, we need to wait for all checks to pass before attempting to merge it. var startTime = DateTime.Now; while (true) { if (DateTime.Now.Subtract(startTime) > TimeSpan.FromHours(12)) { throw new Exception($"Exceeded timeout waiting for PR to be mergeable: {options.PullRequestUrl}"); } if (gitHubClient.GetMergeState(gitHubRepo, pullRequestId) == GitHubClient.MergeState.ReadyToMerge) { Logger.Info($"{options.PullRequestUrl} is mergeable. Attempting to merge."); break; } Logger.Info($"{options.PullRequestUrl} is not in a mergeable state, will query mergeability again in one minute."); Thread.Sleep(TimeSpan.FromMinutes(1)); } PullRequestMerge mergeResult = null; while (true) { // Merge into release try { mergeResult = gitHubClient.MergePullRequest(gitHubRepo, pullRequestId, PullRequestMergeMethod.Merge, $"Merging final GDK for Unreal {options.Version} release"); } catch (Octokit.PullRequestNotMergeableException e) { Logger.Info($"Was unable to merge pull request at: {options.PullRequestUrl}. Received error: {e.Message}"); } if (DateTime.Now.Subtract(startTime) > TimeSpan.FromHours(12)) { throw new Exception($"Exceeded timeout waiting for PR to be mergeable: {options.PullRequestUrl}"); } if (!mergeResult.Merged) { Logger.Info($"{options.PullRequestUrl} is not in a mergeable state, will query mergeability again in one minute."); Thread.Sleep(TimeSpan.FromMinutes(1)); } else { break; } } Logger.Info($"{options.PullRequestUrl} had been merged."); // This uploads the commit hashes of the merge into release. // When run against UnrealGDK, the UnrealEngine hashes are used to update the unreal-engine.version file to include the UnrealEngine release commits. BuildkiteAgent.SetMetaData(options.ReleaseBranch, mergeResult.Sha); // TODO: UNR-3615 - Fix this so it does not throw Octokit.ApiValidationException: Reference does not exist. // Delete candidate branch. //gitHubClient.DeleteBranch(gitHubClient.GetRepositoryFromUrl(repoUrl), options.CandidateBranch); using (var gitClient = GitClient.FromRemote(repoUrl)) { // Create GitHub release in the repo gitClient.Fetch(); gitClient.CheckoutRemoteBranch(options.ReleaseBranch); var release = CreateRelease(gitHubClient, gitHubRepo, gitClient, repoName); BuildkiteAgent.Annotate(AnnotationLevel.Info, "draft-releases", string.Format(releaseAnnotationTemplate, release.HtmlUrl, repoName), true); Logger.Info("Release Successful!"); Logger.Info("Release hash: {0}", gitClient.GetHeadCommit().Sha); Logger.Info("Draft release: {0}", release.HtmlUrl); CreatePRFromReleaseToSource(gitHubClient, gitHubRepo, repoUrl, repoName, gitClient); } } catch (Exception e) { Logger.Error(e, $"ERROR: Unable to merge {options.CandidateBranch} into {options.ReleaseBranch} and/or clean up by merging {options.ReleaseBranch} into {options.SourceBranch}. Error: {0}", e.Message); return(1); } return(0); }