mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-29 16:11:05 +01:00
Merge pull request #682 from tgstation/GitLoggingAndFixes
Git logging and fixes
This commit is contained in:
@@ -207,9 +207,19 @@ namespace Tgstation.Server.Host.Components
|
||||
var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
|
||||
|
||||
//assume 5 steps with synchronize
|
||||
const int ProgressSections = 5;
|
||||
const int ProgressSections = 7;
|
||||
const int ProgressStep = 100 / ProgressSections;
|
||||
progressReporter(0 * ProgressStep);
|
||||
|
||||
|
||||
const int NumSteps = 3;
|
||||
var doneSteps = 0;
|
||||
|
||||
Action<int> NextProgressReporter()
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return progress => progressReporter((progress + 100 * tmpDoneSteps) / NumSteps);
|
||||
};
|
||||
|
||||
using (var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
@@ -219,17 +229,11 @@ namespace Tgstation.Server.Host.Components
|
||||
noRepo = true;
|
||||
return;
|
||||
}
|
||||
progressReporter(1 * ProgressStep);
|
||||
|
||||
var repositorySettings = await repositorySettingsTask.ConfigureAwait(false);
|
||||
|
||||
const int SecondStepProgress = 2 * ProgressStep;
|
||||
progressReporter(SecondStepProgress);
|
||||
|
||||
|
||||
//the main point of auto update is to pull the remote
|
||||
await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, x => progressReporter(SecondStepProgress + (x / ProgressSections)), jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
progressReporter(3 * ProgressStep);
|
||||
await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
var startSha = repo.Head;
|
||||
|
||||
@@ -237,21 +241,20 @@ namespace Tgstation.Server.Host.Components
|
||||
bool shouldSyncTracked;
|
||||
if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
|
||||
{
|
||||
var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, jobCancellationToken).ConfigureAwait(false);
|
||||
var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
if (!result.HasValue)
|
||||
return;
|
||||
shouldSyncTracked = result.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
await repo.ResetToOrigin(jobCancellationToken).ConfigureAwait(false);
|
||||
await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
shouldSyncTracked = true;
|
||||
}
|
||||
progressReporter(4 * ProgressStep);
|
||||
|
||||
//synch if necessary
|
||||
if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
|
||||
await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
|
||||
await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
progressReporter(5 * ProgressStep);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICredentialsProvider"/> for the <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
readonly ICredentialsProvider credentialsProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceFactory"/>
|
||||
/// </summary>
|
||||
@@ -111,7 +116,8 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/></param>
|
||||
/// <param name="watchdogFactory">The value of <see cref="watchdogFactory"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager)
|
||||
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, IServerControl serverUpdater, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IProviderFactory providerFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
@@ -128,6 +134,7 @@ namespace Tgstation.Server.Host.Components
|
||||
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
|
||||
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -148,7 +155,7 @@ namespace Tgstation.Server.Host.Components
|
||||
var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger<DmbFactory>(), metadata.CloneMetadata());
|
||||
try
|
||||
{
|
||||
var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer);
|
||||
var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger<Repository.Repository>(), loggerFactory.CreateLogger<RepositoryManager>());
|
||||
try
|
||||
{
|
||||
var byond = new ByondManager(byondIOManager, byondInstaller, loggerFactory.CreateLogger<ByondManager>());
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using LibGit2Sharp;
|
||||
using LibGit2Sharp.Handlers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class CredentialsProvider : ICredentialsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="CredentialsProvider"/>
|
||||
/// </summary>
|
||||
readonly ILogger<CredentialsProvider> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="CredentialsProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public CredentialsProvider(ILogger<CredentialsProvider> logger)
|
||||
{
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CredentialsHandler GenerateHandler(string username, string password) => (a, b, supportedCredentialTypes) =>
|
||||
{
|
||||
var hasCreds = username != null;
|
||||
var supportsUserPass = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.UsernamePassword);
|
||||
var supportsAnonymous = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.Default);
|
||||
|
||||
logger.LogTrace("Credentials requested. Present: {0}. Supports anonymous: {1}. Supports user/pass: {2}", hasCreds, supportsAnonymous, supportsUserPass);
|
||||
if (supportsUserPass)
|
||||
{
|
||||
if (hasCreds)
|
||||
return new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
};
|
||||
}
|
||||
|
||||
if (supportsAnonymous)
|
||||
return new DefaultCredentials();
|
||||
|
||||
if (hasCreds)
|
||||
throw new JobException("Remote does not support anonymous authentication!");
|
||||
|
||||
throw new JobException("Server does not support anonymous or username/password authentication!");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using LibGit2Sharp.Handlers;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
/// <summary>
|
||||
/// For generating <see cref="CredentialsHandler"/>s
|
||||
/// </summary>
|
||||
interface ICredentialsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a <see cref="CredentialsHandler"/> from a given <paramref name="username"/> and <paramref name="password"/>
|
||||
/// </summary>
|
||||
/// <param name="username">The optional username to use in the <see cref="CredentialsHandler"/></param>
|
||||
/// <param name="password">The optional password to use in the <see cref="CredentialsHandler"/></param>
|
||||
/// <returns>A new <see cref="CredentialsHandler"/></returns>
|
||||
CredentialsHandler GenerateHandler(string username, string password);
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,10 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// Checks out a given <paramref name="committish"/>
|
||||
/// </summary>
|
||||
/// <param name="committish">The sha or reference to checkout</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task CheckoutObject(string committish, CancellationToken cancellationToken);
|
||||
Task CheckoutObject(string committish, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to merge a GitHub pull request into HEAD
|
||||
@@ -70,7 +71,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="username">The username to fetch from the origin repository</param>
|
||||
/// <param name="password">The password to fetch from the origin repository</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <param name="progressReporter">Optional function to report 0-100 progress of the clone</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward or up to date, <see langword="false"/> on a merge, <see langword="null"/> on a conflict</returns>
|
||||
Task<bool?> AddTestMerge(TestMergeParameters testMergeParameters, string committerName, string committerEmail, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
|
||||
@@ -79,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
/// <param name="username">The username to fetch from the origin repository</param>
|
||||
/// <param name="password">The password to fetch from the origin repository</param>
|
||||
/// <param name="progressReporter">Optional function to report 0-100 progress of the clone</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
@@ -87,26 +88,29 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <summary>
|
||||
/// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository
|
||||
/// </summary>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD</returns>
|
||||
Task ResetToOrigin(CancellationToken cancellationToken);
|
||||
Task ResetToOrigin(Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Requires the current HEAD to be a reference. Hard resets the reference to the given sha
|
||||
/// </summary>
|
||||
/// <param name="sha">The sha hash to reset to</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD</returns>
|
||||
Task ResetToSha(string sha, CancellationToken cancellationToken);
|
||||
Task ResetToSha(string sha, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository
|
||||
/// </summary>
|
||||
/// <param name="committerName">The name of the merge committer</param>
|
||||
/// <param name="committerEmail">The e-mail of the merge committer</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward, <see langword="false"/> on a merge or up to date, <see langword="null"/> on a conflict</returns>
|
||||
Task<bool?> MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken);
|
||||
Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the synchronize event script and attempts to push any changes made to the <see cref="IRepository"/> if on a tracked branch
|
||||
@@ -116,9 +120,10 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="committerName">The name of the potential committer</param>
|
||||
/// <param name="committerEmail">The e-mail of the potential committer</param>
|
||||
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Sychronize(string username, string password, string committerName, string committerEmail, bool synchronizeTrackedBranch, CancellationToken cancellationToken);
|
||||
Task Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the current working directory to a given <paramref name="path"/>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using LibGit2Sharp;
|
||||
using LibGit2Sharp.Handlers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -66,12 +68,22 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICredentialsProvider"/> for the <see cref="Repository"/>
|
||||
/// </summary>
|
||||
readonly ICredentialsProvider credentialsProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="Repository"/>
|
||||
/// </summary>
|
||||
readonly ILogger<Repository> logger;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Action"/> to be taken when <see cref="Dispose"/> is called
|
||||
/// </summary>
|
||||
readonly Action onDispose;
|
||||
|
||||
static void GetRepositoryOwnerName(string remote, out string owner, out string name)
|
||||
void GetRepositoryOwnerName(string remote, out string owner, out string name)
|
||||
{
|
||||
//Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
|
||||
//Yes use .git twice in case it was weird
|
||||
@@ -82,20 +94,33 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
var splits = remote.Split('/');
|
||||
name = splits[splits.Length - 1];
|
||||
owner = splits[splits.Length - 2].Split('.')[0];
|
||||
|
||||
logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a given <paramref name="progressReporter"/> to a <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/>
|
||||
/// </summary>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <returns>A <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/> based on <paramref name="progressReporter"/></returns>
|
||||
static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)((((float)completedSteps) / totalSteps) * 100));
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="Repository"/>
|
||||
/// </summary>
|
||||
/// <param name="repository">The value of <see cref="repository"/></param>
|
||||
/// <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="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="onDispose">The value if <see cref="onDispose"/></param>
|
||||
public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, Action onDispose)
|
||||
public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger<Repository> logger, Action onDispose)
|
||||
{
|
||||
this.repository = repository ?? throw new ArgumentNullException(nameof(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.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
|
||||
IsGitHubRepository = Origin.Contains(GitHubUrl, StringComparison.InvariantCultureIgnoreCase);
|
||||
if (IsGitHubRepository)
|
||||
@@ -109,20 +134,57 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
logger.LogTrace("Disposing...");
|
||||
repository.Dispose();
|
||||
onDispose.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a standard set of <see cref="PushOptions"/>
|
||||
/// </summary>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="username">The username for the <see cref="credentialsProvider"/></param>
|
||||
/// <param name="password">The password for the <see cref="credentialsProvider"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A new set of <see cref="PushOptions"/></returns>
|
||||
PushOptions GeneratePushOptions(Action<int> progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions
|
||||
{
|
||||
OnPackBuilderProgress = (stage, current, total) =>
|
||||
{
|
||||
var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25;
|
||||
progressReporter(baseProgress + ((int)(25 * ((float)current) / total)));
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnPushTransferProgress = (a, sentBytes, totalBytes) =>
|
||||
{
|
||||
progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Runs a blocking force checkout to <paramref name="committish"/>
|
||||
/// </summary>
|
||||
/// <param name="committish">The committish to checkout</param>
|
||||
void RawCheckout(string committish)
|
||||
/// <param name="progressReporter">Progress reporter <see cref="Action{T}"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
void RawCheckout(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Checkout: {0}", committish);
|
||||
|
||||
progressReporter(0);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Commands.Checkout(repository, committish, new CheckoutOptions
|
||||
{
|
||||
CheckoutModifiers = CheckoutModifiers.Force
|
||||
CheckoutModifiers = CheckoutModifiers.Force,
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
|
||||
});
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
repository.RemoveUntrackedFiles();
|
||||
}
|
||||
|
||||
@@ -131,22 +193,25 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
if (testMergeParameters == null)
|
||||
throw new ArgumentNullException(nameof(testMergeParameters));
|
||||
|
||||
if (committerName == null)
|
||||
throw new ArgumentNullException(nameof(committerName));
|
||||
if (committerEmail == null)
|
||||
throw new ArgumentNullException(nameof(committerEmail));
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({4}) by <{2} ({3})>", testMergeParameters.Number, testMergeParameters.PullRequestRevision?.Substring(0, 7), committerName, committerEmail, testMergeParameters.Comment);
|
||||
|
||||
if (!IsGitHubRepository)
|
||||
throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
|
||||
throw new JobException("Test merging is only available on GitHub hosted origin repositories!");
|
||||
|
||||
var commitMessage = String.Format(CultureInfo.InvariantCulture, "Test merge of pull request #{0}{1}{2}", testMergeParameters.Number.Value, testMergeParameters.Comment != null ? Environment.NewLine : String.Empty, 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 Refspec = new List<string> { String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName) };
|
||||
var refSpec = String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName);
|
||||
var refSpecList = new List<string> { refSpec };
|
||||
var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);
|
||||
|
||||
var originalCommit = repository.Head;
|
||||
@@ -160,39 +225,45 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Fetching refspec {0}...", refSpec);
|
||||
|
||||
var remote = repository.Network.Remotes.First();
|
||||
Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions
|
||||
progressReporter(0);
|
||||
Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, refSpecList, new FetchOptions
|
||||
{
|
||||
Prune = true,
|
||||
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnTransferProgress = (a) =>
|
||||
{
|
||||
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
|
||||
progressReporter?.Invoke((int)percentage);
|
||||
var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
|
||||
progressReporter((int)percentage);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
} : new DefaultCredentials()
|
||||
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
|
||||
}, logMessage);
|
||||
}
|
||||
catch (UserCancelledException) { }
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
repository.RemoveUntrackedFiles();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
testMergeParameters.PullRequestRevision = repository.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference);
|
||||
|
||||
result = repository.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
|
||||
{
|
||||
CommitOnSuccess = commitMessage == null,
|
||||
FailOnConflict = true,
|
||||
FastForwardStrategy = FastForwardStrategy.NoFastForward,
|
||||
SkipReuc = true
|
||||
SkipReuc = true,
|
||||
OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)((((float)completedSteps) / totalSteps) * 50)))
|
||||
});
|
||||
}
|
||||
finally
|
||||
@@ -204,7 +275,9 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
if (result.Status == MergeStatus.Conflicts)
|
||||
{
|
||||
RawCheckout(originalCommit.CanonicalName ?? originalCommit.Tip.Sha);
|
||||
var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
|
||||
RawCheckout(revertTo, progressReporter, cancellationToken);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
@@ -218,27 +291,41 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
}
|
||||
|
||||
if (commitMessage != null && result.Status != MergeStatus.UpToDate)
|
||||
repository.Commit(commitMessage, sig, sig, new CommitOptions
|
||||
{
|
||||
logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
|
||||
await Task.Factory.StartNew(() => repository.Commit(commitMessage, sig, sig, new CommitOptions
|
||||
{
|
||||
PrettifyMessage = true
|
||||
});
|
||||
}), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CheckoutObject(string committish, CancellationToken cancellationToken)
|
||||
public async Task CheckoutObject(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (committish == null)
|
||||
throw new ArgumentNullException(nameof(committish));
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
logger.LogDebug("Checkout object: {0}...", committish);
|
||||
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, cancellationToken).ConfigureAwait(false);
|
||||
await Task.Factory.StartNew(() => RawCheckout(committish), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
repository.RemoveUntrackedFiles();
|
||||
RawCheckout(committish, progressReporter, cancellationToken);
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken) => Task.WhenAll(
|
||||
eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty<string>(), cancellationToken),
|
||||
Task.Factory.StartNew(() =>
|
||||
public async Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
logger.LogDebug("Fetch origin...");
|
||||
await eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty<string>(), cancellationToken).ConfigureAwait(false);
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
var remote = repository.Network.Remotes.First();
|
||||
try
|
||||
@@ -250,32 +337,31 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
OnTransferProgress = (a) =>
|
||||
{
|
||||
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
|
||||
progressReporter?.Invoke((int)percentage);
|
||||
progressReporter((int)percentage);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
} : new DefaultCredentials()
|
||||
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
|
||||
}, "Fetch origin commits");
|
||||
}
|
||||
catch (UserCancelledException)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current));
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force push the current repository HEAD to <see cref="Repository.RemoteTemporaryBranchName"/>;
|
||||
/// Force push the current repository HEAD to <see cref="RemoteTemporaryBranchName"/>;
|
||||
/// </summary>
|
||||
/// <param name="username">The username to fetch from the origin repository</param>
|
||||
/// <param name="password">The password to fetch from the origin repository</param>
|
||||
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task PushHeadToTemporaryBranch(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
Task PushHeadToTemporaryBranch(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
logger.LogInformation("Pushing changes to temporary remote branch...");
|
||||
var branch = repository.CreateBranch(RemoteTemporaryBranchName);
|
||||
try
|
||||
{
|
||||
@@ -283,17 +369,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
var remote = repository.Network.Remotes.First();
|
||||
try
|
||||
{
|
||||
repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), new PushOptions
|
||||
{
|
||||
OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
} : new DefaultCredentials()
|
||||
});
|
||||
repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), GeneratePushOptions(progressReporter, username, password, cancellationToken));
|
||||
}
|
||||
catch (UserCancelledException)
|
||||
{
|
||||
@@ -307,21 +383,41 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ResetToOrigin(CancellationToken cancellationToken)
|
||||
public async Task ResetToOrigin(Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!repository.Head.IsTracking)
|
||||
throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!");
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
if (!Tracking)
|
||||
throw new JobException("Cannot reset to origin while not on a tracked reference!");
|
||||
logger.LogTrace("Reset to origin...");
|
||||
var trackedBranch = repository.Head.TrackedBranch;
|
||||
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
|
||||
await ResetToSha(trackedBranch.Tip.Sha, cancellationToken).ConfigureAwait(false);
|
||||
await ResetToSha(trackedBranch.Tip.Sha, progressReporter, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ResetToSha(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
public Task ResetToSha(string sha, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
repository.Reset(ResetMode.Hard, sha);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (sha == null)
|
||||
throw new ArgumentNullException(nameof(sha));
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7));
|
||||
|
||||
repository.RemoveUntrackedFiles();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var gitObject = repository.Lookup(sha, ObjectType.Commit);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (gitObject == null)
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot reset to non-existent SHA: {0}", sha));
|
||||
|
||||
repository.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
|
||||
{
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
|
||||
});
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -329,12 +425,16 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
logger.LogTrace("Copying to {0}...", path);
|
||||
await ioMananger.CopyDirectory(".", path, new List<string> { ".git" }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool?> MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken)
|
||||
public async Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
MergeResult result = null;
|
||||
Branch trackedBranch = null;
|
||||
|
||||
@@ -342,23 +442,30 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
if (!repository.Head.IsTracking)
|
||||
throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!");
|
||||
trackedBranch = repository.Head.TrackedBranch;
|
||||
if (!Tracking)
|
||||
throw new JobException("Cannot reset to origin while not on a tracked reference!");
|
||||
|
||||
repository.RemoveUntrackedFiles();
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
trackedBranch = repository.Head.TrackedBranch;
|
||||
logger.LogDebug("Merge origin/{2}: <{0} ({1})>", committerName, committerEmail, trackedBranch.FriendlyName);
|
||||
result = repository.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
|
||||
{
|
||||
CommitOnSuccess = true,
|
||||
FailOnConflict = true,
|
||||
FastForwardStrategy = FastForwardStrategy.Default,
|
||||
SkipReuc = true,
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
|
||||
});
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (result.Status == MergeStatus.Conflicts)
|
||||
{
|
||||
RawCheckout(oldHead.CanonicalName);
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
|
||||
RawCheckout(oldHead.CanonicalName, progressReporter, cancellationToken);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
@@ -375,57 +482,73 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Sychronize(string username, string password, string committerName, string committerEmail, bool synchronizeTrackedBranch, CancellationToken cancellationToken)
|
||||
public async Task Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken)
|
||||
{
|
||||
if (committerName == null)
|
||||
throw new ArgumentNullException(nameof(committerName));
|
||||
if (committerEmail == null)
|
||||
throw new ArgumentNullException(nameof(committerEmail));
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
if (username == null && password == null)
|
||||
{
|
||||
logger.LogTrace("Not synchronizing due to lack of credentials!");
|
||||
return;
|
||||
}
|
||||
logger.LogTrace("Begin Synchronize...");
|
||||
|
||||
if (username == null)
|
||||
throw new ArgumentNullException(nameof(username));
|
||||
if (password == null)
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
if (committerName == null)
|
||||
throw new ArgumentNullException(nameof(committerName));
|
||||
if (committerEmail == null)
|
||||
throw new ArgumentNullException(nameof(committerEmail));
|
||||
|
||||
var startHead = Head;
|
||||
|
||||
logger.LogTrace("Configuring <{0} ({1})> as author/committer", committerName, committerEmail);
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
repository.Config.Set("user.name", committerName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
repository.Config.Set("user.email", committerEmail);
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
|
||||
if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List<string> { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false))
|
||||
return;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List<string> { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
logger.LogDebug("Aborted synchronize due to event handler response!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger.LogTrace("Cleaning untracked files...");
|
||||
await Task.Factory.StartNew(repository.RemoveUntrackedFiles, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!synchronizeTrackedBranch)
|
||||
{
|
||||
await PushHeadToTemporaryBranch(username, password, cancellationToken).ConfigureAwait(false);
|
||||
await PushHeadToTemporaryBranch(username, password, progressReporter, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Head == startHead || !repository.Head.IsTracking)
|
||||
var sameHead = Head == startHead;
|
||||
if (sameHead || !Tracking)
|
||||
{
|
||||
logger.LogTrace("Aborted synchronize due to {0}!", sameHead ? "lack of changes" : "not being on tracked reference");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation("Synchronizing with origin...");
|
||||
|
||||
await Task.Factory.StartNew(() =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var remote = repository.Network.Remotes.First();
|
||||
try
|
||||
{
|
||||
repository.Network.Push(repository.Head, new PushOptions
|
||||
{
|
||||
OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
} : new DefaultCredentials()
|
||||
});
|
||||
repository.Network.Push(repository.Head, GeneratePushOptions(progressReporter, username, password, cancellationToken));
|
||||
}
|
||||
catch (UserCancelledException)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using LibGit2Sharp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -27,6 +28,21 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly IEventConsumer eventConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICredentialsProvider"/> for the <see cref="RepositoryManager"/>
|
||||
/// </summary>
|
||||
readonly ICredentialsProvider credentialsProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> created <see cref="Repository"/>s
|
||||
/// </summary>
|
||||
readonly ILogger<Repository> repositoryLogger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="RepositoryManager"/>
|
||||
/// </summary>
|
||||
readonly ILogger<RepositoryManager> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RepositorySettings"/> for the <see cref="RepositoryManager"/>
|
||||
/// </summary>
|
||||
@@ -43,20 +59,36 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="repositorySettings">The value of <see cref="repositorySettings"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer)
|
||||
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
|
||||
/// <param name="repositoryLogger">The value of <see cref="repositoryLogger"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger<Repository> repositoryLogger, ILogger<RepositoryManager> logger)
|
||||
{
|
||||
this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
|
||||
this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => semaphore.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
logger.LogTrace("Disposing...");
|
||||
semaphore.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IRepository> CloneRepository(Uri url, string initialBranch, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (url == null)
|
||||
throw new ArgumentNullException(nameof(url));
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogInformation("Begin clone {0} (Branch: {1})", url, initialBranch);
|
||||
lock (this)
|
||||
{
|
||||
if (CloneInProgress)
|
||||
@@ -66,6 +98,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
try
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
logger.LogTrace("Semaphore acquired");
|
||||
if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
@@ -87,11 +121,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
|
||||
BranchName = initialBranch,
|
||||
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password
|
||||
} : new DefaultCredentials()
|
||||
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
|
||||
});
|
||||
}
|
||||
catch (UserCancelledException) { }
|
||||
@@ -102,13 +132,22 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Deleting partially cloned repository...");
|
||||
await ioManager.DeleteDirectory(".", default).ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogDebug("Error deleting partially cloned repository! Exception: {0}", e);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Repository exists, clone aborted!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
logger.LogInformation("Clone complete!");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -120,6 +159,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <inheritdoc />
|
||||
public async Task<IRepository> LoadRepository(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Begin LoadRepository...");
|
||||
lock (this)
|
||||
if (CloneInProgress)
|
||||
throw new InvalidOperationException("The repository is being cloned!");
|
||||
@@ -129,28 +169,41 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Creating LibGit2Sharp.Repository...");
|
||||
repo = new LibGit2Sharp.Repository(ioManager.ResolvePath("."));
|
||||
}
|
||||
catch (RepositoryNotFoundException) { }
|
||||
catch (RepositoryNotFoundException e)
|
||||
{
|
||||
logger.LogDebug("Repository not found!");
|
||||
logger.LogTrace("Exception: {0}", e);
|
||||
}
|
||||
catch
|
||||
{
|
||||
semaphore.Release();
|
||||
throw;
|
||||
}
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
|
||||
if (repo == null)
|
||||
{
|
||||
semaphore.Release();
|
||||
return null;
|
||||
}
|
||||
var localSemaphore = semaphore;
|
||||
return new Repository(repo, ioManager, eventConsumer, () =>
|
||||
return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
|
||||
{
|
||||
localSemaphore?.Release();
|
||||
localSemaphore = null;
|
||||
logger.LogTrace("Releasing semaphore due to Repository disposal...");
|
||||
semaphore.Release();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteRepository(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogInformation("Deleting repository...");
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
logger.LogTrace("Semaphore acquired, deleting Repository directory...");
|
||||
await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,10 +434,19 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName;
|
||||
|
||||
var numFetches = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0);
|
||||
var doneFetches = 0;
|
||||
if (numFetches > 0)
|
||||
progressReporter(0);
|
||||
var hardResettingToOriginReference = model.UpdateFromOrigin == true && model.Reference != null;
|
||||
|
||||
var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
|
||||
var doneSteps = 0;
|
||||
|
||||
Action<int> NextProgressReporter()
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return progress => progressReporter((progress + 100 * tmpDoneSteps) / numSteps);
|
||||
};
|
||||
|
||||
progressReporter(0);
|
||||
|
||||
//get a base line for where we are
|
||||
Models.RevisionInformation lastRevisionInfo = null;
|
||||
@@ -465,19 +474,21 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
if (!repo.Tracking)
|
||||
throw new JobException("Not on an updatable reference!");
|
||||
await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, x => progressReporter(x / numFetches), ct).ConfigureAwait(false);
|
||||
doneFetches = 1;
|
||||
await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
doneSteps = 1;
|
||||
if (!modelHasShaOrReference)
|
||||
{
|
||||
var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, ct).ConfigureAwait(false);
|
||||
var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
if (!fastForward.HasValue)
|
||||
throw new JobException("Merge conflict occurred during origin update!");
|
||||
await UpdateRevInfo().ConfigureAwait(false);
|
||||
if (fastForward.Value)
|
||||
{
|
||||
lastRevisionInfo.OriginCommitSha = repo.Head;
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, true, ct).ConfigureAwait(false);
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
NextProgressReporter()(100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,16 +504,18 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
|
||||
throw new JobException("Attempted to checkout a SHA or reference that was actually the opposite!");
|
||||
|
||||
await repo.CheckoutObject(committish, ct).ConfigureAwait(false);
|
||||
await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin
|
||||
}
|
||||
else
|
||||
NextProgressReporter()(100);
|
||||
|
||||
if (model.UpdateFromOrigin == true && model.Reference != null)
|
||||
if (hardResettingToOriginReference)
|
||||
{
|
||||
if (!repo.Tracking)
|
||||
throw new JobException("Checked out reference does not track a remote object!");
|
||||
await repo.ResetToOrigin(ct).ConfigureAwait(false);
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, true, ct).ConfigureAwait(false);
|
||||
await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
|
||||
//repo head is on origin so force this
|
||||
//will update the db if necessary
|
||||
@@ -619,7 +632,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (revInfoWereLookingFor != null)
|
||||
{
|
||||
//goteem
|
||||
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, cancellationToken).ConfigureAwait(false);
|
||||
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false);
|
||||
lastRevisionInfo = revInfoWereLookingFor;
|
||||
}
|
||||
|
||||
@@ -660,12 +673,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (I.PullRequestRevision == null && pr != null)
|
||||
I.PullRequestRevision = pr.Head.Sha;
|
||||
|
||||
var mergeResult = await repo.AddTestMerge(I, committerName, currentModel.CommitterEmail, currentModel.AccessUser, currentModel.AccessToken, x => progressReporter((x + 100 * doneFetches) / numFetches), ct).ConfigureAwait(false);
|
||||
var mergeResult = await repo.AddTestMerge(I, committerName, currentModel.CommitterEmail, currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
|
||||
if (!mergeResult.HasValue) //conflict, we don't care, dd already knows
|
||||
continue;
|
||||
|
||||
++doneFetches;
|
||||
++doneSteps;
|
||||
|
||||
var revInfoUpdateTask = UpdateRevInfo();
|
||||
|
||||
@@ -695,17 +708,21 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (startSha != repo.Head)
|
||||
{
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, false, ct).ConfigureAwait(false);
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
|
||||
await UpdateRevInfo().ConfigureAwait(false);
|
||||
}
|
||||
await databaseContext.Save(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
doneSteps = 0;
|
||||
numSteps = 2;
|
||||
//the stuff didn't make it into the db, forget what we've done and abort
|
||||
await repo.CheckoutObject(startReference ?? startSha, default).ConfigureAwait(false);
|
||||
await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
|
||||
if (startReference != null && repo.Head != startSha)
|
||||
await repo.ResetToSha(startSha, default).ConfigureAwait(false);
|
||||
await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
|
||||
else
|
||||
progressReporter(100);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Controllers;
|
||||
@@ -183,6 +184,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
|
||||
services.AddSingleton<ITokenFactory, TokenFactory>();
|
||||
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
|
||||
services.AddSingleton<ICredentialsProvider, CredentialsProvider>();
|
||||
|
||||
services.AddSingleton<IGitHubClientFactory, GitHubClientFactory>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user