Beispiel #1
0
        private void CreatePRFromReleaseToSource(GitHubClient gitHubClient, Repository gitHubRepo, string repoUrl, string repoName, GitClient gitClient)
        {
            // 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 impotency 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
                {
                    if (gitClient == null)
                    {
                        using (gitClient = GitClient.FromRemote(repoUrl))
                        {
                            gitClient.CheckoutRemoteBranch(options.ReleaseBranch);
                            gitClient.ForcePush(branchFrom);
                        }
                    }
                    else
                    {
                        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;
                    }

                    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}.");
        }
        /*
         *     This tool is designed to execute most of the physical releasing:
         *         1. Merge the RC PR into develop.
         *         2. Draft the release using the changelog notes.
         *
         *     Fast-forwarding master up-to-date with develop and publishing the release
         *     are left up to the release sheriff.
         */
        public int Run()
        {
            try
            {
                var gitHubClient = new GitHubClient(options);

                var(repoName, pullRequestId) = ExtractPullRequestInfo(options.PullRequestUrl);

                var spatialOsRemote = string.Format(Common.RemoteUrlTemplate, Common.SpatialOsOrg, repoName);
                var gitHubRepo      = gitHubClient.GetRepositoryFromRemote(spatialOsRemote);

                // Merge into develop
                var mergeResult = gitHubClient.MergePullRequest(gitHubRepo, pullRequestId);

                if (!mergeResult.Merged)
                {
                    throw new InvalidOperationException(
                              $"Was unable to merge pull request at: {options.PullRequestUrl}. Received error: {mergeResult.Message}");
                }

                // Delete remote on the forked repository.
                var forkedRepoRemote = string.Format(Common.RemoteUrlTemplate, Common.GithubBotUser, repoName);
                var branchName       = string.Format(Common.ReleaseBranchNameTemplate, options.Version);
                // gitHubClient.DeleteBranch(gitHubClient.GetRepositoryFromRemote(forkedRepoRemote), branchName);

                var remoteUrl = string.Format(Common.RemoteUrlTemplate, Common.SpatialOsOrg, repoName);

                using (var gitClient = GitClient.FromRemote(remoteUrl))
                {
                    // Create release
                    gitClient.Fetch();
                    gitClient.CheckoutRemoteBranch(Common.DevelopBranch);
                    var release = CreateRelease(gitHubClient, gitHubRepo, gitClient, repoName);

                    Logger.Info("Release Successful!");
                    Logger.Info("Release hash: {0}", gitClient.GetHeadCommit().Sha);
                    Logger.Info("Draft release: {0}", release.HtmlUrl);

                    if (repoName == "gdk-for-unity" && BuildkiteMetadataSink.CanWrite(options))
                    {
                        using (var sink = new BuildkiteMetadataSink(options))
                        {
                            sink.WriteMetadata("gdk-for-unity-hash", gitClient.GetHeadCommit().Sha);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Error(e, "ERROR: Unable to release candidate branch. Error: {0}", e);
                return(1);
            }

            return(0);
        }
        /*
         *     This command does the necessary preparations for releasing an rc-branch:
         *         1. Re-pointing all <RepoName>Version.txt files
         */
        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))
                {
                    // 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).
                    if (Common.UpdateVersionFilesButNotEngine(gitClient, gitRepoName, options.Version, Logger))
                    {
                        // 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}' in preparation for the full release.");
                    }
                    else
                    {
                        Logger.Info($"Tried to update branch '{options.CandidateBranch}' in preparation for the full release, but it was already up-to-date.");
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Error(e, $"ERROR: Unable to update '{options.CandidateBranch}'. Error: {0}", e.Message);
                return(1);
            }

            return(0);
        }
Beispiel #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) = 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);
        }
Beispiel #5
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);
        }
Beispiel #6
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);
        }
Beispiel #7
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);
        }
Beispiel #8
0
        /*
         *     This tool is designed to be used with a robot Github account which has its own fork of the GDK
         *     repositories. This means that when we prep a release:
         *         1. Checkout our fork of the repo.
         *         2. Add the spatialos org remote to our local copy and fetch this remote.
         *         3. Checkout the spatialos/develop branch (the non-forked develop branch).
         *         4. Make the changes for prepping the release.
         *         5. Push this to an RC branch on the forked repository.
         *         6. Open a PR from the fork into the source repository.
         */
        public int Run()
        {
            var remoteUrl = string.Format(Common.RemoteUrlTemplate, Common.GithubBotUser, options.GitRepoName);

            try
            {
                var gitHubClient = new GitHubClient(options);

                using (var gitClient = GitClient.FromRemote(remoteUrl))
                {
                    // This does step 2 from above.
                    var spatialOsRemote =
                        string.Format(Common.RemoteUrlTemplate, Common.SpatialOsOrg, options.GitRepoName);
                    gitClient.AddRemote(Common.SpatialOsOrg, spatialOsRemote);
                    gitClient.Fetch(Common.SpatialOsOrg);

                    // This does step 3 from above.
                    gitClient.CheckoutRemoteBranch(Common.DevelopBranch, Common.SpatialOsOrg);

                    // This does step 4 from above.
                    using (new WorkingDirectoryScope(gitClient.RepositoryPath))
                    {
                        UpdateManifestJson(gitClient);
                        UpdateAllPackageJsons(gitClient);

                        if (options.ShouldUpdateGdkVersion)
                        {
                            UpdateGdkVersion(gitClient, options.PinnedGdkVersion);
                        }

                        if (!File.Exists(ChangeLogFilename))
                        {
                            throw new InvalidOperationException("Could not update the change log as the file," +
                                                                $" {ChangeLogFilename}, does not exist");
                        }

                        Logger.Info("Updating {0}...", ChangeLogFilename);

                        var changelog = File.ReadAllLines(ChangeLogFilename).ToList();
                        UpdateChangeLog(changelog, options);
                        File.WriteAllLines(ChangeLogFilename, changelog);
                        gitClient.StageFile(ChangeLogFilename);
                    }

                    // This does step 5 from above.
                    var branchName = string.Format(Common.ReleaseBranchNameTemplate, options.Version);
                    gitClient.Commit(string.Format(CommitMessageTemplate, options.Version));
                    gitClient.ForcePush(branchName);

                    // This does step 6 from above.
                    var gitHubRepo = gitHubClient.GetRepositoryFromRemote(spatialOsRemote);

                    var branchFrom = $"{Common.GithubBotUser}:{branchName}";
                    var branchTo   = Common.DevelopBranch;

                    // Only open a PR if one does not exist yet.
                    if (!gitHubClient.TryGetPullRequest(gitHubRepo, branchFrom, branchTo, out var pullRequest))
                    {
                        pullRequest = gitHubClient.CreatePullRequest(gitHubRepo,
                                                                     branchFrom,
                                                                     branchTo,
                                                                     string.Format(PullRequestTemplate, options.Version),
                                                                     GetPullRequestBody(options.GitRepoName));
                    }

                    if (BuildkiteMetadataSink.CanWrite(options))
                    {
                        using (var sink = new BuildkiteMetadataSink(options))
                        {
                            sink.WriteMetadata($"{options.GitRepoName}-release-branch",
                                               $"pull/{pullRequest.Number}/head:{branchName}");
                            sink.WriteMetadata($"{options.GitRepoName}-pr-url", pullRequest.HtmlUrl);
                        }
                    }

                    Logger.Info("Pull request available: {0}", pullRequest.HtmlUrl);
                    Logger.Info("Successfully created release!");
                    Logger.Info("Release 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 command does the necessary preparations for releasing an rc-branch:
         *         1. Re-pointing all <RepoName>Version.txt files
         */
        public int Run()
        {
            Common.VerifySemanticVersioningFormat(options.Version);
            var(repoName, pullRequestId) = Common.ExtractPullRequestInfo(options.PullRequestUrl);
            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);

                    bool madeChanges = false;

                    // 3. Makes repo-specific changes for prepping the release (e.g. updating version files, formatting the CHANGELOG).
                    switch (repoName)
                    {
                    case "UnrealEngine":
                        madeChanges |= Common.UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile, Logger);
                        madeChanges |= Common.UpdateVersionFile(gitClient, options.Version, UnrealGDKExampleProjectVersionFile, Logger);
                        break;

                    case "UnrealGDK":
                        Logger.Info("Updating {0}...", ChangeLogFilename);
                        madeChanges |= Common.UpdateChangeLog(ChangeLogFilename, options.Version, gitClient, ChangeLogReleaseHeadingTemplate);
                        if (!madeChanges)
                        {
                            Logger.Info("{0} was already up-to-date.", ChangeLogFilename);
                        }
                        break;

                    case "UnrealGDKExampleProject":
                        madeChanges |= Common.UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile, Logger);
                        break;

                    case "UnrealGDKTestGyms":
                        madeChanges |= Common.UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile, Logger);
                        break;

                    case "UnrealGDKEngineNetTest":
                        madeChanges |= Common.UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile, Logger);
                        break;

                    case "TestGymBuildKite":
                        madeChanges |= Common.UpdateVersionFile(gitClient, options.Version, UnrealGDKVersionFile, Logger);
                        break;
                    }

                    if (madeChanges)
                    {
                        // 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}' in preparation for the full release.");
                    }
                    else
                    {
                        Logger.Info($"Tried to update branch '${options.CandidateBranch}' in preparation for the full release, but it was already up-to-date.");
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Error(e, $"ERROR: Unable to update {options.CandidateBranch}. Error: {0}", e.Message);
                return(1);
            }

            return(0);
        }
Beispiel #10
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);
        }