Merge pull request #1317 from tgstation/CompileProcess

Support for describing a job's current stage
This commit is contained in:
Jordan Brown
2021-09-14 21:06:19 -04:00
committed by GitHub
18 changed files with 214 additions and 116 deletions
+3 -3
View File
@@ -5,9 +5,9 @@
<PropertyGroup>
<TgsCoreVersion>4.15.0</TgsCoreVersion>
<TgsConfigVersion>4.0.0</TgsConfigVersion>
<TgsApiVersion>9.2.0</TgsApiVersion>
<TgsApiLibraryVersion>9.2.0</TgsApiLibraryVersion>
<TgsClientVersion>10.2.0</TgsClientVersion>
<TgsApiVersion>9.3.0</TgsApiVersion>
<TgsApiLibraryVersion>9.3.0</TgsApiLibraryVersion>
<TgsClientVersion>10.3.0</TgsClientVersion>
<TgsDmapiVersion>6.0.4</TgsDmapiVersion>
<TgsInteropVersion>5.3.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.1.1</TgsHostWatchdogVersion>
@@ -21,5 +21,11 @@
/// </summary>
[ResponseOptions]
public int? Progress { get; set; }
/// <summary>
/// Optional description of the job's current .
/// </summary>
[ResponseOptions]
public string? Stage { get; set; }
}
}
@@ -113,6 +113,11 @@ namespace Tgstation.Server.Host.Components.Deployment
/// </summary>
string currentDreamMakerOutput;
/// <summary>
/// Current stage to report on the job.
/// </summary>
string currentStage;
/// <summary>
/// If a compile job is running.
/// </summary>
@@ -178,7 +183,7 @@ namespace Tgstation.Server.Host.Components.Deployment
public async Task DeploymentProcess(
Models.Job job,
IDatabaseContextFactory databaseContextFactory,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (job == null)
@@ -450,7 +455,7 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <param name="apiValidateTimeout">The API validation timeout.</param>
/// <param name="repository">The <see cref="IRepository"/>.</param>
/// <param name="remoteDeploymentManager">The <see cref="IRemoteDeploymentManager"/>.</param>
/// <param name="progressReporter">The progress reporting <see cref="Action{T}"/>.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="estimatedDuration">The optional estimated <see cref="TimeSpan"/> of the compilation.</param>
/// <param name="localCommitExistsOnRemote">Whether or not the <paramref name="repository"/>'s current commit exists on the remote repository.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
@@ -461,7 +466,7 @@ namespace Tgstation.Server.Host.Components.Deployment
uint apiValidateTimeout,
IRepository repository,
IRemoteDeploymentManager remoteDeploymentManager,
Action<int> progressReporter,
JobProgressReporter progressReporter,
TimeSpan? estimatedDuration,
bool localCommitExistsOnRemote,
CancellationToken cancellationToken)
@@ -469,7 +474,9 @@ namespace Tgstation.Server.Host.Components.Deployment
logger.LogTrace("Begin Compile");
using var progressCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var progressTask = estimatedDuration.HasValue ? ProgressTask(progressReporter, estimatedDuration.Value, progressCts.Token) : Task.CompletedTask;
currentStage = "Reserving BYOND version";
var progressTask = ProgressTask(progressReporter, estimatedDuration, progressCts.Token);
try
{
using var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false);
@@ -490,6 +497,7 @@ namespace Tgstation.Server.Host.Components.Deployment
RepositoryOrigin = repository.Origin.ToString(),
};
currentStage = "Creating remote deployment notification";
await remoteDeploymentManager.StartDeployment(
repository,
job,
@@ -525,6 +533,7 @@ namespace Tgstation.Server.Host.Components.Deployment
catch (OperationCanceledException)
{
// DCT: Cancellation token is for job, delaying here is fine
currentStage = "Running CompileCancelled event";
await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty<string>(), default).ConfigureAwait(false);
throw;
}
@@ -561,7 +570,8 @@ namespace Tgstation.Server.Host.Components.Deployment
try
{
// copy the repository
logger.LogTrace("Copying repository to game directory...");
logger.LogTrace("Copying repository to game directory");
currentStage = "Copying repository";
var resolvedOutputDirectory = ioManager.ResolvePath(outputDirectory);
var repoOrigin = repository.Origin;
using (repository)
@@ -570,6 +580,7 @@ namespace Tgstation.Server.Host.Components.Deployment
// repository closed now
// run precompile scripts
currentStage = "Running PreCompile event";
await eventConsumer.HandleEvent(
EventType.CompileStart,
new List<string>
@@ -582,9 +593,10 @@ namespace Tgstation.Server.Host.Components.Deployment
.ConfigureAwait(false);
// determine the dme
currentStage = "Determining .dme";
if (job.DmeName == null)
{
logger.LogTrace("Searching for available .dmes...");
logger.LogTrace("Searching for available .dmes");
var foundPaths = await ioManager.GetFilesWithExtension(resolvedOutputDirectory, DmeExtension, true, cancellationToken).ConfigureAwait(false);
var foundPath = foundPaths.FirstOrDefault();
if (foundPath == default)
@@ -603,9 +615,11 @@ namespace Tgstation.Server.Host.Components.Deployment
logger.LogDebug("Selected {0}.dme for compilation!", job.DmeName);
currentStage = "Modifying .dme";
await ModifyDme(job, cancellationToken).ConfigureAwait(false);
// run precompile scripts
currentStage = "Running PreDreamMaker event";
await eventConsumer.HandleEvent(
EventType.PreDreamMaker,
new List<string>
@@ -618,6 +632,7 @@ namespace Tgstation.Server.Host.Components.Deployment
.ConfigureAwait(false);
// run compiler
currentStage = "Running DreamMaker";
var exitCode = await RunDreamMaker(byondLock.DreamMakerPath, job, cancellationToken).ConfigureAwait(false);
// verify api
@@ -628,6 +643,7 @@ namespace Tgstation.Server.Host.Components.Deployment
ErrorCode.DreamMakerExitCode,
new JobException($"Exit code: {exitCode}{Environment.NewLine}{Environment.NewLine}{job.Output}"));
currentStage = "Validating DMAPI";
await VerifyApi(
apiValidateTimeout,
dreamMakerSettings.ApiValidationSecurityLevel.Value,
@@ -641,6 +657,7 @@ namespace Tgstation.Server.Host.Components.Deployment
catch (JobException)
{
// DD never validated or compile failed
currentStage = "Running CompileFailure event";
await eventConsumer.HandleEvent(
EventType.CompileFailure,
new List<string>
@@ -654,6 +671,7 @@ namespace Tgstation.Server.Host.Components.Deployment
throw;
}
currentStage = "Running CompileComplete event";
await eventConsumer.HandleEvent(
EventType.CompileComplete,
new List<string>
@@ -665,6 +683,7 @@ namespace Tgstation.Server.Host.Components.Deployment
.ConfigureAwait(false);
logger.LogTrace("Applying static game file symlinks...");
currentStage = "Symlinking GameStaticFiles";
// symlink in the static data
await configuration.SymlinkStaticFilesTo(resolvedOutputDirectory, cancellationToken).ConfigureAwait(false);
@@ -673,6 +692,7 @@ namespace Tgstation.Server.Host.Components.Deployment
}
catch (Exception ex)
{
currentStage = "Cleaning output directory";
await CleanupFailedCompile(job, remoteDeploymentManager, ex).ConfigureAwait(false);
throw;
}
@@ -681,22 +701,22 @@ namespace Tgstation.Server.Host.Components.Deployment
/// <summary>
/// Gradually triggers a given <paramref name="progressReporter"/> over a given <paramref name="estimatedDuration"/>.
/// </summary>
/// <param name="progressReporter">The <see cref="Action{T1}"/> to report progress.</param>
/// <param name="estimatedDuration">A <see cref="TimeSpan"/> representing the duration to give progress over.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="estimatedDuration">A <see cref="TimeSpan"/> representing the duration to give progress over if any.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task ProgressTask(Action<int> progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken)
async Task ProgressTask(JobProgressReporter progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
{
progressReporter(0);
var sleepInterval = estimatedDuration / 100;
progressReporter(currentStage, estimatedDuration.HasValue ? (int?)0 : null);
var sleepInterval = estimatedDuration.HasValue ? estimatedDuration.Value / 100 : TimeSpan.FromMilliseconds(250);
logger.LogDebug("Compile is expected to take: {0}", estimatedDuration);
try
{
for (var iteration = 0; iteration < 99; ++iteration)
for (var iteration = 0; iteration < (estimatedDuration.HasValue ? 99 : Int32.MaxValue); ++iteration)
{
await Task.Delay(sleepInterval, cancellationToken).ConfigureAwait(false);
progressReporter(iteration + 1);
progressReporter(currentStage, estimatedDuration.HasValue ? (int?)(iteration + 1) : null);
}
}
catch (OperationCanceledException)
@@ -1,8 +1,8 @@
using System;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components.Deployment
@@ -13,17 +13,17 @@ namespace Tgstation.Server.Host.Components.Deployment
public interface IDreamMaker
{
/// <summary>
/// Create and a compile job and insert it into the database. Meant to be called by a <see cref="Jobs.IJobManager"/>.
/// Create and a compile job and insert it into the database. Meant to be called by a <see cref="IJobManager"/>.
/// </summary>
/// <param name="job">The running <see cref="Job"/>.</param>
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the operation.</param>
/// <param name="progressReporter">The <see cref="Action{T1}"/> to report compilation progress.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report compilation progress.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
Task DeploymentProcess(
Job job,
IDatabaseContextFactory databaseContextFactory,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
}
}
@@ -256,7 +256,7 @@ namespace Tgstation.Server.Host.Components
IInstanceCore core,
IDatabaseContextFactory databaseContextFactory,
Job job,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
=> databaseContextFactory.UseContext(
async databaseContext =>
@@ -277,11 +277,11 @@ namespace Tgstation.Server.Host.Components
const int NumSteps = 3;
var doneSteps = 0;
Action<int> NextProgressReporter()
JobProgressReporter NextProgressReporter()
{
var tmpDoneSteps = doneSteps;
++doneSteps;
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps);
return (status, progress) => progressReporter(status, (progress + (100 * tmpDoneSteps)) / NumSteps);
}
using var repo = await RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
@@ -466,7 +466,7 @@ namespace Tgstation.Server.Host.Components
throw;
}
progressReporter(5 * ProgressStep);
progressReporter(null, 5 * ProgressStep);
});
#pragma warning restore CA1502 // Cyclomatic complexity
@@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
{
@@ -46,7 +47,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="username">The username used for fetching from submodule repositories.</param>
/// <param name="password">The password used for fetching from submodule repositories.</param>
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report 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(
@@ -54,7 +55,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
/// <summary>
@@ -66,7 +67,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="username">The username used to fetch from the origin and submodule repositories.</param>
/// <param name="password">The password used to fetch from the origin and submodule repositories.</param>
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report 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 or up to date, <see langword="false"/> on a non-fast-forward, <see langword="null"/> on a conflict.</returns>
Task<bool?> AddTestMerge(
@@ -76,7 +77,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
/// <summary>
@@ -84,10 +85,14 @@ 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"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report 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);
Task FetchOrigin(
string username,
string password,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
/// <summary>
/// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository.
@@ -95,34 +100,34 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="username">The username used for fetching from submodule repositories.</param>
/// <param name="password">The password used for fetching from submodule repositories.</param>
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report 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(
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter 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="progressReporter">The <see cref="JobProgressReporter"/> to report 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, Action<int> progressReporter, CancellationToken cancellationToken);
Task ResetToSha(string sha, JobProgressReporter 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="progressReporter">The <see cref="JobProgressReporter"/> to report 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, Action<int> progressReporter, CancellationToken cancellationToken);
Task<bool?> MergeOrigin(string committerName, string committerEmail, JobProgressReporter 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.
@@ -131,11 +136,18 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="password">The password to fetch from the origin repository.</param>
/// <param name="committerName">The name of the potential committer.</param>
/// <param name="committerEmail">The e-mail of the potential committer.</param>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if commits were pushed to the tracked origin reference, <see langword="false"/> otherwise.</returns>
Task<bool> Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken);
Task<bool> Sychronize(
string username,
string password,
string committerName,
string committerEmail,
JobProgressReporter progressReporter,
bool synchronizeTrackedBranch,
CancellationToken cancellationToken);
/// <summary>
/// Copies the current working directory to a given <paramref name="path"/>.
@@ -2,6 +2,8 @@
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
{
/// <summary>
@@ -15,7 +17,7 @@ namespace Tgstation.Server.Host.Components.Repository
bool InUse { get; }
/// <summary>
/// If a <see cref="CloneRepository(Uri, string, string, string, Action{int}, bool, CancellationToken)"/> operation is in progress.
/// If a <see cref="CloneRepository(Uri, string, string, string, JobProgressReporter, bool, CancellationToken)"/> operation is in progress.
/// </summary>
bool CloneInProgress { get; }
@@ -33,7 +35,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="initialBranch">The branch to clone.</param>
/// <param name="username">The username to clone from <paramref name="url"/>.</param>
/// <param name="password">The password to clone from <paramref name="url"/>.</param>
/// <param name="progressReporter">A function to report 0-100 progress of the clone.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> for progress of the clone.</param>
/// <param name="recurseSubmodules">If submodules should be recusively cloned and initialized.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>The newly cloned <see cref="IRepository"/>, <see langword="null"/> if one already exists.</returns>
@@ -42,7 +44,7 @@ namespace Tgstation.Server.Host.Components.Repository
string initialBranch,
string username,
string password,
Action<int> progressReporter,
JobProgressReporter progressReporter,
bool recurseSubmodules,
CancellationToken cancellationToken);
@@ -114,20 +114,22 @@ namespace Tgstation.Server.Host.Components.Repository
/// <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>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> of the operation.</param>
/// <param name="stage">The stage argument for <paramref name="progressReporter"/>.</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));
static CheckoutProgressHandler CheckoutProgressHandler(JobProgressReporter progressReporter, string stage) => (a, completedSteps, totalSteps) => progressReporter(stage, (int)(((float)completedSteps) / totalSteps * 100));
/// <summary>
/// Generate a <see cref="LibGit2Sharp.Handlers.TransferProgressHandler"/> from a given <paramref name="progressReporter"/> and <paramref name="cancellationToken"/>.
/// </summary>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> of the operation.</param>
/// <param name="stage">The stage argument for <paramref name="progressReporter"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A new <see cref="LibGit2Sharp.Handlers.TransferProgressHandler"/> based on <paramref name="progressReporter"/>.</returns>
static TransferProgressHandler TransferProgressHandler(Action<int> progressReporter, CancellationToken cancellationToken) => (transferProgress) =>
static TransferProgressHandler TransferProgressHandler(JobProgressReporter progressReporter, string stage, CancellationToken cancellationToken) => (transferProgress) =>
{
var percentage = 100 * (((float)transferProgress.IndexedObjects + transferProgress.ReceivedObjects) / (transferProgress.TotalObjects * 2));
progressReporter((int)percentage);
progressReporter(stage, (int)percentage);
return !cancellationToken.IsCancellationRequested;
};
@@ -204,7 +206,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (testMergeParameters == null)
@@ -258,7 +260,8 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogTrace("Fetching refspec {0}...", refSpec);
var remote = libGitRepo.Network.Remotes.First();
progressReporter(0);
var stage = $"Fetch {refSpec}";
progressReporter(stage, 0);
commands.Fetch(
libGitRepo,
refSpecList,
@@ -267,7 +270,10 @@ namespace Tgstation.Server.Host.Components.Repository
{
Prune = true,
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = TransferProgressHandler(percentage => progressReporter(percentage / 2), cancellationToken),
OnTransferProgress = TransferProgressHandler(
(lambdaStage, progress) => progressReporter(lambdaStage, progress / 2),
stage,
cancellationToken),
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
},
@@ -299,7 +305,9 @@ namespace Tgstation.Server.Host.Components.Repository
FailOnConflict = true,
FastForwardStrategy = FastForwardStrategy.NoFastForward,
SkipReuc = true,
OnCheckoutProgress = CheckoutProgressHandler(percentage => progressReporter(50 + (percentage / 2))),
OnCheckoutProgress = CheckoutProgressHandler(
(lambdaStage, progress) => progressReporter(lambdaStage, 50 + (progress / 2)),
$"Merge {testMergeParameters.TargetCommitSha}"),
});
}
finally
@@ -354,7 +362,11 @@ namespace Tgstation.Server.Host.Components.Repository
.ConfigureAwait(false);
if (updateSubmodules)
await UpdateSubmodules(percentage => progressReporter(66 + (percentage / 3)), username, password, cancellationToken).ConfigureAwait(false);
await UpdateSubmodules(
(stage, progress) => progressReporter(stage, 66 + (progress.Value / 3)),
username,
password,
cancellationToken).ConfigureAwait(false);
}
await eventConsumer.HandleEvent(
@@ -378,7 +390,7 @@ namespace Tgstation.Server.Host.Components.Repository
string username,
string password,
bool updateSubmodules,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (committish == null)
@@ -391,7 +403,10 @@ namespace Tgstation.Server.Host.Components.Repository
() =>
{
libGitRepo.RemoveUntrackedFiles();
RawCheckout(committish, percentage => progressReporter(percentage * (updateSubmodules ? 2 : 3) / 3), cancellationToken);
RawCheckout(
committish,
(stage, progress) => progressReporter(stage, progress * (updateSubmodules ? 2 : 3) / 3),
cancellationToken);
},
cancellationToken,
DefaultIOManager.BlockingTaskCreationOptions,
@@ -399,11 +414,15 @@ namespace Tgstation.Server.Host.Components.Repository
.ConfigureAwait(false);
if (updateSubmodules)
await UpdateSubmodules(percentage => progressReporter(66 + (percentage / 3)), username, password, cancellationToken).ConfigureAwait(false);
await UpdateSubmodules(
(stage, progress) => progressReporter(stage, 66 + (progress / 3)),
username,
password,
cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task FetchOrigin(string username, string password, JobProgressReporter progressReporter, CancellationToken cancellationToken)
{
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
@@ -425,7 +444,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
Prune = true,
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = TransferProgressHandler(progressReporter, cancellationToken),
OnTransferProgress = TransferProgressHandler(progressReporter, "Fetch Origin", cancellationToken),
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
},
@@ -447,7 +466,12 @@ namespace Tgstation.Server.Host.Components.Repository
}
/// <inheritdoc />
public async Task ResetToOrigin(string username, string password, bool updateSubmodules, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task ResetToOrigin(
string username,
string password,
bool updateSubmodules,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
@@ -458,16 +482,16 @@ namespace Tgstation.Server.Host.Components.Repository
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
await ResetToSha(
trackedBranch.Tip.Sha,
percentage => progressReporter(percentage / (updateSubmodules ? 2 : 1)),
(stage, progress) => progressReporter(stage, progress / (updateSubmodules ? 2 : 1)),
cancellationToken)
.ConfigureAwait(false);
if (updateSubmodules)
await UpdateSubmodules(percentage => progressReporter(50 + (percentage / 2)), username, password, cancellationToken).ConfigureAwait(false);
await UpdateSubmodules((stage, progress) => progressReporter(stage, 50 + (progress / 2)), username, password, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public Task ResetToSha(string sha, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
public Task ResetToSha(string sha, JobProgressReporter progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
() =>
{
if (sha == null)
@@ -488,7 +512,7 @@ namespace Tgstation.Server.Host.Components.Repository
libGitRepo.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, $"Reset to {gitObject.Sha}"),
});
},
cancellationToken,
@@ -520,7 +544,11 @@ namespace Tgstation.Server.Host.Components.Repository
TaskScheduler.Current);
/// <inheritdoc />
public async Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task<bool?> MergeOrigin(
string committerName,
string committerEmail,
JobProgressReporter progressReporter,
CancellationToken cancellationToken)
{
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
@@ -553,7 +581,7 @@ namespace Tgstation.Server.Host.Components.Repository
FailOnConflict = true,
FastForwardStrategy = FastForwardStrategy.Default,
SkipReuc = true,
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, "Merge Origin"),
});
cancellationToken.ThrowIfCancellationRequested();
@@ -563,7 +591,7 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, $"Hard Reset to {oldHead.FriendlyName}"),
});
cancellationToken.ThrowIfCancellationRequested();
}
@@ -590,7 +618,7 @@ namespace Tgstation.Server.Host.Components.Repository
string password,
string committerName,
string committerEmail,
Action<int> progressReporter,
JobProgressReporter progressReporter,
bool synchronizeTrackedBranch,
CancellationToken cancellationToken)
{
@@ -648,7 +676,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10)),
OnCheckoutProgress = CheckoutProgressHandler((stage, progress) => progressReporter(stage, progress / 10), "Hard reset and remove untracked files"),
});
cancellationToken.ThrowIfCancellationRequested();
libGitRepo.RemoveUntrackedFiles();
@@ -659,7 +687,7 @@ namespace Tgstation.Server.Host.Components.Repository
.ConfigureAwait(false);
}
void FinalReporter(int progress) => progressReporter((int)(((float)progress) / 100 * 90));
void FinalReporter(string stage, int? progress) => progressReporter(stage, (int)(((float)progress) / 100 * 90));
if (!synchronizeTrackedBranch)
{
@@ -803,19 +831,20 @@ namespace Tgstation.Server.Host.Components.Repository
/// Runs a blocking force checkout to <paramref name="committish"/>.
/// </summary>
/// <param name="committish">The committish to checkout.</param>
/// <param name="progressReporter">Progress reporter <see cref="Action{T}"/>.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> for the operation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
void RawCheckout(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
void RawCheckout(string committish, JobProgressReporter progressReporter, CancellationToken cancellationToken)
{
logger.LogTrace("Checkout: {0}", committish);
progressReporter(0);
var stage = $"Checkout {committish}";
progressReporter(stage, 0);
cancellationToken.ThrowIfCancellationRequested();
var checkoutOptions = new CheckoutOptions
{
CheckoutModifiers = CheckoutModifiers.Force,
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, stage),
};
void RunCheckout() => commands.Checkout(
@@ -855,10 +884,10 @@ 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"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter"><see cref="JobProgressReporter"/> 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, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
Task PushHeadToTemporaryBranch(string username, string password, JobProgressReporter progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(
() =>
{
logger.LogInformation("Pushing changes to temporary remote branch...");
@@ -870,9 +899,9 @@ namespace Tgstation.Server.Host.Components.Repository
try
{
var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions((stage, progress) => progressReporter(stage, (int)(0.9f * progress)), username, password, cancellationToken));
var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
libGitRepo.Network.Push(remote, removalString, GeneratePushOptions((stage, progress) => progressReporter(stage, 90 + (int)(0.1f * progress)), username, password, cancellationToken));
}
catch (UserCancelledException)
{
@@ -895,23 +924,23 @@ namespace Tgstation.Server.Host.Components.Repository
/// <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="progressReporter"><see cref="JobProgressReporter"/> 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
PushOptions GeneratePushOptions(JobProgressReporter 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)));
progressReporter("Push", baseProgress + ((int)(25 * ((float)current) / total)));
return !cancellationToken.IsCancellationRequested;
},
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
OnPushTransferProgress = (a, sentBytes, totalBytes) =>
{
progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
progressReporter("Push", 50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
return !cancellationToken.IsCancellationRequested;
},
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
@@ -920,12 +949,12 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Recusively update all <see cref="Submodule"/>s in the <see cref="libGitRepo"/>.
/// </summary>
/// <param name="progressReporter"><see cref="Action{T1}"/> to report 0-100 <see cref="int"/> progress of the operation.</param>
/// <param name="progressReporter"><see cref="JobProgressReporter"/> 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 <see cref="Task"/> representing the running operation.</returns>
async Task UpdateSubmodules(Action<int> progressReporter, string username, string password, CancellationToken cancellationToken)
async Task UpdateSubmodules(JobProgressReporter progressReporter, string username, string password, CancellationToken cancellationToken)
{
var submoduleCount = libGitRepo.Submodules.Count();
if (submoduleCount == 0)
@@ -940,15 +969,20 @@ namespace Tgstation.Server.Host.Components.Repository
var factor = 100 / submoduleCount;
foreach (var submodule in libGitRepo.Submodules)
{
void LocalProgressReporter(int percentage) => progressReporter((iteration * factor) + (percentage / submoduleCount));
void LocalProgressReporter(string stage, int percentage) => progressReporter(stage, (iteration * factor) + (percentage / submoduleCount));
var submoduleUpdateOptions = new SubmoduleUpdateOptions
{
Init = true,
OnTransferProgress = TransferProgressHandler(percentage => LocalProgressReporter(percentage / 2), cancellationToken),
OnTransferProgress = TransferProgressHandler(
(stage, progress) => LocalProgressReporter(stage, progress.Value / 2),
$"Fetch submodule {submodule.Name}",
cancellationToken),
OnProgress = output => !cancellationToken.IsCancellationRequested,
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
OnCheckoutProgress = CheckoutProgressHandler(percentage => LocalProgressReporter(50 + (percentage / 2))),
OnCheckoutProgress = CheckoutProgressHandler(
(stage, progress) => LocalProgressReporter(stage, 50 + (progress.Value / 2)),
$"Checkout submodule {submodule.Name}"),
};
logger.LogDebug("Updating submodule {0}...", submodule.Name);
@@ -104,7 +104,7 @@ namespace Tgstation.Server.Host.Components.Repository
string initialBranch,
string username,
string password,
Action<int> progressReporter,
JobProgressReporter progressReporter,
bool recurseSubmodules,
CancellationToken cancellationToken)
{
@@ -136,7 +136,7 @@ namespace Tgstation.Server.Host.Components.Repository
OnTransferProgress = (a) =>
{
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
progressReporter((int)percentage);
progressReporter("Cloning", (int)percentage);
return !cancellationToken.IsCancellationRequested;
},
RecurseSubmodules = recurseSubmodules,
@@ -167,7 +167,7 @@ namespace Tgstation.Server.Host.Controllers
if (job == default)
return NotFound();
var api = job.ToApi();
api.Progress = jobManager.JobProgress(job);
jobManager.SetJobProgress(api);
return Json(api);
}
@@ -178,7 +178,7 @@ namespace Tgstation.Server.Host.Controllers
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
private Task AddJobProgressResponseTransformer(JobResponse jobResponse)
{
jobResponse.Progress = jobManager.JobProgress(jobResponse);
jobManager.SetJobProgress(jobResponse);
return Task.CompletedTask;
}
}
@@ -452,7 +452,7 @@ namespace Tgstation.Server.Host.Controllers
async Task<IActionResult> RepositoryUpdateJobOhGodPleaseSomeoneRefactorThisItsTooFuckingBig(
IInstanceCore instance,
IDatabaseContextFactory databaseContextFactory,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken ct)
{
var repoManager = instance.RepositoryManager;
@@ -478,14 +478,14 @@ namespace Tgstation.Server.Host.Controllers
var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
var doneSteps = 0;
Action<int> NextProgressReporter()
JobProgressReporter NextProgressReporter()
{
var tmpDoneSteps = doneSteps;
++doneSteps;
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
return (status, progress) => progressReporter(status, (progress + (100 * tmpDoneSteps)) / numSteps);
}
progressReporter(0);
progressReporter(null, 0);
// get a base line for where we are
Models.RevisionInformation lastRevisionInfo = null;
@@ -577,7 +577,7 @@ namespace Tgstation.Server.Host.Controllers
postUpdateSha = repo.Head;
}
else
NextProgressReporter()(100);
NextProgressReporter()(null, 100);
}
}
@@ -612,7 +612,7 @@ namespace Tgstation.Server.Host.Controllers
await CallLoadRevInfo().ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
}
else
NextProgressReporter()(100);
NextProgressReporter()(null, 100);
if (hardResettingToOriginReference)
{
@@ -882,7 +882,7 @@ namespace Tgstation.Server.Host.Controllers
if (startReference != null && repo.Head != startSha)
await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
else
progressReporter(100);
progressReporter(null, 100);
throw;
}
}
@@ -2,7 +2,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Jobs
@@ -13,11 +13,10 @@ namespace Tgstation.Server.Host.Jobs
public interface IJobManager : IHostedService
{
/// <summary>
/// Get the <see cref="Api.Models.Response.JobResponse.Progress"/> for a <paramref name="job"/>.
/// Set the <see cref="JobResponse.Progress"/> and <see cref="JobResponse.Stage"/> for a given <paramref name="apiResponse"/>.
/// </summary>
/// <param name="job">The <see cref="Api.Models.Internal.Job"/> to get <see cref="Api.Models.Response.JobResponse.Progress"/> for.</param>
/// <returns>The <see cref="Api.Models.Response.JobResponse.Progress"/> of <paramref name="job"/>.</returns>
int? JobProgress(Api.Models.Internal.Job job);
/// <param name="apiResponse">The <see cref="JobResponse"/> to update.</param>
void SetJobProgress(JobResponse apiResponse);
/// <summary>
/// Registers a given <see cref="Job"/> and begins running it.
@@ -1,5 +1,4 @@
using System;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Components;
@@ -14,13 +13,13 @@ namespace Tgstation.Server.Host.Jobs
/// <param name="instance">The <see cref="IInstanceCore"/> the job is running on. <see langword="null"/> only when performing an instance move operation.</param>
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the operation.</param>
/// <param name="job">The running <see cref="Job"/>.</param>
/// <param name="progressReporter">A <see cref="Action{T1}"/> that will update the progress of the job.</param>
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> for the job.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
public delegate Task JobEntrypoint(
IInstanceCore instance,
IDatabaseContextFactory databaseContextFactory,
Job job,
Action<int> progressReporter,
JobProgressReporter progressReporter,
CancellationToken cancellationToken);
}
@@ -44,6 +44,11 @@ namespace Tgstation.Server.Host.Jobs
/// </summary>
public int? Progress { get; set; }
/// <summary>
/// The stage of the job.
/// </summary>
public string Stage { get; set; }
/// <summary>
/// Wait for <see cref="task"/> to complete.
/// </summary>
+16 -8
View File
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
@@ -221,15 +221,16 @@ namespace Tgstation.Server.Host.Jobs
}
/// <inheritdoc />
public int? JobProgress(Api.Models.Internal.Job job)
public void SetJobProgress(JobResponse apiResponse)
{
if (job == null)
throw new ArgumentNullException(nameof(job));
if (apiResponse == null)
throw new ArgumentNullException(nameof(apiResponse));
lock (synchronizationLock)
{
if (!jobs.TryGetValue(job.Id.Value, out var handler))
return null;
return handler.Progress;
if (!jobs.TryGetValue(apiResponse.Id.Value, out var handler))
return;
apiResponse.Progress = handler.Progress;
apiResponse.Stage = handler.Stage;
}
}
@@ -293,11 +294,18 @@ namespace Tgstation.Server.Host.Jobs
var oldJob = job;
job = new Job { Id = oldJob.Id };
void UpdateProgress(int progress)
void UpdateProgress(string stage, int? progress)
{
if (progress.HasValue
&& (progress.Value < 0 || progress.Value > 100))
throw new ArgumentOutOfRangeException(nameof(progress), "Progress must be a value from 0-100!");
lock (synchronizationLock)
if (jobs.TryGetValue(oldJob.Id.Value, out var handler))
{
handler.Stage = stage;
handler.Progress = progress;
}
}
await activationTcs.Task.WithToken(cancellationToken).ConfigureAwait(false);
@@ -0,0 +1,13 @@
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Jobs
{
/// <summary>
/// Progress reporter for a <see cref="Job"/>.
/// </summary>
/// <param name="stage">A description of what the job is currently doing.</param>
/// <param name="progress">The progress of the job on a scale from 0-100.</param>
public delegate void JobProgressReporter(
string stage,
int? progress);
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../../build/Version.props" />
<PropertyGroup>
@@ -26,7 +26,7 @@
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<Target Name="ClientInstall" Inputs="../../build/ControlPanelVersion.props" Outputs="$(NpmInstallStampFile)">
<Target Name="ClientInstall" BeforeTargets="ResolveAssemblyReferences" Inputs="../../build/ControlPanelVersion.props" Outputs="$(NpmInstallStampFile)">
<Message Text="Pulling web control panel..." Importance="high" />
<RemoveDir Directories="ClientApp" />
<Exec Command="git clone https://github.com/tgstation/tgstation-server-webpanel --branch v$(TgsControlPanelVersion) --depth 1 ClientApp" />
@@ -1061,7 +1061,7 @@ namespace Tgstation.Server.Tests
() => { });
const string StartSha = "af4da8beb9f9b374b04a3cc4d65acca662e8cc1a";
await repo.CheckoutObject(StartSha, null, null, true, progress => { }, default);
await repo.CheckoutObject(StartSha, null, null, true, (stage, progress) => { }, default);
var result = await repo.ShaIsParent("2f8588a3ca0f6b027704a2a04381215619de3412", default);
Assert.IsTrue(result);
Assert.AreEqual(StartSha, repo.Head);