File: Automation\GitHubVersionsRepoUpdater.cs
Web Access
Project: src\src\VersionTools\Microsoft.DotNet.VersionTools\Microsoft.DotNet.VersionTools.csproj (Microsoft.DotNet.VersionTools)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.DotNet.VersionTools.Automation.GitHubApi;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
 
namespace Microsoft.DotNet.VersionTools.Automation
{
    public class GitHubVersionsRepoUpdater : VersionsRepoUpdater
    {
        private const int MaxTries = 10;
        private const int RetryMillisecondsDelay = 5000;
 
        private GitHubAuth _gitHubAuth;
        private GitHubProject _project;
 
        public GitHubVersionsRepoUpdater(
            INupkgInfoFactory nupkgInfoFactory,
            GitHubAuth gitHubAuth,
            string versionsRepoOwner = null,
            string versionsRepo = null)
            : this(
                gitHubAuth,
                new GitHubProject(versionsRepo ?? "versions", versionsRepoOwner),
                nupkgInfoFactory)
        {
        }
 
        public GitHubVersionsRepoUpdater(GitHubAuth gitHubAuth, GitHubProject project, INupkgInfoFactory nupkgInfoFactory) : base(nupkgInfoFactory)
        {
            if (gitHubAuth == null)
            {
                throw new ArgumentNullException(nameof(gitHubAuth));
            }
            _gitHubAuth = gitHubAuth;
 
            if (project == null)
            {
                throw new ArgumentNullException(nameof(project));
            }
            _project = project;
        }
 
        /// <param name="updateLatestVersion">If true, updates Latest.txt with a prerelease moniker. If there isn't one, makes the file empty.</param>
        /// <param name="updateLatestPackageList">If true, updates Latest_Packages.txt.</param>
        /// <param name="updateLastBuildPackageList">If true, updates Last_Build_Packages.txt, and enables keeping old packages in Latest_Packages.txt.</param>
        public async Task UpdateBuildInfoAsync(
            IEnumerable<string> packagePaths,
            string versionsRepoPath,
            bool updateLatestPackageList = true,
            bool updateLatestVersion = true,
            bool updateLastBuildPackageList = true)
        {
            if (packagePaths == null)
            {
                throw new ArgumentNullException(nameof(packagePaths));
            }
            if (versionsRepoPath == null)
            {
                throw new ArgumentNullException(nameof(versionsRepoPath));
            }
 
            NupkgInfo[] packages = CreatePackageInfos(packagePaths).ToArray();
 
            string prereleaseVersion = GetPrereleaseVersion(packages);
 
            Dictionary<string, string> packageDictionary = CreatePackageInfoDictionary(packages);
 
            using (GitHubClient client = new GitHubClient(_gitHubAuth))
            {
                for (int i = 0; i < MaxTries; i++)
                {
                    try
                    {
                        // Master commit to use as new commit's parent.
                        string masterRef = "heads/master";
                        GitReference currentMaster = await client.GetReferenceAsync(_project, masterRef);
                        string masterSha = currentMaster.Object.Sha;
 
                        List<GitObject> objects = new List<GitObject>();
 
                        if (updateLastBuildPackageList)
                        {
                            objects.Add(new GitObject
                            {
                                Path = $"{versionsRepoPath}/{BuildInfo.LastBuildPackagesTxtFilename}",
                                Type = GitObject.TypeBlob,
                                Mode = GitObject.ModeFile,
                                Content = CreatePackageListContent(packageDictionary)
                            });
                        }
 
                        if (updateLatestPackageList)
                        {
                            var allPackages = new Dictionary<string, string>(packageDictionary);
 
                            if (updateLastBuildPackageList)
                            {
                                await AddExistingPackages(
                                    client,
                                    new GitHubBranch("master", _project),
                                    versionsRepoPath,
                                    allPackages);
                            }
 
                            objects.Add(new GitObject
                            {
                                Path = $"{versionsRepoPath}/{BuildInfo.LatestPackagesTxtFilename}",
                                Type = GitObject.TypeBlob,
                                Mode = GitObject.ModeFile,
                                Content = CreatePackageListContent(allPackages)
                            });
                        }
 
                        if (updateLatestVersion)
                        {
                            objects.Add(new GitObject
                            {
                                Path = $"{versionsRepoPath}/{BuildInfo.LatestTxtFilename}",
                                Type = GitObject.TypeBlob,
                                Mode = GitObject.ModeFile,
                                Content = prereleaseVersion
                            });
                        }
 
                        string message = $"Updating {versionsRepoPath}";
                        if (string.IsNullOrEmpty(prereleaseVersion))
                        {
                            message += ". No prerelease versions published.";
                        }
                        else
                        {
                            message += $" for {prereleaseVersion}";
                        }
 
                        GitTree tree = await client.PostTreeAsync(_project, masterSha, objects.ToArray());
                        GitCommit commit = await client.PostCommitAsync(_project, message, tree.Sha, new[] { masterSha });
 
                        // Only fast-forward. Don't overwrite other changes: throw exception instead.
                        await client.PatchReferenceAsync(_project, masterRef, commit.Sha, force: false);
 
                        Trace.TraceInformation($"Committed build-info update on attempt {i + 1}.");
                        break;
                    }
                    catch (Exception ex) when (ex is HttpRequestException || ex is NotFastForwardUpdateException)
                    {
                        int nextTry = i + 1;
                        if (nextTry < MaxTries)
                        {
                            Trace.TraceInformation($"Encountered exception committing build-info update: {ex.Message}");
                            Trace.TraceInformation($"Trying again in {RetryMillisecondsDelay}ms. {MaxTries - nextTry} tries left.");
                            await Task.Delay(RetryMillisecondsDelay);
                        }
                        else
                        {
                            Trace.TraceInformation("Encountered exception committing build-info update.");
                            throw;
                        }
                    }
                }
            }
        }
    }
}