diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 6e3ae3cc..3792eeff 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -487,29 +487,31 @@ protected async Task GitHubSubmitManifests(Manifests manifests) Logger.InfoLocalized(nameof(Resources.PullRequestURI_Message), pullRequest.HtmlUrl); Console.WriteLine(); - } - catch (AggregateException ae) - { - ae.Handle((e) => - { - TelemetryManager.Log.WriteEvent(new PullRequestEvent - { - IsSuccessful = false, - ErrorMessage = e.Message, - ExceptionType = e.GetType().ToString(), - StackTrace = e.StackTrace, - }); - - if (e is Octokit.ForbiddenException) - { - Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); - return true; - } - else - { - return false; - } - }); + } + catch (Exception e) + { + TelemetryManager.Log.WriteEvent(new PullRequestEvent + { + IsSuccessful = false, + ErrorMessage = e.Message, + ExceptionType = e.GetType().ToString(), + StackTrace = e.StackTrace, + }); + + if (e is Octokit.ForbiddenException) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + return true; + } + else if (e is NonFastForwardException nonFastForwardException) + { + Logger.ErrorLocalized(nameof(Resources.FastForwardUpdateFailed_Message), nonFastForwardException.CommitsAheadBy); + return true; + } + else + { + return false; + } } return true; diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index e6da16d4..4515bacf 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.WingetCreateCLI.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -708,6 +708,15 @@ public static string ExternalDependencies_KeywordDescription { } } + /// + /// Looks up a localized string similar to Failed to update fork because the default branch is ahead by {0} commit(s). . + /// + public static string FastForwardUpdateFailed_Message { + get { + return ResourceManager.GetString("FastForwardUpdateFailed_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to [{0}] value is. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index d5fe8558..00b8aafb 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -890,4 +890,8 @@ UpgradeCode used for correlation of packages across sources + + Failed to update fork because the default branch is ahead by {0} commit(s). + {0} - represents the number of commits the default branch is ahead by + \ No newline at end of file diff --git a/src/WingetCreateCore/Common/Exceptions/NonFastForwardException.cs b/src/WingetCreateCore/Common/Exceptions/NonFastForwardException.cs new file mode 100644 index 00000000..75e8bebf --- /dev/null +++ b/src/WingetCreateCore/Common/Exceptions/NonFastForwardException.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCore.Common.Exceptions +{ + using System; + + /// + /// The exception that is thrown when attemping a non-fast forward update to a GitHub repository branch. + /// + public class NonFastForwardException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The number of commits the branch is ahead by. + public NonFastForwardException(int commitsAheadBy) + { + this.CommitsAheadBy = commitsAheadBy; + } + + /// + /// Gets the number of commits the branch is ahead by. + /// + public int CommitsAheadBy { get; private set; } + } +} diff --git a/src/WingetCreateCore/Common/GitHub.cs b/src/WingetCreateCore/Common/GitHub.cs index 2bd66225..5cadb25c 100644 --- a/src/WingetCreateCore/Common/GitHub.cs +++ b/src/WingetCreateCore/Common/GitHub.cs @@ -10,6 +10,7 @@ namespace Microsoft.WingetCreateCore.Common using System.Security.Cryptography; using System.Threading.Tasks; using Jose; + using Microsoft.WingetCreateCore.Common.Exceptions; using Microsoft.WingetCreateCore.Models; using Octokit; using Polly; @@ -277,8 +278,8 @@ private async Task FindPackageIdRecursive(string[] packageId, string pat private async Task SubmitPRAsync(string packageId, string version, Dictionary contents, bool submitToFork) { bool createdRepo = false; - Repository repo; + if (submitToFork) { try @@ -306,12 +307,22 @@ private async Task SubmitPRAsync(string packageId, string version, var upstreamMasterSha = upstreamMaster.Object.Sha; Reference newBranch = null; + try { var retryPolicy = Policy.Handle().WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(i)); await retryPolicy.ExecuteAsync(async () => { - await this.github.Git.Reference.Create(repo.Id, new NewReference($"refs/{newBranchNameHeads}", upstreamMasterSha)); + try + { + await this.github.Git.Reference.Create(repo.Id, new NewReference($"refs/{newBranchNameHeads}", upstreamMasterSha)); + } + catch (Octokit.NotFoundException) + { + // Creating a reference can sometimes fail with a NotFoundException if the fork is not up to date with the upstream repository. + await this.UpdateForkedRepoWithUpstreamCommits(repo); + throw; + } }); // Update from upstream branch master @@ -366,6 +377,30 @@ await retryPolicy.ExecuteAsync(async () => } } + /// + /// Checks if the provided forked repository is behind on upstream commits and updates the default branch with the fetched commits. Update can only be a fast-forward update. + /// + /// Forked repository to be updated. + /// A representing the asynchronous operation. + private async Task UpdateForkedRepoWithUpstreamCommits(Repository forkedRepo) + { + var upstream = forkedRepo.Parent; + var compareResult = await this.github.Repository.Commit.Compare(upstream.Id, upstream.DefaultBranch, $"{forkedRepo.Owner.Login}:{forkedRepo.DefaultBranch}"); + + // Check to ensure that the update is only a fast-forward update. + if (compareResult.BehindBy > 0) + { + int commitsAheadBy = compareResult.AheadBy; + if (commitsAheadBy > 0) + { + throw new NonFastForwardException(commitsAheadBy); + } + + var upstreamBranchReference = await this.github.Git.Reference.Get(upstream.Id, $"heads/{upstream.DefaultBranch}"); + await this.github.Git.Reference.Update(forkedRepo.Id, $"heads/{forkedRepo.DefaultBranch}", new ReferenceUpdate(upstreamBranchReference.Object.Sha)); + } + } + private async Task DeletePullRequestBranch(int pullRequestId) { // Delete branch if it's not on a forked repo.