From bc505c244616f960aae5356bfef59fad5314dee5 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 6 Dec 2020 19:37:20 -0500 Subject: [PATCH] Diverse test merging big commit --- .../Models/RemoteGitProvider.cs | 5 + .../Components/Deployment/DreamMaker.cs | 4 +- .../Components/InstanceFactory.cs | 9 ++ .../Repository/DefaultGitRemoteFeatures.cs | 14 ++- .../Repository/GitHubRemoteFeatures.cs | 93 ++++++++++++++++-- .../Repository/GitLabRemoteFeatures.cs | 97 +++++++++++++++++++ .../Repository/GitRemoteFeaturesBase.cs | 95 ++++++++++++++++++ .../Repository/GitRemoteFeaturesFactory.cs | 80 +++++++++++++++ .../IGitRemoteAdditionalInformation.cs | 25 +++++ .../Repository/IGitRemoteFeatures.cs | 9 +- .../Repository/IGitRemoteFeaturesFactory.cs | 15 +++ .../Repository/ILibGit2RepositoryFactory.cs | 4 +- .../Components/Repository/IRepository.cs | 3 +- .../Repository/LibGit2RepositoryFactory.cs | 40 +------- .../Components/Repository/Repository.cs | 22 ++++- .../Repository/RepositoryManager.cs | 47 +++++---- .../Controllers/RepositoryController.cs | 92 ++++++------------ src/Tgstation.Server.Host/Core/Application.cs | 15 ++- src/Tgstation.Server.Host/Models/Job.cs | 4 +- .../Tgstation.Server.Host.csproj | 1 + .../Repository/TestRepositoryFactory.cs | 3 +- .../Tgstation.Server.Tests/IntegrationTest.cs | 2 +- 22 files changed, 521 insertions(+), 158 deletions(-) create mode 100644 src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs create mode 100644 src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs create mode 100644 src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs create mode 100644 src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs create mode 100644 src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs diff --git a/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs b/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs index 61a3f73d0a..df0da1312b 100644 --- a/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs +++ b/src/Tgstation.Server.Api/Models/RemoteGitProvider.cs @@ -14,5 +14,10 @@ namespace Tgstation.Server.Api.Models /// Remote provider is GitHub.com /// GitHub, + + /// + /// Remote provider is GitLab.com + /// + GitLab, } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index cbc81333fd..62b62aa652 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -495,7 +495,7 @@ namespace Tgstation.Server.Host.Components.Deployment } /// - #pragma warning disable CA1506 + #pragma warning disable CA1506, CA1508 public async Task DeploymentProcess( Models.Job job, IDatabaseContextFactory databaseContextFactory, @@ -714,7 +714,7 @@ namespace Tgstation.Server.Host.Components.Deployment deploying = false; } } - #pragma warning restore CA1506 + #pragma warning restore CA1506, CA1508 /// /// Calculate the average length of a deployment using a given . diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index c2f1587ca4..12e308ccb0 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -132,6 +132,11 @@ namespace Tgstation.Server.Host.Components /// readonly IFileTransferTicketProvider fileTransferService; + /// + /// The for the . + /// + readonly IGitRemoteFeaturesFactory gitRemoteFeaturesFactory; + /// /// The for the . /// @@ -161,6 +166,7 @@ namespace Tgstation.Server.Host.Components /// The value of . /// The value of . /// The value of . + /// The value of . /// The containing the value of . public InstanceFactory( IIOManager ioManager, @@ -184,6 +190,7 @@ namespace Tgstation.Server.Host.Components ILibGit2Commands repositoryCommands, IServerPortProvider serverPortProvider, IFileTransferTicketProvider fileTransferService, + IGitRemoteFeaturesFactory gitRemoteFeaturesFactory, IOptions generalConfigurationOptions) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); @@ -207,6 +214,7 @@ namespace Tgstation.Server.Host.Components this.repositoryCommands = repositoryCommands ?? throw new ArgumentNullException(nameof(repositoryCommands)); this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider)); this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService)); + this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory)); generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } @@ -239,6 +247,7 @@ namespace Tgstation.Server.Host.Components repositoryCommands, repoIoManager, eventConsumer, + gitRemoteFeaturesFactory, loggerFactory.CreateLogger(), loggerFactory.CreateLogger()); try diff --git a/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs index 868ac3f789..3bdc876709 100644 --- a/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/DefaultGitRemoteFeatures.cs @@ -1,3 +1,6 @@ +using System; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Components.Repository @@ -8,7 +11,10 @@ namespace Tgstation.Server.Host.Components.Repository sealed class DefaultGitRemoteFeatures : IGitRemoteFeatures { /// - public string TestMergeRefSpecFormatter => null; + public string TestMergeRefSpecFormatter => throw new NotSupportedException(); + + /// + public string TestMergeLocalBranchNameFormatter => throw new NotSupportedException(); /// public RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.Unknown; @@ -18,5 +24,11 @@ namespace Tgstation.Server.Host.Components.Repository /// public string RemoteRepositoryName => null; + + /// + public Task GetTestMerge( + TestMergeParameters parameters, + Api.Models.Internal.RepositorySettings repositorySettings, + CancellationToken cancellationToken) => throw new NotSupportedException(); } } diff --git a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs index b7cac504da..1878171196 100644 --- a/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/GitHubRemoteFeatures.cs @@ -1,36 +1,117 @@ +using Microsoft.Extensions.Logging; +using Octokit; using System; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Extensions; namespace Tgstation.Server.Host.Components.Repository { /// /// GitHub . /// - sealed class GitHubRemoteFeatures : IGitRemoteFeatures + sealed class GitHubRemoteFeatures : GitRemoteFeaturesBase { /// - public string TestMergeRefSpecFormatter => "pull/{0}/head:{1}"; + public override string TestMergeRefSpecFormatter => "pull/{0}/head:{1}"; /// - public RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitHub; + public override string TestMergeLocalBranchNameFormatter => "pull/{0}/headrefs/heads/{1}"; /// - public string RemoteRepositoryOwner { get; } + public override RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitHub; /// - public string RemoteRepositoryName { get; } + public override string RemoteRepositoryOwner { get; } + + /// + public override string RemoteRepositoryName { get; } + + /// + /// The for the . + /// + readonly IGitHubClientFactory gitHubClientFactory; /// /// Initializes a new instance of the . /// + /// The value of . + /// The for the . /// The remote repository . - public GitHubRemoteFeatures(Uri remoteUrl) + public GitHubRemoteFeatures(IGitHubClientFactory gitHubClientFactory, ILogger logger, Uri remoteUrl) + : base(logger, remoteUrl) { + this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); + if (remoteUrl == null) throw new ArgumentNullException(nameof(remoteUrl)); RemoteRepositoryOwner = remoteUrl.Segments[1].TrimEnd('/'); RemoteRepositoryName = remoteUrl.Segments[2].TrimEnd('/'); } + + /// + protected override async Task GetTestMergeImpl( + TestMergeParameters parameters, + RepositorySettings repositorySettings, + CancellationToken cancellationToken) + { + var gitHubClient = repositorySettings.AccessToken != null + ? gitHubClientFactory.CreateClient(repositorySettings.AccessToken) + : gitHubClientFactory.CreateClient(); + + PullRequest pr = null; + ApiException exception = null; + string errorMessage = null; + try + { + pr = await gitHubClient + .PullRequest + .Get(RemoteRepositoryOwner, RemoteRepositoryName, parameters.Number) + .WithToken(cancellationToken) + .ConfigureAwait(false); + } + catch (RateLimitExceededException ex) + { + // you look at your anonymous access and sigh + errorMessage = "GITHUB API ERROR: RATE LIMITED"; + exception = ex; + } + catch (AuthorizationException ex) + { + errorMessage = "GITHUB API ERROR: BAD CREDENTIALS"; + exception = ex; + } + catch (NotFoundException ex) + { + // you look at your shithub and sigh + errorMessage = "GITHUB API ERROR: PULL REQUEST NOT FOUND"; + exception = ex; + } + + if (exception != null) + Logger.LogWarning(exception, "Error retrieving pull request metadata!"); + + var revisionToUse = parameters.PullRequestRevision == null + || pr?.Head.Sha.StartsWith(parameters.PullRequestRevision, StringComparison.OrdinalIgnoreCase) == true + ? pr?.Head.Sha + : parameters.PullRequestRevision; + + var testMerge = new Models.TestMerge + { + Author = pr?.User.Login ?? errorMessage, + BodyAtMerge = pr?.Body ?? errorMessage ?? String.Empty, + TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty, + Comment = parameters.Comment, + Number = parameters.Number, + PullRequestRevision = revisionToUse, + Url = pr?.HtmlUrl ?? errorMessage + }; + + return testMerge; + } } } diff --git a/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs new file mode 100644 index 0000000000..aa5b932fd5 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository/GitLabRemoteFeatures.cs @@ -0,0 +1,97 @@ +using GitLabApiClient; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Host.Extensions; + +namespace Tgstation.Server.Host.Components.Repository +{ + /// + /// GitLab . + /// + sealed class GitLabRemoteFeatures : GitRemoteFeaturesBase + { + /// + public override string TestMergeRefSpecFormatter => "merge-requests/{0}/head:{1}"; + + /// + public override string TestMergeLocalBranchNameFormatter => "merge-requests/{0}/headrefs/heads/{1}"; + + /// + public override RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitLab; + + /// + public override string RemoteRepositoryOwner { get; } + + /// + public override string RemoteRepositoryName { get; } + + /// + /// Initializes a new instance of the . + /// + /// The for the . + /// The remote repository . + public GitLabRemoteFeatures(ILogger logger, Uri remoteUrl) + : base(logger, remoteUrl) + { + RemoteRepositoryOwner = remoteUrl.Segments[1].TrimEnd('/'); + RemoteRepositoryName = remoteUrl.Segments[2].TrimEnd('/'); + } + + /// + protected override async Task GetTestMergeImpl( + TestMergeParameters parameters, + RepositorySettings repositorySettings, + CancellationToken cancellationToken) + { + const string GitLabUrl = "https://gitlab.com"; + + var client = repositorySettings.AccessToken != null + ? new GitLabClient(GitLabUrl, repositorySettings.AccessToken) + : new GitLabClient(GitLabUrl); + + try + { + var mr = await client + .MergeRequests + .GetAsync($"{RemoteRepositoryOwner}/{RemoteRepositoryName}", parameters.Number) + .WithToken(cancellationToken) + .ConfigureAwait(false); + + var revisionToUse = parameters.PullRequestRevision == null + || mr.Sha.StartsWith(parameters.PullRequestRevision, StringComparison.OrdinalIgnoreCase) + ? mr.Sha + : parameters.PullRequestRevision; + + return new Models.TestMerge + { + Author = mr.Author.Username, + BodyAtMerge = mr.Description, + TitleAtMerge = mr.Title, + Comment = parameters.Comment, + Number = parameters.Number, + PullRequestRevision = mr.Sha, + Url = mr.WebUrl + }; + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Error retrieving merge request metadata!"); + + return new Models.TestMerge + { + Author = ex.Message, + BodyAtMerge = ex.Message, + TitleAtMerge = ex.Message, + Comment = parameters.Comment, + Number = parameters.Number, + PullRequestRevision = parameters.PullRequestRevision, + Url = ex.Message + }; + } + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs new file mode 100644 index 0000000000..92835dd7ad --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesBase.cs @@ -0,0 +1,95 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Repository +{ + /// + /// Base for implementing . + /// + abstract class GitRemoteFeaturesBase : IGitRemoteFeatures + { + /// + public abstract string TestMergeRefSpecFormatter { get; } + + /// + public abstract string TestMergeLocalBranchNameFormatter { get; } + + /// + public abstract RemoteGitProvider? RemoteGitProvider { get; } + + /// + public abstract string RemoteRepositoryOwner { get; } + + /// + public abstract string RemoteRepositoryName { get; } + + /// + /// The for the . + /// + protected ILogger Logger { get; } + + /// + /// Cache of created s. + /// + readonly Dictionary cachedLookups; + + /// + /// Initializes a new instance of the . + /// + /// The value of . + /// The remote repository . + public GitRemoteFeaturesBase(ILogger logger, Uri remoteUrl) + { + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + if (remoteUrl == null) + throw new ArgumentNullException(nameof(remoteUrl)); + + cachedLookups = new Dictionary(); + } + + /// + public async Task GetTestMerge( + TestMergeParameters parameters, + RepositorySettings repositorySettings, + CancellationToken cancellationToken) + { + if (parameters == null) + throw new ArgumentNullException(nameof(parameters)); + if (repositorySettings == null) + throw new ArgumentNullException(nameof(repositorySettings)); + + Models.TestMerge result; + lock (cachedLookups) + if (cachedLookups.TryGetValue(parameters, out result)) + Logger.LogTrace("Using cache for test merge #{0}", parameters.Number); + + if (result == null) + { + Logger.LogTrace("Retrieving metadata for test merge #{0}...", parameters.Number); + result = await GetTestMergeImpl(parameters, repositorySettings, cancellationToken).ConfigureAwait(false); + lock (cachedLookups) + if (!cachedLookups.TryAdd(parameters, result)) + Logger.LogError("Race condition on adding test merge #{0}!", parameters.Number); + } + + return result; + } + + /// + /// Implementation of + /// + /// The . + /// The . + /// The for the operation. + /// A resulting in the of the . + protected abstract Task GetTestMergeImpl( + TestMergeParameters parameters, + RepositorySettings repositorySettings, + CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs new file mode 100644 index 0000000000..1c061adc6e --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository/GitRemoteFeaturesFactory.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Logging; +using System; +using Tgstation.Server.Host.Core; + +namespace Tgstation.Server.Host.Components.Repository +{ + /// + sealed class GitRemoteFeaturesFactory : IGitRemoteFeaturesFactory + { + /// + /// The for the . + /// + readonly IGitHubClientFactory gitHubClientFactory; + + /// + /// The for the . + /// + readonly ILoggerFactory loggerFactory; + + /// + /// The for the . + /// + readonly ILogger logger; + + /// + /// Initializes a new instance of the . + /// + /// The value of . + /// The value of . + /// The value of . + public GitRemoteFeaturesFactory( + IGitHubClientFactory gitHubClientFactory, + ILoggerFactory loggerFactory, + ILogger logger) + { + this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + public IGitRemoteFeatures CreateGitRemoteFeatures(IRepository repository) + { + if (repository == null) + throw new ArgumentNullException(nameof(repository)); + + var primaryRemote = repository.Origin; + try + { + var primaryRemoteUrl = new Uri(primaryRemote); + + switch (primaryRemoteUrl.Host.ToUpperInvariant()) + { + case "GITHUB.COM": + case "WWW.GITHUB.COM": + case "GIT.GITHUB.COM": + return new GitHubRemoteFeatures( + gitHubClientFactory, + loggerFactory.CreateLogger(), + primaryRemoteUrl); + case "GITLAB.COM": + case "WWW.GITLAB.COM": + case "GIT.GITLAB.COM": + return new GitLabRemoteFeatures( + loggerFactory.CreateLogger(), + primaryRemoteUrl); + default: + logger.LogTrace("Unknown git remote: {0}", primaryRemoteUrl); + break; + } + } + catch (Exception ex) + { + logger.LogWarning(ex, "Error parsing remote git provider."); + } + + return new DefaultGitRemoteFeatures(); + } + } +} diff --git a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs new file mode 100644 index 0000000000..7b4de75b0e --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteAdditionalInformation.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Components.Repository +{ + /// + /// Additional information from git remotes. + /// + public interface IGitRemoteAdditionalInformation : IGitRemoteInformation + { + /// + /// Retrieve the representation of given test merge . + /// + /// The . + /// The . + /// The for the operation. + /// A resulting in the of the . + Task GetTestMerge( + TestMergeParameters parameters, + RepositorySettings repositorySettings, + CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs index d26e9cd562..7dc7502ba7 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeatures.cs @@ -1,15 +1,18 @@ -using Tgstation.Server.Api.Models.Internal; - namespace Tgstation.Server.Host.Components.Repository { /// /// Provides features for remote git services /// - interface IGitRemoteFeatures : IGitRemoteInformation + interface IGitRemoteFeatures : IGitRemoteAdditionalInformation { /// /// Gets a formatter string which creates the remote refspec for fetching the HEAD of passed in pull request number. /// string TestMergeRefSpecFormatter { get; } + + /// + /// Get + /// + string TestMergeLocalBranchNameFormatter { get; } } } diff --git a/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs new file mode 100644 index 0000000000..31085a16d2 --- /dev/null +++ b/src/Tgstation.Server.Host/Components/Repository/IGitRemoteFeaturesFactory.cs @@ -0,0 +1,15 @@ +namespace Tgstation.Server.Host.Components.Repository +{ + /// + /// Factory for creating . + /// + interface IGitRemoteFeaturesFactory + { + /// + /// Create the for a given . + /// + /// The to create for. + /// A new instance. + IGitRemoteFeatures CreateGitRemoteFeatures(IRepository repository); + } +} diff --git a/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs index 79e20a64fb..086ca8d93e 100644 --- a/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs +++ b/src/Tgstation.Server.Host/Components/Repository/ILibGit2RepositoryFactory.cs @@ -21,8 +21,8 @@ namespace Tgstation.Server.Host.Components.Repository /// /// The full path to the . /// The for the operation. - /// A resulting in a containing the loaded and the associated . - Task> CreateFromPath(string path, CancellationToken cancellationToken); + /// A resulting in the loaded . + Task CreateFromPath(string path, CancellationToken cancellationToken); /// /// Clone a remote . diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index 41dd80e15e..be9d6b0b0d 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -2,14 +2,13 @@ using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; -using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Host.Components.Repository { /// /// Represents an on-disk git repository /// - public interface IRepository : IGitRemoteInformation, IDisposable + public interface IRepository : IGitRemoteAdditionalInformation, IDisposable { /// /// If tracks an upstream branch diff --git a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs index bd1f7de84c..137d6be054 100644 --- a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs +++ b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs @@ -2,7 +2,6 @@ using LibGit2Sharp; using LibGit2Sharp.Handlers; using Microsoft.Extensions.Logging; using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; @@ -36,7 +35,7 @@ namespace Tgstation.Server.Host.Components.Repository } /// - public async Task> CreateFromPath(string path, CancellationToken cancellationToken) + public async Task CreateFromPath(string path, CancellationToken cancellationToken) { if (path == null) throw new ArgumentNullException(nameof(path)); @@ -52,42 +51,7 @@ namespace Tgstation.Server.Host.Components.Repository TaskScheduler.Current) .ConfigureAwait(false); - try - { - var remoteFeatures = CreateGitRemoteFeatures(repo); - return Tuple.Create(repo, remoteFeatures); - } - catch - { - repo.Dispose(); - throw; - } - } - - IGitRemoteFeatures CreateGitRemoteFeatures(LibGit2Sharp.IRepository repo) - { - var primaryRemote = repo.Network.Remotes.First(); - var primaryRemoteUrl = new Uri(primaryRemote.Url); - - try - { - switch (primaryRemoteUrl.Host.ToUpperInvariant()) - { - case "GITHUB.COM": - case "WWW.GITHUB.COM": - case "GIT.GITHUB.COM": - return new GitHubRemoteFeatures(primaryRemoteUrl); - default: - logger.LogTrace("Unknown git remote: {0}", primaryRemoteUrl); - break; - } - } - catch (Exception ex) - { - logger.LogWarning(ex, "Error parsing remote git provider."); - } - - return new DefaultGitRemoteFeatures(); + return repo; } /// diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 6c752c0cf6..3d83a36dbb 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; using Tgstation.Server.Host.Components.Events; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; @@ -123,7 +124,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The value of /// The value of /// The value of - /// The value of . + /// The to provide the value of . /// The value of /// The value if public Repository( @@ -132,7 +133,7 @@ namespace Tgstation.Server.Host.Components.Repository IIOManager ioMananger, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, - IGitRemoteFeatures gitRemoteFeatures, + IGitRemoteFeaturesFactory gitRemoteFeaturesFactory, ILogger logger, Action onDispose) { @@ -141,9 +142,13 @@ namespace Tgstation.Server.Host.Components.Repository this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider)); - this.gitRemoteFeatures = gitRemoteFeatures ?? throw new ArgumentNullException(nameof(gitRemoteFeatures)); + if (gitRemoteFeaturesFactory == null) + throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose)); + + gitRemoteFeatures = gitRemoteFeaturesFactory.CreateGitRemoteFeatures(this); } /// @@ -299,7 +304,7 @@ namespace Tgstation.Server.Host.Components.Repository testMergeParameters.Comment ?? String.Empty); var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number); - var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName); + var localBranchName = String.Format(CultureInfo.InvariantCulture, gitRemoteFeatures.TestMergeLocalBranchNameFormatter, testMergeParameters.Number, prBranchName); var refSpec = String.Format(CultureInfo.InvariantCulture, gitRemoteFeatures.TestMergeRefSpecFormatter, testMergeParameters.Number, prBranchName); var refSpecList = new List { refSpec }; @@ -774,5 +779,14 @@ namespace Tgstation.Server.Host.Components.Repository return false; }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); + + /// + public Task GetTestMerge( + TestMergeParameters parameters, + RepositorySettings repositorySettings, + CancellationToken cancellationToken) => gitRemoteFeatures.GetTestMerge( + parameters, + repositorySettings, + cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs index c82b56e22a..6e025f8b6f 100644 --- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs +++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs @@ -40,6 +40,11 @@ namespace Tgstation.Server.Host.Components.Repository /// readonly IEventConsumer eventConsumer; + /// + /// The for the + /// + readonly IGitRemoteFeaturesFactory gitRemoteFeaturesFactory; + /// /// The created s /// @@ -62,6 +67,7 @@ namespace Tgstation.Server.Host.Components.Repository /// The value of . /// The value of /// The value of + /// The value of . /// The value of /// The value of public RepositoryManager( @@ -69,6 +75,7 @@ namespace Tgstation.Server.Host.Components.Repository ILibGit2Commands commands, IIOManager ioManager, IEventConsumer eventConsumer, + IGitRemoteFeaturesFactory gitRemoteFeaturesFactory, ILogger repositoryLogger, ILogger logger) { @@ -76,6 +83,7 @@ namespace Tgstation.Server.Host.Components.Repository this.commands = commands ?? throw new ArgumentNullException(nameof(commands)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory)); this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); semaphore = new SemaphoreSlim(1); @@ -188,32 +196,21 @@ namespace Tgstation.Server.Host.Components.Repository { try { - var repoTuple = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false); + var libGit2Repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false); - try - { - var libGit2Repo = repoTuple.Item1; - var gitRemoteFeatures = repoTuple.Item2; - - return new Repository( - libGit2Repo, - commands, - ioManager, - eventConsumer, - repositoryFactory, - gitRemoteFeatures, - repositoryLogger, - () => - { - logger.LogTrace("Releasing semaphore due to Repository disposal..."); - semaphore.Release(); - }); - } - catch - { - repoTuple.Item1.Dispose(); - throw; - } + return new Repository( + libGit2Repo, + commands, + ioManager, + eventConsumer, + repositoryFactory, + gitRemoteFeaturesFactory, + repositoryLogger, + () => + { + logger.LogTrace("Releasing semaphore due to Repository disposal..."); + semaphore.Release(); + }); } catch { diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 1b77f22bf6..c83393aa98 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -17,7 +17,6 @@ using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; -using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -644,7 +643,6 @@ namespace Tgstation.Server.Host.Controllers } // test merging - Dictionary prMap = null; if (newTestMerges) { if (repo.RemoteGitProvider == RemoteGitProvider.Unknown) @@ -666,10 +664,6 @@ namespace Tgstation.Server.Host.Controllers bool needToApplyRemainingPrs = true; if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha) { - // In order for this to work though we need the shas of all the commits - if (model.NewTestMerges.Any(x => x.PullRequestRevision == null)) - prMap = new Dictionary(); - bool cantSearch = false; foreach (var I in model.NewTestMerges) { @@ -681,11 +675,10 @@ namespace Tgstation.Server.Host.Controllers try { // retrieve the latest sha - var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number) - .WithToken(ct) - .ConfigureAwait(false); - prMap.Add(I.Number, pr); - I.PullRequestRevision = pr.Head.Sha; + var pr = await repo.GetTestMerge(I, currentModel, ct).ConfigureAwait(false); + + // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it + I.PullRequestRevision = pr.PullRequestRevision; } catch { @@ -798,47 +791,10 @@ namespace Tgstation.Server.Host.Controllers { foreach (var I in model.NewTestMerges) { - Octokit.PullRequest pr = null; - string errorMessage = null; - if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number)) throw new JobException(ErrorCode.RepoDuplicateTestMerge); - Exception exception = null; - try - { - // load from cache if possible - if (prMap == null || !prMap.TryGetValue(I.Number, out pr)) - pr = await gitHubClient - .PullRequest - .Get(repoOwner, repoName, I.Number) - .WithToken(ct) - .ConfigureAwait(false); - } - catch (Octokit.RateLimitExceededException ex) - { - // you look at your anonymous access and sigh - errorMessage = "REMOTE API ERROR: RATE LIMITED"; - exception = ex; - } - catch (Octokit.AuthorizationException ex) - { - errorMessage = "REMOTE API ERROR: BAD CREDENTIALS"; - exception = ex; - } - catch (Octokit.NotFoundException ex) - { - // you look at your shithub and sigh - errorMessage = "REMOTE API ERROR: PULL REQUEST NOT FOUND"; - exception = ex; - } - - if (exception != null) - Logger.LogWarning(exception, "Error retrieving pull request metadata!"); - - // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it - if (I.PullRequestRevision == null && pr != null) - I.PullRequestRevision = pr.Head.Sha; + var fullTestMergeTask = repo.GetTestMerge(I, currentModel, ct); var mergeResult = await repo.AddTestMerge( I, @@ -849,28 +805,38 @@ namespace Tgstation.Server.Host.Controllers NextProgressReporter(), ct).ConfigureAwait(false); - if (!mergeResult.HasValue) + if (mergeResult == null) throw new JobException( ErrorCode.RepoTestMergeConflict, new JobException( $"Merge of PR #{I.Number} at {I.PullRequestRevision.Substring(0, 7)} conflicted!")); - ++doneSteps; + Models.TestMerge fullTestMerge; + try + { + fullTestMerge = await fullTestMergeTask.ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogWarning("Error retrieving metadata for test merge #{0}!", I.Number); + + fullTestMerge = new Models.TestMerge + { + Author = ex.Message, + BodyAtMerge = ex.Message, + MergedAt = DateTimeOffset.Now, + TitleAtMerge = ex.Message, + Comment = I.Comment, + Number = I.Number, + PullRequestRevision = I.PullRequestRevision, + Url = ex.Message + }; + } // MergedBy will be set later - var tm = new Models.TestMerge - { - Author = pr?.User.Login ?? errorMessage, - BodyAtMerge = pr?.Body ?? errorMessage ?? String.Empty, - MergedAt = DateTimeOffset.Now, - TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty, - Comment = I.Comment, - Number = I.Number, - PullRequestRevision = I.PullRequestRevision, - Url = pr?.HtmlUrl ?? errorMessage - }; + ++doneSteps; - await UpdateRevInfo(tm).ConfigureAwait(false); + await UpdateRevInfo(fullTestMerge).ConfigureAwait(false); } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index cf80be7bfd..071380e041 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -306,24 +306,23 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); } - // configure misc services + // configure component/misc services services.AddScoped(); services.AddTransient, LimitedFileStreamResultExecutor>(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(x => x.GetRequiredService()); - services.AddSingleton(x => x.GetRequiredService()); - - // configure component services + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(x => x.GetRequiredService()); + services.AddSingleton(x => x.GetRequiredService()); // configure root services services.AddSingleton(); diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs index 134993f14c..d2ba919450 100644 --- a/src/Tgstation.Server.Host/Models/Job.cs +++ b/src/Tgstation.Server.Host/Models/Job.cs @@ -1,9 +1,11 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Host.Models { /// + #pragma warning disable CA1724 // naming conflict with gitlab package public sealed class Job : Api.Models.Internal.Job + #pragma warning restore CA1724 { /// /// See diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index f8d5296d9d..f2de7240e3 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -65,6 +65,7 @@ + diff --git a/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs b/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs index 3fcb973507..43b4c66124 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs @@ -20,8 +20,7 @@ namespace Tgstation.Server.Host.Components.Repository.Tests string path, ILibGit2RepositoryFactory repositoryFactory = null) => (await (repositoryFactory ?? CreateFactory()) - .CreateFromPath(path, default)) - .Item1; + .CreateFromPath(path, default)); [TestMethod] public void TestConstructionThrows() => Assert.ThrowsException(() => new LibGit2RepositoryFactory(null)); diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index 9aa2f223a2..d0d50482eb 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -480,7 +480,7 @@ namespace Tgstation.Server.Tests Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of(), + Mock.Of(), Mock.Of>(), () => { });