Massive Repository Cleanup:

- Added (I)CredentialsProvider for easier formatting
- Refactored out several portions of duplicated Repository code
- Many more repository functions now take progress reporters via OnCheckoutProgress and PushOptions progress handlers
- Added much needed logging to Repository functions
- Converted most Repository InvalidOperationExceptions to JobExceptions
- Added needed calls to RemoveUntrackedFiles() in several places
- Progress reporters are now required arguments
This commit is contained in:
Cyberboss
2018-09-19 11:46:13 -04:00
parent 305d076b29
commit 3f821d22f8
9 changed files with 346 additions and 145 deletions
@@ -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, loggerFactory.CreateLogger<Repository.Repository>(), loggerFactory.CreateLogger<RepositoryManager>());
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,5 @@
using LibGit2Sharp;
using LibGit2Sharp.Handlers;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@@ -67,6 +68,11 @@ 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>
@@ -77,7 +83,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// </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
@@ -88,21 +94,32 @@ 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, ILogger<Repository> logger, 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);
@@ -117,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();
}
@@ -139,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;
@@ -168,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
@@ -212,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();
}
@@ -226,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
@@ -258,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
{
@@ -291,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)
{
@@ -315,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 />
@@ -337,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;
@@ -350,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();
}
@@ -383,64 +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);
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)
{
@@ -28,6 +28,11 @@ 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>
@@ -54,20 +59,24 @@ 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>
/// <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, ILogger<Repository> repositoryLogger, ILogger<RepositoryManager> logger)
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()
{
logger.LogTrace("Disposed");
logger.LogTrace("Disposing...");
semaphore.Dispose();
}
@@ -112,31 +121,7 @@ namespace Tgstation.Server.Host.Components.Repository
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
BranchName = initialBranch,
CredentialsProvider = (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!");
}
CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
});
}
catch (UserCancelledException) { }
@@ -203,7 +188,11 @@ namespace Tgstation.Server.Host.Components.Repository
semaphore.Release();
return null;
}
return new Repository(repo, ioManager, eventConsumer, repositoryLogger, () => semaphore.Release());
return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
{
logger.LogTrace("Releasing semaphore due to Repository disposal...");
semaphore.Release();
});
}
/// <inheritdoc />
@@ -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>();