Diverse test merging big commit

This commit is contained in:
Jordan Brown
2020-12-06 19:37:20 -05:00
parent 2838170350
commit bc505c2446
22 changed files with 521 additions and 158 deletions
@@ -14,5 +14,10 @@ namespace Tgstation.Server.Api.Models
/// Remote provider is GitHub.com
/// </summary>
GitHub,
/// <summary>
/// Remote provider is GitLab.com
/// </summary>
GitLab,
}
}
@@ -495,7 +495,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
/// <inheritdoc />
#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
/// <summary>
/// Calculate the average length of a deployment using a given <paramref name="databaseContext"/>.
@@ -132,6 +132,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
readonly IFileTransferTicketProvider fileTransferService;
/// <summary>
/// The <see cref="IGitRemoteFeaturesFactory"/> for the <see cref="InstanceFactory"/>.
/// </summary>
readonly IGitRemoteFeaturesFactory gitRemoteFeaturesFactory;
/// <summary>
/// The <see cref="GeneralConfiguration"/> for the <see cref="InstanceFactory"/>.
/// </summary>
@@ -161,6 +166,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="repositoryCommands">The value of <see cref="repositoryCommands"/>.</param>
/// <param name="serverPortProvider">The value of <see cref="serverPortProvider"/>.</param>
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
/// <param name="gitRemoteFeaturesFactory">The value of <see cref="gitRemoteFeaturesFactory"/>.</param>
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
public InstanceFactory(
IIOManager ioManager,
@@ -184,6 +190,7 @@ namespace Tgstation.Server.Host.Components
ILibGit2Commands repositoryCommands,
IServerPortProvider serverPortProvider,
IFileTransferTicketProvider fileTransferService,
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
IOptions<GeneralConfiguration> 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<Repository.Repository>(),
loggerFactory.CreateLogger<RepositoryManager>());
try
@@ -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
{
/// <inheritdoc />
public string TestMergeRefSpecFormatter => null;
public string TestMergeRefSpecFormatter => throw new NotSupportedException();
/// <inheritdoc />
public string TestMergeLocalBranchNameFormatter => throw new NotSupportedException();
/// <inheritdoc />
public RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.Unknown;
@@ -18,5 +24,11 @@ namespace Tgstation.Server.Host.Components.Repository
/// <inheritdoc />
public string RemoteRepositoryName => null;
/// <inheritdoc />
public Task<Models.TestMerge> GetTestMerge(
TestMergeParameters parameters,
Api.Models.Internal.RepositorySettings repositorySettings,
CancellationToken cancellationToken) => throw new NotSupportedException();
}
}
@@ -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
{
/// <summary>
/// GitHub <see cref="IGitRemoteFeatures"/>.
/// </summary>
sealed class GitHubRemoteFeatures : IGitRemoteFeatures
sealed class GitHubRemoteFeatures : GitRemoteFeaturesBase
{
/// <inheritdoc />
public string TestMergeRefSpecFormatter => "pull/{0}/head:{1}";
public override string TestMergeRefSpecFormatter => "pull/{0}/head:{1}";
/// <inheritdoc />
public RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitHub;
public override string TestMergeLocalBranchNameFormatter => "pull/{0}/headrefs/heads/{1}";
/// <inheritdoc />
public string RemoteRepositoryOwner { get; }
public override RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitHub;
/// <inheritdoc />
public string RemoteRepositoryName { get; }
public override string RemoteRepositoryOwner { get; }
/// <inheritdoc />
public override string RemoteRepositoryName { get; }
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitHubRemoteFeatures"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
/// <summary>
/// Initializes a new instance of the <see cref="GitHubRemoteFeatures"/> <see langword="class"/>.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GitRemoteFeaturesBase"/>.</param>
/// <param name="remoteUrl">The remote repository <see cref="Uri"/>.</param>
public GitHubRemoteFeatures(Uri remoteUrl)
public GitHubRemoteFeatures(IGitHubClientFactory gitHubClientFactory, ILogger<GitHubRemoteFeatures> 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('/');
}
/// <inheritdoc />
protected override async Task<Models.TestMerge> 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;
}
}
}
@@ -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
{
/// <summary>
/// GitLab <see cref="IGitRemoteFeatures"/>.
/// </summary>
sealed class GitLabRemoteFeatures : GitRemoteFeaturesBase
{
/// <inheritdoc />
public override string TestMergeRefSpecFormatter => "merge-requests/{0}/head:{1}";
/// <inheritdoc />
public override string TestMergeLocalBranchNameFormatter => "merge-requests/{0}/headrefs/heads/{1}";
/// <inheritdoc />
public override RemoteGitProvider? RemoteGitProvider => Api.Models.RemoteGitProvider.GitLab;
/// <inheritdoc />
public override string RemoteRepositoryOwner { get; }
/// <inheritdoc />
public override string RemoteRepositoryName { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GitLabRemoteFeatures"/> <see langword="class"/>.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="GitRemoteFeaturesBase"/>.</param>
/// <param name="remoteUrl">The remote repository <see cref="Uri"/>.</param>
public GitLabRemoteFeatures(ILogger<GitLabRemoteFeatures> logger, Uri remoteUrl)
: base(logger, remoteUrl)
{
RemoteRepositoryOwner = remoteUrl.Segments[1].TrimEnd('/');
RemoteRepositoryName = remoteUrl.Segments[2].TrimEnd('/');
}
/// <inheritdoc />
protected override async Task<Models.TestMerge> 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
};
}
}
}
}
@@ -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
{
/// <summary>
/// Base <see langword="class"/> for implementing <see cref="IGitRemoteFeatures"/>.
/// </summary>
abstract class GitRemoteFeaturesBase : IGitRemoteFeatures
{
/// <inheritdoc />
public abstract string TestMergeRefSpecFormatter { get; }
/// <inheritdoc />
public abstract string TestMergeLocalBranchNameFormatter { get; }
/// <inheritdoc />
public abstract RemoteGitProvider? RemoteGitProvider { get; }
/// <inheritdoc />
public abstract string RemoteRepositoryOwner { get; }
/// <inheritdoc />
public abstract string RemoteRepositoryName { get; }
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="GitRemoteFeaturesBase"/>.
/// </summary>
protected ILogger<GitRemoteFeaturesBase> Logger { get; }
/// <summary>
/// Cache of created <see cref="Models.TestMerge"/>s.
/// </summary>
readonly Dictionary<TestMergeParameters, Models.TestMerge> cachedLookups;
/// <summary>
/// Initializes a new instance of the <see cref="GitRemoteFeaturesBase"/> <see langword="class"/>.
/// </summary>
/// <param name="logger">The value of <see cref="Logger"/>.</param>
/// <param name="remoteUrl">The remote repository <see cref="Uri"/>.</param>
public GitRemoteFeaturesBase(ILogger<GitRemoteFeaturesBase> logger, Uri remoteUrl)
{
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
if (remoteUrl == null)
throw new ArgumentNullException(nameof(remoteUrl));
cachedLookups = new Dictionary<TestMergeParameters, Models.TestMerge>();
}
/// <inheritdoc />
public async Task<Models.TestMerge> 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;
}
/// <summary>
/// Implementation of <see cref="GetTestMerge(TestMergeParameters, RepositorySettings, CancellationToken)"/>
/// </summary>
/// <param name="parameters">The <see cref="TestMergeParameters"/>.</param>
/// <param name="repositorySettings">The <see cref="RepositorySettings"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Models.TestMerge"/> of the <paramref name="parameters"/>.</returns>
protected abstract Task<Models.TestMerge> GetTestMergeImpl(
TestMergeParameters parameters,
RepositorySettings repositorySettings,
CancellationToken cancellationToken);
}
}
@@ -0,0 +1,80 @@
using Microsoft.Extensions.Logging;
using System;
using Tgstation.Server.Host.Core;
namespace Tgstation.Server.Host.Components.Repository
{
/// <inheritdoc />
sealed class GitRemoteFeaturesFactory : IGitRemoteFeaturesFactory
{
/// <summary>
/// The <see cref="IGitHubClientFactory"/> for the <see cref="GitRemoteFeaturesFactory"/>.
/// </summary>
readonly IGitHubClientFactory gitHubClientFactory;
/// <summary>
/// The <see cref="ILoggerFactory"/> for the <see cref="GitRemoteFeaturesFactory"/>.
/// </summary>
readonly ILoggerFactory loggerFactory;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="GitRemoteFeaturesFactory"/>.
/// </summary>
readonly ILogger<GitRemoteFeaturesFactory> logger;
/// <summary>
/// Initializes a new instance of the <see cref="GitRemoteFeaturesFactory"/> <see langword="class"/>.
/// </summary>
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public GitRemoteFeaturesFactory(
IGitHubClientFactory gitHubClientFactory,
ILoggerFactory loggerFactory,
ILogger<GitRemoteFeaturesFactory> 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));
}
/// <inheritdoc />
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<GitHubRemoteFeatures>(),
primaryRemoteUrl);
case "GITLAB.COM":
case "WWW.GITLAB.COM":
case "GIT.GITLAB.COM":
return new GitLabRemoteFeatures(
loggerFactory.CreateLogger<GitLabRemoteFeatures>(),
primaryRemoteUrl);
default:
logger.LogTrace("Unknown git remote: {0}", primaryRemoteUrl);
break;
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Error parsing remote git provider.");
}
return new DefaultGitRemoteFeatures();
}
}
}
@@ -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
{
/// <summary>
/// Additional information from git remotes.
/// </summary>
public interface IGitRemoteAdditionalInformation : IGitRemoteInformation
{
/// <summary>
/// Retrieve the <see cref="Models.TestMerge"/> representation of given test merge <paramref name="parameters"/>.
/// </summary>
/// <param name="parameters">The <see cref="TestMergeParameters"/>.</param>
/// <param name="repositorySettings">The <see cref="RepositorySettings"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Models.TestMerge"/> of the <paramref name="parameters"/>.</returns>
Task<Models.TestMerge> GetTestMerge(
TestMergeParameters parameters,
RepositorySettings repositorySettings,
CancellationToken cancellationToken);
}
}
@@ -1,15 +1,18 @@
using Tgstation.Server.Api.Models.Internal;
namespace Tgstation.Server.Host.Components.Repository
{
/// <summary>
/// Provides features for remote git services
/// </summary>
interface IGitRemoteFeatures : IGitRemoteInformation
interface IGitRemoteFeatures : IGitRemoteAdditionalInformation
{
/// <summary>
/// Gets a formatter string which creates the remote refspec for fetching the HEAD of passed in pull request number.
/// </summary>
string TestMergeRefSpecFormatter { get; }
/// <summary>
/// Get
/// </summary>
string TestMergeLocalBranchNameFormatter { get; }
}
}
@@ -0,0 +1,15 @@
namespace Tgstation.Server.Host.Components.Repository
{
/// <summary>
/// Factory for creating <see cref="IGitRemoteFeatures"/>.
/// </summary>
interface IGitRemoteFeaturesFactory
{
/// <summary>
/// Create the <see cref="IGitRemoteFeatures"/> for a given <paramref name="repository"/>.
/// </summary>
/// <param name="repository">The <see cref="IRepository"/> to create <see cref="IGitRemoteFeatures"/> for.</param>
/// <returns>A new <see cref="IGitRemoteFeatures"/> instance.</returns>
IGitRemoteFeatures CreateGitRemoteFeatures(IRepository repository);
}
}
@@ -21,8 +21,8 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
/// <param name="path">The full path to the <see cref="LibGit2Sharp.IRepository"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Tuple{T1, T2}"/> containing the loaded <see cref="LibGit2Sharp.IRepository"/> and the associated <see cref="IGitRemoteFeatures"/>.</returns>
Task<Tuple<LibGit2Sharp.IRepository, IGitRemoteFeatures>> CreateFromPath(string path, CancellationToken cancellationToken);
/// <returns>A <see cref="Task{TResult}"/> resulting in the loaded <see cref="LibGit2Sharp.IRepository"/>.</returns>
Task<LibGit2Sharp.IRepository> CreateFromPath(string path, CancellationToken cancellationToken);
/// <summary>
/// Clone a remote <see cref="LibGit2Sharp.IRepository"/>.
@@ -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
{
/// <summary>
/// Represents an on-disk git repository
/// </summary>
public interface IRepository : IGitRemoteInformation, IDisposable
public interface IRepository : IGitRemoteAdditionalInformation, IDisposable
{
/// <summary>
/// If <see cref="Reference"/> tracks an upstream branch
@@ -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
}
/// <inheritdoc />
public async Task<Tuple<LibGit2Sharp.IRepository, IGitRemoteFeatures>> CreateFromPath(string path, CancellationToken cancellationToken)
public async Task<LibGit2Sharp.IRepository> 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;
}
/// <inheritdoc />
@@ -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
/// <param name="ioMananger">The value of <see cref="ioMananger"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
/// <param name="gitRemoteFeatures">The value of <see cref="gitRemoteFeatures"/>.</param>
/// <param name="gitRemoteFeaturesFactory">The <see cref="IGitRemoteFeaturesFactory"/> to provide the value of <see cref="gitRemoteFeatures"/>.</param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="onDispose">The value if <see cref="onDispose"/></param>
public Repository(
@@ -132,7 +133,7 @@ namespace Tgstation.Server.Host.Components.Repository
IIOManager ioMananger,
IEventConsumer eventConsumer,
ICredentialsProvider credentialsProvider,
IGitRemoteFeatures gitRemoteFeatures,
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
ILogger<Repository> 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);
}
/// <inheritdoc />
@@ -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<string> { refSpec };
@@ -774,5 +779,14 @@ namespace Tgstation.Server.Host.Components.Repository
return false;
}, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
/// <inheritdoc />
public Task<Models.TestMerge> GetTestMerge(
TestMergeParameters parameters,
RepositorySettings repositorySettings,
CancellationToken cancellationToken) => gitRemoteFeatures.GetTestMerge(
parameters,
repositorySettings,
cancellationToken);
}
}
@@ -40,6 +40,11 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
readonly IEventConsumer eventConsumer;
/// <summary>
/// The <see cref="IGitRemoteFeaturesFactory"/> for the <see cref="RepositoryManager"/>
/// </summary>
readonly IGitRemoteFeaturesFactory gitRemoteFeaturesFactory;
/// <summary>
/// The <see cref="ILogger"/> created <see cref="Repository"/>s
/// </summary>
@@ -62,6 +67,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="commands">The value of <see cref="commands"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="gitRemoteFeaturesFactory">The value of <see cref="gitRemoteFeaturesFactory"/>.</param>
/// <param name="repositoryLogger">The value of <see cref="repositoryLogger"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public RepositoryManager(
@@ -69,6 +75,7 @@ namespace Tgstation.Server.Host.Components.Repository
ILibGit2Commands commands,
IIOManager ioManager,
IEventConsumer eventConsumer,
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
ILogger<Repository> repositoryLogger,
ILogger<RepositoryManager> 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
{
@@ -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<int, Octokit.PullRequest> 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<int, Octokit.PullRequest>();
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);
}
}
}
@@ -306,24 +306,23 @@ namespace Tgstation.Server.Host.Core
services.AddSingleton<INetworkPromptReaper, PosixNetworkPromptReaper>();
}
// configure misc services
// configure component/misc services
services.AddScoped<IPortAllocator, PortAllocator>();
services.AddTransient<IActionResultExecutor<LimitedFileStreamResult>, LimitedFileStreamResultExecutor>();
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
services.AddSingleton<IGitHubClientFactory, GitHubClientFactory>();
services.AddSingleton<IProcessExecutor, ProcessExecutor>();
services.AddSingleton<IServerPortProvider, ServerPortProivder>();
services.AddSingleton<ITopicClientFactory, TopicClientFactory>();
services.AddSingleton<FileTransferService>();
services.AddSingleton<IFileTransferStreamHandler>(x => x.GetRequiredService<FileTransferService>());
services.AddSingleton<IFileTransferTicketProvider>(x => x.GetRequiredService<FileTransferService>());
// configure component services
services.AddSingleton<IInstanceFactory, InstanceFactory>();
services.AddSingleton<IGitRemoteFeaturesFactory, GitRemoteFeaturesFactory>();
services.AddSingleton<ILibGit2RepositoryFactory, LibGit2RepositoryFactory>();
services.AddSingleton<ILibGit2Commands, LibGit2Commands>();
services.AddSingleton<IProviderFactory, ProviderFactory>();
services.AddSingleton<IChatManagerFactory, ChatManagerFactory>();
services.AddSingleton<IInstanceFactory, InstanceFactory>();
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
services.AddSingleton<FileTransferService>();
services.AddSingleton<IFileTransferStreamHandler>(x => x.GetRequiredService<FileTransferService>());
services.AddSingleton<IFileTransferTicketProvider>(x => x.GetRequiredService<FileTransferService>());
// configure root services
services.AddSingleton<IJobManager, JobManager>();
+3 -1
View File
@@ -1,9 +1,11 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Host.Models
{
/// <inheritdoc />
#pragma warning disable CA1724 // naming conflict with gitlab package
public sealed class Job : Api.Models.Internal.Job
#pragma warning restore CA1724
{
/// <summary>
/// See <see cref="Api.Models.Job.StartedBy"/>
@@ -65,6 +65,7 @@
<PackageReference Include="Cyberboss.AspNetCore.AsyncInitializer" Version="1.2.0" />
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.6" />
<PackageReference Include="Discord.Net.WebSocket" Version="2.2.0" />
<PackageReference Include="GitLabApiClient" Version="1.7.0" />
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0034" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.10" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.10" />
@@ -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<ArgumentNullException>(() => new LibGit2RepositoryFactory(null));
@@ -480,7 +480,7 @@ namespace Tgstation.Server.Tests
Mock.Of<Host.IO.IIOManager>(),
Mock.Of<IEventConsumer>(),
Mock.Of<ICredentialsProvider>(),
Mock.Of<IGitRemoteFeatures>(),
Mock.Of<IGitRemoteFeaturesFactory>(),
Mock.Of<ILogger<Host.Components.Repository.Repository>>(),
() => { });