mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 14:37:44 +01:00
@@ -717,7 +717,14 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task ProgressTask(JobProgressReporter progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
|
||||
{
|
||||
progressReporter(currentStage, estimatedDuration.HasValue ? 0 : null);
|
||||
if (!estimatedDuration.HasValue)
|
||||
{
|
||||
progressReporter.ReportProgress(null);
|
||||
return;
|
||||
}
|
||||
|
||||
progressReporter.ReportProgress(0);
|
||||
|
||||
var sleepInterval = estimatedDuration.HasValue ? estimatedDuration.Value / 100 : TimeSpan.FromMilliseconds(250);
|
||||
|
||||
logger.LogDebug("Compile is expected to take: {0}", estimatedDuration);
|
||||
@@ -726,7 +733,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
for (var iteration = 0; iteration < (estimatedDuration.HasValue ? 99 : Int32.MaxValue); ++iteration)
|
||||
{
|
||||
await Task.Delay(sleepInterval, cancellationToken);
|
||||
progressReporter(currentStage, estimatedDuration.HasValue ? iteration + 1 : null);
|
||||
progressReporter.ReportProgress(sleepInterval * (iteration + 1) / estimatedDuration.Value);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -266,23 +266,16 @@ namespace Tgstation.Server.Host.Components
|
||||
throw new InvalidOperationException(DifferentCoreExceptionMessage);
|
||||
|
||||
// assume 5 steps with synchronize
|
||||
const int ProgressSections = 7;
|
||||
const int ProgressStep = 100 / ProgressSections;
|
||||
|
||||
var repositorySettingsTask = databaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.FirstAsync(cancellationToken);
|
||||
|
||||
const int NumSteps = 3;
|
||||
var doneSteps = 0;
|
||||
|
||||
JobProgressReporter NextProgressReporter()
|
||||
const int ProgressSections = 7;
|
||||
JobProgressReporter NextProgressReporter(string stage)
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return (status, progress) => progressReporter(status, (progress + (100 * tmpDoneSteps)) / NumSteps);
|
||||
return progressReporter.CreateSection(stage, 1.0 / ProgressSections);
|
||||
}
|
||||
|
||||
using var repo = await RepositoryManager.LoadRepository(cancellationToken);
|
||||
@@ -305,7 +298,7 @@ namespace Tgstation.Server.Host.Components
|
||||
await repo.FetchOrigin(
|
||||
repositorySettings.AccessUser,
|
||||
repositorySettings.AccessToken,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Fetch Origin"),
|
||||
cancellationToken)
|
||||
;
|
||||
|
||||
@@ -371,7 +364,7 @@ namespace Tgstation.Server.Host.Components
|
||||
var result = await repo.MergeOrigin(
|
||||
repositorySettings.CommitterName,
|
||||
repositorySettings.CommitterEmail,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Merge Origin"),
|
||||
cancellationToken)
|
||||
;
|
||||
|
||||
@@ -416,12 +409,13 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
if (!preserveTestMerges)
|
||||
{
|
||||
logger.LogTrace("Resetting to origin...");
|
||||
const string StageName = "Resetting to origin...";
|
||||
logger.LogTrace(StageName);
|
||||
await repo.ResetToOrigin(
|
||||
repositorySettings.AccessUser,
|
||||
repositorySettings.AccessToken,
|
||||
repositorySettings.UpdateSubmodules.Value,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter(StageName),
|
||||
cancellationToken)
|
||||
;
|
||||
|
||||
@@ -447,7 +441,7 @@ namespace Tgstation.Server.Host.Components
|
||||
repositorySettings.AccessToken,
|
||||
repositorySettings.CommitterName,
|
||||
repositorySettings.CommitterEmail,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Synchronize"),
|
||||
shouldSyncTracked,
|
||||
cancellationToken);
|
||||
var currentHead = repo.Head;
|
||||
@@ -466,8 +460,6 @@ namespace Tgstation.Server.Host.Components
|
||||
await repo.ResetToSha(startSha, progressReporter, default);
|
||||
throw;
|
||||
}
|
||||
|
||||
progressReporter(null, 5 * ProgressStep);
|
||||
});
|
||||
#pragma warning restore CA1502 // Cyclomatic complexity
|
||||
|
||||
|
||||
@@ -214,6 +214,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
MergeResult result = null;
|
||||
|
||||
var progressFactor = 1.0 / (updateSubmodules ? 3 : 2);
|
||||
|
||||
var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.UtcNow);
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -225,8 +227,6 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
logger.LogTrace("Fetching refspec {0}...", refSpec);
|
||||
|
||||
var remote = libGitRepo.Network.Remotes.First();
|
||||
var stage = $"Fetch {refSpec}";
|
||||
progressReporter(stage, 0);
|
||||
commands.Fetch(
|
||||
libGitRepo,
|
||||
refSpecList,
|
||||
@@ -236,8 +236,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
Prune = true,
|
||||
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnTransferProgress = TransferProgressHandler(
|
||||
(lambdaStage, progress) => progressReporter(lambdaStage, progress / 2),
|
||||
stage,
|
||||
progressReporter.CreateSection($"Fetch {refSpec}", progressFactor),
|
||||
cancellationToken),
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
|
||||
@@ -267,7 +266,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogTrace("Merging {0} into {1}...", testMergeParameters.TargetCommitSha.Substring(0, 7), Reference);
|
||||
logger.LogTrace("Merging {0} into {1}...", testMergeParameters.TargetCommitSha[..7], Reference);
|
||||
|
||||
result = libGitRepo.Merge(testMergeParameters.TargetCommitSha, sig, new MergeOptions
|
||||
{
|
||||
@@ -276,8 +275,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
FastForwardStrategy = FastForwardStrategy.NoFastForward,
|
||||
SkipReuc = true,
|
||||
OnCheckoutProgress = CheckoutProgressHandler(
|
||||
(lambdaStage, progress) => progressReporter(lambdaStage, progress.HasValue ? (int?)(50 + (progress.Value / 2)) : null),
|
||||
$"Merge {testMergeParameters.TargetCommitSha}"),
|
||||
progressReporter.CreateSection($"Merge {testMergeParameters.TargetCommitSha[..7]}", progressFactor)),
|
||||
});
|
||||
}
|
||||
finally
|
||||
@@ -291,7 +289,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
|
||||
RawCheckout(revertTo, progressReporter, cancellationToken);
|
||||
progressReporter.ReportProgress(0);
|
||||
RawCheckout(revertTo, progressReporter.CreateSection("Hard Reset to {revertTo}", 1.0), cancellationToken);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
@@ -332,11 +331,13 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
;
|
||||
|
||||
if (updateSubmodules)
|
||||
{
|
||||
await UpdateSubmodules(
|
||||
(stage, progress) => progressReporter(stage, 66 + (progress.Value / 3)),
|
||||
progressReporter.CreateSection("Update Submodules", progressFactor),
|
||||
username,
|
||||
password,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
await eventConsumer.HandleEvent(
|
||||
@@ -375,7 +376,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
libGitRepo.RemoveUntrackedFiles();
|
||||
RawCheckout(
|
||||
committish,
|
||||
(stage, progress) => progressReporter(stage, progress * (updateSubmodules ? 2 : 3) / 3),
|
||||
progressReporter.CreateSection(null, updateSubmodules ? 2.0 / 3 : 1.0),
|
||||
cancellationToken);
|
||||
},
|
||||
cancellationToken,
|
||||
@@ -385,7 +386,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
if (updateSubmodules)
|
||||
await UpdateSubmodules(
|
||||
(stage, progress) => progressReporter(stage, 66 + (progress / 3)),
|
||||
progressReporter.CreateSection(null, 1.0 / 3),
|
||||
username,
|
||||
password,
|
||||
cancellationToken);
|
||||
@@ -414,7 +415,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
Prune = true,
|
||||
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnTransferProgress = TransferProgressHandler(progressReporter, "Fetch Origin", cancellationToken),
|
||||
OnTransferProgress = TransferProgressHandler(progressReporter.CreateSection("Fetch Origin", 1.0), cancellationToken),
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
|
||||
},
|
||||
@@ -452,12 +453,16 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken);
|
||||
await ResetToSha(
|
||||
trackedBranch.Tip.Sha,
|
||||
(stage, progress) => progressReporter(stage, progress / (updateSubmodules ? 2 : 1)),
|
||||
progressReporter.CreateSection(null, updateSubmodules ? 2.0 / 3 : 1.0),
|
||||
cancellationToken)
|
||||
;
|
||||
|
||||
if (updateSubmodules)
|
||||
await UpdateSubmodules((stage, progress) => progressReporter(stage, 50 + (progress / 2)), username, password, cancellationToken);
|
||||
await UpdateSubmodules(
|
||||
progressReporter.CreateSection(null, 1.0 / 3),
|
||||
username,
|
||||
password,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -482,7 +487,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
libGitRepo.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
|
||||
{
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, $"Reset to {gitObject.Sha}"),
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter.CreateSection($"Reset to {gitObject.Sha}", 1.0)),
|
||||
});
|
||||
},
|
||||
cancellationToken,
|
||||
@@ -551,7 +556,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
FailOnConflict = true,
|
||||
FastForwardStrategy = FastForwardStrategy.Default,
|
||||
SkipReuc = true,
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, "Merge Origin"),
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter.CreateSection("Merge Origin", 1.0)),
|
||||
});
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -559,9 +564,10 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (result.Status == MergeStatus.Conflicts)
|
||||
{
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
|
||||
progressReporter.ReportProgress(0);
|
||||
libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
|
||||
{
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, $"Hard Reset to {oldHead.FriendlyName}"),
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter.CreateSection($"Hard Reset to {oldHead.FriendlyName}", 1.0)),
|
||||
});
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
@@ -646,7 +652,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions
|
||||
{
|
||||
OnCheckoutProgress = CheckoutProgressHandler((stage, progress) => progressReporter(stage, progress.HasValue ? (int?)(progress.Value / 10) : null), "Hard reset and remove untracked files"),
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter.CreateSection("Hard reset and remove untracked files", 0.1)),
|
||||
});
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
libGitRepo.RemoveUntrackedFiles();
|
||||
@@ -657,11 +663,14 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
;
|
||||
}
|
||||
|
||||
void FinalReporter(string stage, int? progress) => progressReporter(stage, (int)(((float)progress) / 100 * 90));
|
||||
|
||||
var remainingProgressFactor = 0.9;
|
||||
if (!synchronizeTrackedBranch)
|
||||
{
|
||||
await PushHeadToTemporaryBranch(username, password, FinalReporter, cancellationToken);
|
||||
await PushHeadToTemporaryBranch(
|
||||
username,
|
||||
password,
|
||||
progressReporter.CreateSection("Push to temporary branch", remainingProgressFactor),
|
||||
cancellationToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -680,7 +689,13 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
var remote = libGitRepo.Network.Remotes.First();
|
||||
try
|
||||
{
|
||||
libGitRepo.Network.Push(libGitRepo.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken));
|
||||
libGitRepo.Network.Push(
|
||||
libGitRepo.Head,
|
||||
GeneratePushOptions(
|
||||
progressReporter.CreateSection("Push to origin", remainingProgressFactor),
|
||||
username,
|
||||
password,
|
||||
cancellationToken));
|
||||
return true;
|
||||
}
|
||||
catch (NonFastForwardException)
|
||||
@@ -808,13 +823,14 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
logger.LogTrace("Checkout: {0}", committish);
|
||||
|
||||
var stage = $"Checkout {committish}";
|
||||
progressReporter(stage, 0);
|
||||
progressReporter = progressReporter.CreateSection(stage, 1.0);
|
||||
progressReporter.ReportProgress(0);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var checkoutOptions = new CheckoutOptions
|
||||
{
|
||||
CheckoutModifiers = CheckoutModifiers.Force,
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter, stage),
|
||||
OnCheckoutProgress = CheckoutProgressHandler(progressReporter),
|
||||
};
|
||||
|
||||
void RunCheckout() => commands.Checkout(
|
||||
@@ -872,9 +888,9 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
try
|
||||
{
|
||||
var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
|
||||
libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions((stage, progress) => progressReporter(stage, (int)(0.9f * progress)), username, password, cancellationToken));
|
||||
libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progressReporter.CreateSection(null, 0.9), username, password, cancellationToken));
|
||||
var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
|
||||
libGitRepo.Network.Push(remote, removalString, GeneratePushOptions((stage, progress) => progressReporter(stage, 90 + (int)(0.1f * progress)), username, password, cancellationToken));
|
||||
libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progressReporter.CreateSection(null, 0.1), username, password, cancellationToken));
|
||||
}
|
||||
catch (UserCancelledException)
|
||||
{
|
||||
@@ -902,22 +918,31 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <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(JobProgressReporter progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions
|
||||
PushOptions GeneratePushOptions(JobProgressReporter progressReporter, string username, string password, CancellationToken cancellationToken)
|
||||
{
|
||||
OnPackBuilderProgress = (stage, current, total) =>
|
||||
var subProgressReporter = progressReporter.CreateSection(null, 0.5);
|
||||
|
||||
return new PushOptions
|
||||
{
|
||||
var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25;
|
||||
progressReporter("Push", baseProgress + ((int)(25 * ((float)current) / total)));
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnPushTransferProgress = (a, sentBytes, totalBytes) =>
|
||||
{
|
||||
progressReporter("Push", 50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
|
||||
};
|
||||
OnPackBuilderProgress = (stage, current, total) =>
|
||||
{
|
||||
var baseProgress = stage == PackBuilderStage.Counting ? 0 : 0.5;
|
||||
progressReporter.ReportProgress(baseProgress + (0.5 * ((double)current / total)));
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnNegotiationCompletedBeforePush = (a) =>
|
||||
{
|
||||
subProgressReporter = progressReporter.CreateSection(null, 0.5);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
OnPushTransferProgress = (a, sentBytes, totalBytes) =>
|
||||
{
|
||||
progressReporter.ReportProgress((double)sentBytes / totalBytes);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recusively update all <see cref="Submodule"/>s in the <see cref="libGitRepo"/>.
|
||||
@@ -938,24 +963,20 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
logger.LogTrace("Updating submodules with{0} credentials...", username == null ? "out" : String.Empty);
|
||||
|
||||
var iteration = 0;
|
||||
var factor = 100 / submoduleCount;
|
||||
var factor = 1.0 / submoduleCount / 2;
|
||||
foreach (var submodule in libGitRepo.Submodules)
|
||||
{
|
||||
void LocalProgressReporter(string stage, int? percentage) => progressReporter(stage, percentage.HasValue ? (int?)((iteration * factor) + (percentage.Value / submoduleCount)) : null);
|
||||
var submoduleUpdateOptions = new SubmoduleUpdateOptions
|
||||
{
|
||||
Init = true,
|
||||
OnTransferProgress = TransferProgressHandler(
|
||||
(stage, progress) => LocalProgressReporter(stage, progress.Value / 2),
|
||||
$"Fetch submodule {submodule.Name}",
|
||||
progressReporter.CreateSection($"Fetch submodule {submodule.Name}", factor),
|
||||
cancellationToken),
|
||||
OnProgress = output => !cancellationToken.IsCancellationRequested,
|
||||
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
|
||||
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password),
|
||||
OnCheckoutProgress = CheckoutProgressHandler(
|
||||
(stage, progress) => LocalProgressReporter(stage, progress.HasValue ? (int?)(50 + (progress.Value / 2)) : null),
|
||||
$"Checkout submodule {submodule.Name}"),
|
||||
progressReporter.CreateSection($"Checkout submodule {submodule.Name}", factor)),
|
||||
};
|
||||
|
||||
logger.LogDebug("Updating submodule {0}...", submodule.Name);
|
||||
@@ -972,6 +993,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
// workaround for https://github.com/libgit2/libgit2/issues/3820
|
||||
// kill off the modules/ folder in .git and try again
|
||||
progressReporter.ReportProgress(null);
|
||||
credentialsProvider.CheckBadCredentialsException(ex);
|
||||
logger.LogWarning(ex, "Initial update of submodule {0} failed. Deleting submodule directories and re-attempting...", submodule.Name);
|
||||
|
||||
@@ -1005,11 +1027,10 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// Converts a given <paramref name="progressReporter"/> to a <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/>.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
CheckoutProgressHandler CheckoutProgressHandler(JobProgressReporter progressReporter, string stage) => (a, completedSteps, totalSteps) =>
|
||||
CheckoutProgressHandler CheckoutProgressHandler(JobProgressReporter progressReporter) => (a, completedSteps, totalSteps) =>
|
||||
{
|
||||
int? percentage;
|
||||
double? percentage;
|
||||
|
||||
// short circuit initialization where totalSteps is 0
|
||||
if (completedSteps == 0)
|
||||
@@ -1018,40 +1039,36 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
percentage = null;
|
||||
else
|
||||
{
|
||||
var ratio = ((float)completedSteps) / totalSteps;
|
||||
percentage = (int)(ratio * 100);
|
||||
percentage = ((double)completedSteps) / totalSteps;
|
||||
if (percentage < 0)
|
||||
percentage = null;
|
||||
}
|
||||
|
||||
if (percentage == null)
|
||||
logger.LogDebug(
|
||||
"Bad checkout progress values (Please tell Cyberboss)! Completeds: {completed}, Total: {total}",
|
||||
"Bad checkout progress values (Please tell Dominion)! Completeds: {completed}, Total: {total}",
|
||||
completedSteps,
|
||||
totalSteps);
|
||||
|
||||
progressReporter(
|
||||
stage,
|
||||
percentage);
|
||||
progressReporter.ReportProgress(percentage);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Generate a <see cref="LibGit2Sharp.Handlers.TransferProgressHandler"/> from a given <paramref name="progressReporter"/> and <paramref name="cancellationToken"/>.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
TransferProgressHandler TransferProgressHandler(JobProgressReporter progressReporter, string stage, CancellationToken cancellationToken) => (transferProgress) =>
|
||||
TransferProgressHandler TransferProgressHandler(JobProgressReporter progressReporter, CancellationToken cancellationToken) => (transferProgress) =>
|
||||
{
|
||||
float? percentage;
|
||||
double? percentage;
|
||||
var totalObjectsToProcess = transferProgress.TotalObjects * 2;
|
||||
var processedObjects = transferProgress.IndexedObjects + transferProgress.ReceivedObjects;
|
||||
if (totalObjectsToProcess < processedObjects || totalObjectsToProcess == 0)
|
||||
percentage = null;
|
||||
else
|
||||
{
|
||||
percentage = 100 * (((float)processedObjects) / totalObjectsToProcess);
|
||||
percentage = (double)processedObjects / totalObjectsToProcess;
|
||||
if (percentage < 0)
|
||||
percentage = null;
|
||||
}
|
||||
@@ -1063,7 +1080,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
transferProgress.ReceivedObjects,
|
||||
transferProgress.TotalObjects);
|
||||
|
||||
progressReporter(stage, (int?)percentage);
|
||||
progressReporter.ReportProgress(percentage);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -135,8 +135,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
|
||||
OnTransferProgress = (a) =>
|
||||
{
|
||||
var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
|
||||
progressReporter("Cloning", (int)percentage);
|
||||
var percentage = ((double)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2);
|
||||
progressReporter.ReportProgress(percentage);
|
||||
return !cancellationToken.IsCancellationRequested;
|
||||
},
|
||||
RecurseSubmodules = recurseSubmodules,
|
||||
|
||||
@@ -489,16 +489,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
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;
|
||||
var progressFactor = 1.0 / numSteps;
|
||||
|
||||
JobProgressReporter NextProgressReporter()
|
||||
JobProgressReporter NextProgressReporter(string stage)
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return (status, progress) => progressReporter(status, (progress + (100 * tmpDoneSteps)) / numSteps);
|
||||
return progressReporter.CreateSection(stage, progressFactor);
|
||||
}
|
||||
|
||||
progressReporter(null, 0);
|
||||
progressReporter.ReportProgress(0);
|
||||
|
||||
// get a base line for where we are
|
||||
Models.RevisionInformation lastRevisionInfo = null;
|
||||
@@ -568,11 +566,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
if (!repo.Tracking)
|
||||
throw new JobException(ErrorCode.RepoReferenceRequired);
|
||||
await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct);
|
||||
doneSteps = 1;
|
||||
await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter("Fetch Origin"), ct);
|
||||
|
||||
if (!modelHasShaOrReference)
|
||||
{
|
||||
var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct);
|
||||
var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter("Merge Origin"), ct);
|
||||
if (!fastForward.HasValue)
|
||||
throw new JobException(ErrorCode.RepoMergeConflict);
|
||||
lastRevisionInfo.OriginCommitSha = await repo.GetOriginSha(cancellationToken);
|
||||
@@ -584,14 +582,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
currentModel.AccessToken,
|
||||
currentModel.CommitterName,
|
||||
currentModel.CommitterEmail,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Sychronize"),
|
||||
true,
|
||||
ct)
|
||||
;
|
||||
postUpdateSha = repo.Head;
|
||||
}
|
||||
else
|
||||
NextProgressReporter()(null, 100);
|
||||
NextProgressReporter(null).ReportProgress(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,13 +618,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
updateSubmodules,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Checkout"),
|
||||
ct)
|
||||
;
|
||||
await CallLoadRevInfo(); // we've either seen origin before or what we're checking out is on origin
|
||||
}
|
||||
else
|
||||
NextProgressReporter()(null, 100);
|
||||
NextProgressReporter(null).ReportProgress(1.0);
|
||||
|
||||
if (hardResettingToOriginReference)
|
||||
{
|
||||
@@ -636,7 +634,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
updateSubmodules,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Reset to Origin"),
|
||||
ct)
|
||||
;
|
||||
await repo.Sychronize(
|
||||
@@ -644,7 +642,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
currentModel.AccessToken,
|
||||
currentModel.CommitterName,
|
||||
currentModel.CommitterEmail,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Synchronize"),
|
||||
true,
|
||||
ct)
|
||||
;
|
||||
@@ -798,7 +796,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
// goteem
|
||||
Logger.LogDebug("Reusing existing SHA {0}...", revInfoWereLookingFor.CommitSha);
|
||||
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken);
|
||||
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter($"Reset to {revInfoWereLookingFor.CommitSha[..7]}"), cancellationToken);
|
||||
lastRevisionInfo = revInfoWereLookingFor;
|
||||
}
|
||||
|
||||
@@ -818,14 +816,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
updateSubmodules,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter($"Test merge #{newTestMerge.Number}"),
|
||||
ct);
|
||||
|
||||
if (mergeResult == null)
|
||||
throw new JobException(
|
||||
ErrorCode.RepoTestMergeConflict,
|
||||
new JobException(
|
||||
$"Test Merge #{newTestMerge.Number} at {newTestMerge.TargetCommitSha.Substring(0, 7)} conflicted!"));
|
||||
$"Test Merge #{newTestMerge.Number} at {newTestMerge.TargetCommitSha[..7]} conflicted!"));
|
||||
|
||||
Models.TestMerge fullTestMerge;
|
||||
try
|
||||
@@ -834,7 +832,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning("Error retrieving metadata for test merge #{0}!", newTestMerge.Number);
|
||||
Logger.LogWarning("Error retrieving metadata for test merge #{testMergeNumber}!", newTestMerge.Number);
|
||||
|
||||
fullTestMerge = new Models.TestMerge
|
||||
{
|
||||
@@ -850,9 +848,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
// Ensure we're getting the full sha from git itself
|
||||
fullTestMerge.TargetCommitSha = newTestMerge.TargetCommitSha;
|
||||
|
||||
// MergedBy will be set later
|
||||
++doneSteps;
|
||||
|
||||
await UpdateRevInfo(fullTestMerge);
|
||||
}
|
||||
}
|
||||
@@ -866,7 +861,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
currentModel.AccessToken,
|
||||
currentModel.CommitterName,
|
||||
currentModel.CommitterEmail,
|
||||
NextProgressReporter(),
|
||||
NextProgressReporter("Synchronize"),
|
||||
false,
|
||||
ct)
|
||||
;
|
||||
@@ -877,23 +872,25 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
catch
|
||||
{
|
||||
doneSteps = 0;
|
||||
numSteps = 2;
|
||||
|
||||
// Forget what we've done and abort
|
||||
progressReporter.ReportProgress(0.0);
|
||||
|
||||
var secondStep = startReference != null && repo.Head != startSha;
|
||||
|
||||
// DCTx2: Cancellation token is for job, operations should always run
|
||||
await repo.CheckoutObject(
|
||||
startReference ?? startSha,
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
true,
|
||||
NextProgressReporter(),
|
||||
default)
|
||||
;
|
||||
if (startReference != null && repo.Head != startSha)
|
||||
await repo.ResetToSha(startSha, NextProgressReporter(), default);
|
||||
else
|
||||
progressReporter(null, 100);
|
||||
progressReporter.CreateSection($"Checkout {startReference ?? startSha[..7]}", secondStep ? 0.5 : 1.0),
|
||||
default);
|
||||
|
||||
if (secondStep)
|
||||
await repo.ResetToSha(startSha, progressReporter.CreateSection($"Hard reset to SHA {startSha[..7]}", 0.5), default);
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="JobManager"/>.
|
||||
/// </summary>
|
||||
readonly ILoggerFactory loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="JobManager"/>.
|
||||
/// </summary>
|
||||
@@ -58,11 +63,17 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// </summary>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/>.</param>
|
||||
/// <param name="instanceCoreProvider">The value of <see cref="instanceCoreProvider"/>.</param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public JobManager(IDatabaseContextFactory databaseContextFactory, Lazy<IInstanceCoreProvider> instanceCoreProvider, ILogger<JobManager> logger)
|
||||
public JobManager(
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
Lazy<IInstanceCoreProvider> instanceCoreProvider,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILogger<JobManager> logger)
|
||||
{
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.instanceCoreProvider = instanceCoreProvider ?? throw new ArgumentNullException(nameof(instanceCoreProvider));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
jobs = new Dictionary<long, JobHandler>();
|
||||
activationTcs = new TaskCompletionSource<object>();
|
||||
@@ -293,12 +304,12 @@ namespace Tgstation.Server.Host.Jobs
|
||||
var oldJob = job;
|
||||
job = new Job { Id = oldJob.Id };
|
||||
|
||||
void UpdateProgress(string stage, int? progress)
|
||||
void UpdateProgress(string stage, double? progress)
|
||||
{
|
||||
if (progress.HasValue
|
||||
&& (progress.Value < 0 || progress.Value > 100))
|
||||
&& (progress.Value < 0 || progress.Value > 1))
|
||||
{
|
||||
var exception = new ArgumentOutOfRangeException(nameof(progress), progress, "Progress must be a value from 0-100!");
|
||||
var exception = new ArgumentOutOfRangeException(nameof(progress), progress, "Progress must be a value from 0-1!");
|
||||
logger.LogError(exception, "Invalid progress value!");
|
||||
return;
|
||||
}
|
||||
@@ -307,7 +318,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
if (jobs.TryGetValue(oldJob.Id.Value, out var handler))
|
||||
{
|
||||
handler.Stage = stage;
|
||||
handler.Progress = progress;
|
||||
handler.Progress = progress.HasValue ? (int)Math.Floor(progress.Value * 100) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +329,10 @@ namespace Tgstation.Server.Host.Jobs
|
||||
instanceCoreProvider.Value.GetInstance(oldJob.Instance),
|
||||
databaseContextFactory,
|
||||
job,
|
||||
UpdateProgress,
|
||||
new JobProgressReporter(
|
||||
loggerFactory.CreateLogger<JobProgressReporter>(),
|
||||
null,
|
||||
UpdateProgress),
|
||||
cancellationToken)
|
||||
;
|
||||
|
||||
|
||||
@@ -1,13 +1,127 @@
|
||||
using Tgstation.Server.Host.Models;
|
||||
using System;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
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);
|
||||
public sealed class JobProgressReporter
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the current stage.
|
||||
/// </summary>
|
||||
public string StageName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger{TCategoryName}"/> for the <see cref="JobProgressReporter"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<JobProgressReporter> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Progress reporter callback taking a description of what the job is currently doing and the (optional) progress of the job on a scale from 0.0-1.0.
|
||||
/// </summary>
|
||||
readonly Action<string, double?> callback;
|
||||
|
||||
/// <summary>
|
||||
/// The total progress reported so far in this section.
|
||||
/// </summary>
|
||||
double sectionProgression;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JobProgressReporter"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="stageName">The value of <see cref="StageName"/>.</param>
|
||||
/// <param name="callback">The value of <see cref="callback"/>.</param>
|
||||
public JobProgressReporter(ILogger<JobProgressReporter> logger, string stageName, Action<string, double?> callback)
|
||||
{
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.callback = callback ?? throw new ArgumentNullException(nameof(callback));
|
||||
StageName = stageName;
|
||||
|
||||
logger.LogDebug("Job progress reporter created. Stage: {stageName}", stageName ?? "(null)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Report progress.
|
||||
/// </summary>
|
||||
/// <param name="progress">A percentage value from 0.0f-1.0f.</param>
|
||||
public void ReportProgress(double? progress)
|
||||
{
|
||||
var clampedProgress = progress;
|
||||
if (progress.HasValue)
|
||||
if (progress > 1 || progress < 0)
|
||||
{
|
||||
logger.LogError(
|
||||
new ArgumentOutOfRangeException(nameof(progress), progress, "Progress must be a value from 0-1!"),
|
||||
"Invalid progress value for stage {stageName}",
|
||||
StageName ?? "(null)");
|
||||
clampedProgress = null;
|
||||
}
|
||||
else
|
||||
sectionProgression = progress.Value;
|
||||
|
||||
if (clampedProgress.HasValue)
|
||||
callback(StageName, (int)Math.Floor(clampedProgress.Value * 100));
|
||||
else
|
||||
callback(StageName, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a subsection of the <see cref="JobProgressReporter"/> with its optional own stage name.
|
||||
/// </summary>
|
||||
/// <param name="newStageName">The optional <see cref="StageName"/> of the new <see cref="JobProgressReporter"/>.</param>
|
||||
/// <param name="percentage">The 0.0f-1.0f percentage of the current <see cref="JobProgressReporter"/>'s percentage should be given to the section.</param>
|
||||
/// <returns>A new <see cref="JobProgressReporter"/> that is a subsection of this one.</returns>
|
||||
/// <remarks>A <see cref="JobProgressReporter"/> should only have one active child at a time.</remarks>
|
||||
public JobProgressReporter CreateSection(string newStageName, double percentage)
|
||||
{
|
||||
if (percentage > 1 || percentage < 0)
|
||||
{
|
||||
logger.LogError(
|
||||
new ArgumentOutOfRangeException(nameof(percentage), percentage, "Percentage must be a value from 0-1!"),
|
||||
"Invalid percentage value for stage {newStageName}! Clamping...",
|
||||
newStageName ?? "(null)");
|
||||
|
||||
percentage = Math.Min(Math.Max(percentage, 0.0), 1.0);
|
||||
}
|
||||
|
||||
var childBaseProgress = sectionProgression;
|
||||
if (percentage + childBaseProgress > 1.0)
|
||||
{
|
||||
var remainingPercentage = 1.0 - childBaseProgress;
|
||||
logger.LogError(
|
||||
"Stage {newStageName} is overbudgeted ({budget}/{remainingPercentage})! Clamping...",
|
||||
newStageName,
|
||||
percentage,
|
||||
remainingPercentage);
|
||||
percentage = remainingPercentage;
|
||||
}
|
||||
|
||||
var newReporter = new JobProgressReporter(
|
||||
logger,
|
||||
newStageName,
|
||||
(currentStage, progress) =>
|
||||
{
|
||||
currentStage ??= StageName;
|
||||
if (!progress.HasValue)
|
||||
{
|
||||
callback(currentStage, null);
|
||||
return;
|
||||
}
|
||||
|
||||
var childLocalProgress = progress.Value * percentage;
|
||||
|
||||
sectionProgression = childLocalProgress + childBaseProgress;
|
||||
callback(currentStage, sectionProgression);
|
||||
});
|
||||
|
||||
newReporter.ReportProgress(0);
|
||||
return newReporter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Database.Migrations;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.System;
|
||||
using Tgstation.Server.Tests.Instance;
|
||||
|
||||
@@ -1066,18 +1067,18 @@ namespace Tgstation.Server.Tests
|
||||
using var testingServer = new TestingServer(null, false);
|
||||
LibGit2Sharp.Repository.Clone("https://github.com/Cyberboss/test", testingServer.Directory);
|
||||
var libGit2Repo = new LibGit2Sharp.Repository(testingServer.Directory);
|
||||
using var repo = new Host.Components.Repository.Repository(
|
||||
using var repo = new Repository(
|
||||
libGit2Repo,
|
||||
new LibGit2Commands(),
|
||||
Mock.Of<Host.IO.IIOManager>(),
|
||||
Mock.Of<IEventConsumer>(),
|
||||
Mock.Of<ICredentialsProvider>(),
|
||||
Mock.Of<IGitRemoteFeaturesFactory>(),
|
||||
Mock.Of<ILogger<Host.Components.Repository.Repository>>(),
|
||||
Mock.Of<ILogger<Repository>>(),
|
||||
() => { });
|
||||
|
||||
const string StartSha = "af4da8beb9f9b374b04a3cc4d65acca662e8cc1a";
|
||||
await repo.CheckoutObject(StartSha, null, null, true, (stage, progress) => { }, default);
|
||||
await repo.CheckoutObject(StartSha, null, null, true, new JobProgressReporter(Mock.Of<ILogger<JobProgressReporter>>(), null, (stage, progress) => { }), default);
|
||||
var result = await repo.ShaIsParent("2f8588a3ca0f6b027704a2a04381215619de3412", default);
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(StartSha, repo.Head);
|
||||
|
||||
Reference in New Issue
Block a user