示例#1
0
        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);
        }
示例#2
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) = 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);
        }
示例#3
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);
        }
示例#4
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);
        }
示例#5
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);
        }