Exemple #1
0
        public async Task Given_Parameters_When_MergePrAsync_Invoked_Then_It_Should_Return_Property()
        {
            var sha    = this._fixture.Create <string>();
            var merged = this._fixture.Create <bool>();

            var prm = new PullRequestMerge().SetValue("Merged", merged);

            var prc = new Mock <IPullRequestsClient>();

            prc.Setup(p => p.Merge(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <MergePullRequest>())).ReturnsAsync(prm);

            var client = new Mock <IGitHubClient>();

            client.SetupGet(p => p.PullRequest).Returns(prc.Object);

            var options = new Options();

            var handler = new MessageHandler()
                          .SetValue("Sha", sha)
                          .WithGitHubClient(client.Object);

            await handler.MergePrAsync(options).ConfigureAwait(false);

            handler.IsMerged.Should().Be(prm.Merged);
        }
Exemple #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);
        }
Exemple #3
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 gitRepoName = options.GitRepoName;
            var repoUrl     = Common.makeRepoUrl(options.GithubOrgName, gitRepoName);

            var gitHubClient = new GitHubClient(options);
            var gitHubRepo   = gitHubClient.GetRepositoryFromUrl(repoUrl);

            if (string.IsNullOrWhiteSpace(options.PullRequestUrl.Trim().Replace("\"", "")))
            {
                Logger.Info("The passed PullRequestUrl was empty or missing. Trying to release without merging a PR.");

                using (var gitClient = GitClient.FromRemote(repoUrl))
                {
                    // Create the release branch, since if there is no PR, the release branch did not exist previously
                    gitClient.Fetch();
                    if (gitClient.LocalBranchExists($"origin/{options.ReleaseBranch}"))
                    {
                        Logger.Error("The PullRequestUrl was empty or missing, but the release branch already exists, so presuming this step already ran.");
                    }
                    else
                    {
                        gitClient.CheckoutRemoteBranch(options.CandidateBranch);
                        gitClient.ForcePush(options.ReleaseBranch);
                    }

                    gitClient.Fetch();
                    gitClient.CheckoutRemoteBranch(options.ReleaseBranch);

                    FinalizeRelease(gitHubClient, gitClient, gitHubRepo, gitRepoName, repoUrl);
                }

                return(0);
            }

            var(repoName, pullRequestId) = Common.ExtractPullRequestInfo(options.PullRequestUrl);
            if (gitRepoName != repoName)
            {
                Logger.Error($"Repository names given do not match. Repository name given: {gitRepoName}, PR URL repository name: {repoName}.");
                return(1);
            }

            // 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 = Common.makeRepoUrl(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(gitClient, options.Version);

                        var releaseHashes = options.EngineVersions.Replace("\"", "").Split(" ")
                                            .Select(version => $"{version.Trim()}")
                                            .Select(BuildkiteAgent.GetMetadata)
                                            .Select(hash => $"{hash}")
                                            .ToList();

                        Common.UpdateUnrealEngineVersionFile(gitClient, releaseHashes);

                        // 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.");

                using (var gitClient = GitClient.FromRemote(repoUrl))
                {
                    // Create GitHub release in the repo
                    gitClient.Fetch();
                    gitClient.CheckoutRemoteBranch(options.ReleaseBranch);
                    FinalizeRelease(gitHubClient, gitClient, gitHubRepo, gitRepoName, repoUrl);
                }
            }
            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: {e.Message}");
                return(1);
            }

            return(0);
        }
Exemple #4
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);
        }