diff --git a/build/ControlPanelVersion.props b/build/ControlPanelVersion.props
index 5da5b5abcc..3fc3e9b4ec 100644
--- a/build/ControlPanelVersion.props
+++ b/build/ControlPanelVersion.props
@@ -1,6 +1,6 @@
- 4.0.0
+ 4.2.2
diff --git a/build/Version.props b/build/Version.props
index db174ccffb..c94784c13d 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -3,11 +3,11 @@
- 5.1.4
+ 5.2.0
4.4.0
- 9.6.0
- 10.0.0
- 11.0.0
+ 9.7.0
+ 10.1.0
+ 11.1.0
6.0.5
5.3.0
1.2.0
diff --git a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs
index 88ff9b0d47..c51d74a49f 100644
--- a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs
+++ b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs
@@ -24,6 +24,11 @@ namespace Tgstation.Server.Api.Models
///
public bool BasedMeme { get; set; }
+ ///
+ /// If the tgstation-server logo is shown in deployment embeds.
+ ///
+ public bool DeploymentBranding { get; set; }
+
///
/// The .
///
@@ -47,21 +52,25 @@ namespace Tgstation.Server.Api.Models
throw new ArgumentNullException(nameof(connectionString));
var splits = connectionString.Split(';');
+
BotToken = splits.First();
+
if (splits.Length < 2 || !Enum.TryParse(splits[1], out var dMOutputDisplayType))
dMOutputDisplayType = DiscordDMOutputDisplayType.Always;
DMOutputDisplay = dMOutputDisplayType;
+
if (splits.Length > 2 && Int32.TryParse(splits[2], out Int32 basedMeme))
- {
BasedMeme = Convert.ToBoolean(basedMeme);
- }
else
- {
BasedMeme = true; // oranges said this needs to be true by default :pensive:
- }
+
+ if (splits.Length > 3 && Int32.TryParse(splits[3], out Int32 branding))
+ DeploymentBranding = Convert.ToBoolean(branding);
+ else
+ DeploymentBranding = true; // previous default behaviour
}
///
- public override string ToString() => $"{BotToken};{(int)DMOutputDisplay};{Convert.ToInt32(BasedMeme)}";
+ public override string ToString() => $"{BotToken};{(int)DMOutputDisplay};{Convert.ToInt32(BasedMeme)};{Convert.ToInt32(DeploymentBranding)}";
}
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
index 468982d7af..8613aacb86 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
@@ -70,6 +70,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
readonly bool basedMeme;
+ ///
+ /// If the tgstation-server logo is shown in deployment embeds.
+ ///
+ readonly bool deploymentBranding;
+
///
/// The .
///
@@ -177,6 +182,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var botToken = csb.BotToken;
basedMeme = csb.BasedMeme;
outputDisplayType = csb.DMOutputDisplay;
+ deploymentBranding = csb.DeploymentBranding;
serviceProvider = new ServiceCollection()
.AddDiscordGateway(serviceProvider => botToken)
@@ -282,13 +288,14 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
var fields = BuildUpdateEmbedFields(revisionInformation, byondVersion, gitHubOwner, gitHubRepo, localCommitPushed);
+ var author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
+ {
+ Url = "https://github.com/tgstation/tgstation-server",
+ IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4",
+ };
var embed = new Embed
{
- Author = new EmbedAuthor(assemblyInformationProvider.VersionPrefix)
- {
- Url = "https://github.com/tgstation/tgstation-server",
- IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4",
- },
+ Author = deploymentBranding ? author : default,
Colour = Color.FromArgb(0xF1, 0xC4, 0x0F),
Description = "TGS has begun deploying active repository code to production.",
Fields = fields,
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs
index 96c1fd3f8f..c75c3832bf 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/BaseRemoteDeploymentManager.cs
@@ -147,7 +147,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
public abstract Task MarkInactive(CompileJob compileJob, CancellationToken cancellationToken);
///
- public abstract Task> RemoveMergedTestMerges(
+ public abstract Task> RemoveMergedTestMerges(
IRepository repository,
RepositorySettings repositorySettings,
RevisionInformation revisionInformation,
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
index c0d15ed98b..4bbdb835d4 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitHubRemoteDeploymentManager.cs
@@ -187,7 +187,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
cancellationToken);
///
- public override async Task> RemoveMergedTestMerges(
+ public override async Task> RemoveMergedTestMerges(
IRepository repository,
RepositorySettings repositorySettings,
RevisionInformation revisionInformation,
@@ -203,7 +203,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
if (revisionInformation.ActiveTestMerges?.Any() != true)
{
Logger.LogTrace("No test merges to remove.");
- return Array.Empty();
+ return Array.Empty();
}
var gitHubClient = repositorySettings.AccessToken != null
@@ -225,7 +225,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
Logger.LogWarning(ex, "Pull requests update check failed!");
}
- var newList = revisionInformation.ActiveTestMerges.ToList();
+ var newList = revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList();
PullRequest lastMerged = null;
async Task CheckRemovePR(Task task)
@@ -241,7 +241,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
lastMerged = pr;
newList.Remove(
newList.First(
- potential => potential.TestMerge.Number == pr.Number));
+ potential => potential.Number == pr.Number));
}
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs
index 8d242b7164..62c6e31ccd 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/GitLabRemoteDeploymentManager.cs
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
}
///
- public override async Task> RemoveMergedTestMerges(
+ public override async Task> RemoveMergedTestMerges(
IRepository repository,
RepositorySettings repositorySettings,
RevisionInformation revisionInformation,
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
if (revisionInformation.ActiveTestMerges?.Any() != true)
{
Logger.LogTrace("No test merges to remove.");
- return Array.Empty();
+ return Array.Empty();
}
var client = repositorySettings.AccessToken != null
@@ -72,7 +72,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
Logger.LogWarning(ex, "Merge requests update check failed!");
}
- var newList = revisionInformation.ActiveTestMerges.ToList();
+ var newList = revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList();
MergeRequest lastMerged = null;
async Task CheckRemoveMR(Task task)
@@ -88,7 +88,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
lastMerged = mergeRequest;
newList.Remove(
newList.First(
- potential => potential.TestMerge.Number == mergeRequest.Id));
+ potential => potential.Number == mergeRequest.Id));
}
}
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs
index faed399d66..4c3f357cda 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/IRemoteDeploymentManager.cs
@@ -85,8 +85,8 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
/// The .
/// The current .
/// The for the operation.
- /// A resulting in the of s that should remain the new .
- Task> RemoveMergedTestMerges(
+ /// A resulting in the of s that should remain the new .
+ Task> RemoveMergedTestMerges(
IRepository repository,
RepositorySettings repositorySettings,
RevisionInformation revisionInformation,
diff --git a/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs b/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs
index 3a19f96471..069b8912fa 100644
--- a/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs
+++ b/src/Tgstation.Server.Host/Components/Deployment/Remote/NoOpRemoteDeploymentManager.cs
@@ -40,11 +40,11 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
CancellationToken cancellationToken) => Task.CompletedTask;
///
- public Task> RemoveMergedTestMerges(
+ public Task> RemoveMergedTestMerges(
IRepository repository,
RepositorySettings repositorySettings,
RevisionInformation revisionInformation,
- CancellationToken cancellationToken) => Task.FromResult>(Array.Empty());
+ CancellationToken cancellationToken) => Task.FromResult>(Array.Empty());
///
public Task StageDeployment(CompileJob compileJob, CancellationToken cancellationToken) => Task.CompletedTask;
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index 3f6a114571..aba8fec889 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -305,7 +305,7 @@ namespace Tgstation.Server.Host.Components
var hasDbChanges = false;
RevisionInformation currentRevInfo = null;
Models.Instance attachedInstance = null;
- async Task UpdateRevInfo(string currentHead, bool onOrigin, IEnumerable updatedTestMerges)
+ async Task UpdateRevInfo(string currentHead, bool onOrigin, IEnumerable updatedTestMerges)
{
if (currentRevInfo == null)
{
@@ -351,8 +351,17 @@ namespace Tgstation.Server.Host.Components
};
if (!onOrigin)
- currentRevInfo.ActiveTestMerges = new List(
- updatedTestMerges ?? oldRevInfo.ActiveTestMerges);
+ {
+ var testMerges = updatedTestMerges ?? oldRevInfo.ActiveTestMerges.Select(x => x.TestMerge);
+ var revInfoTestMerges = testMerges.Select(
+ testMerge => new RevInfoTestMerge
+ {
+ TestMerge = testMerge,
+ })
+ .ToList();
+
+ currentRevInfo.ActiveTestMerges = revInfoTestMerges;
+ }
databaseContext.RevisionInformations.Add(currentRevInfo);
hasDbChanges = true;
@@ -381,8 +390,7 @@ namespace Tgstation.Server.Host.Components
repo,
repositorySettings,
currentRevInfo,
- cancellationToken)
- ;
+ cancellationToken);
if (updatedTestMerges.Count == 0)
{
diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs
new file mode 100644
index 0000000000..5e6b9808cf
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryUpdateService.cs
@@ -0,0 +1,578 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Models.Request;
+using Tgstation.Server.Host.Database;
+using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Components.Repository
+{
+ ///
+ /// Service for performing the complex of updating the repository on user request.
+ ///
+ sealed class RepositoryUpdateService
+ {
+ ///
+ /// The for the .
+ ///
+ readonly RepositoryUpdateRequest model;
+
+ ///
+ /// The current for the .
+ ///
+ readonly RepositorySettings currentModel;
+
+ ///
+ /// The that initiated the repository update.
+ ///
+ readonly User initiatingUser;
+
+ ///
+ /// The for the .
+ ///
+ readonly ILogger logger;
+
+ ///
+ /// The of the associated .
+ ///
+ readonly long instanceId;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ /// The value of .
+ public RepositoryUpdateService(
+ RepositoryUpdateRequest model,
+ RepositorySettings currentModel,
+ User initiatingUser,
+ ILogger logger,
+ long instanceId)
+ {
+ this.model = model ?? throw new ArgumentNullException(nameof(model));
+ this.currentModel = currentModel ?? throw new ArgumentNullException(nameof(currentModel));
+ this.initiatingUser = initiatingUser ?? throw new ArgumentNullException(nameof(initiatingUser));
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ this.instanceId = instanceId;
+ }
+
+ ///
+ /// Load the for the current state into a given .
+ ///
+ /// The .
+ /// The active .
+ /// The to use.
+ /// The active .
+ /// The last known origin commit SHA of the if any.
+ /// An optional to receive the loaded .
+ /// The for the operation.
+ /// A resulting in if the was modified in a way that requires saving, otherwise.
+ public static async Task LoadRevisionInformation(
+ IRepository repository,
+ IDatabaseContext databaseContext,
+ ILogger logger,
+ Models.Instance instance,
+ string lastOriginCommitSha,
+ Action revInfoSink,
+ CancellationToken cancellationToken)
+ {
+ var repoSha = repository.Head;
+
+ IQueryable ApplyQuery(IQueryable query) => query
+ .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
+ .Include(x => x.CompileJobs)
+ .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy);
+
+ var revisionInfo = await ApplyQuery(databaseContext.RevisionInformations).FirstOrDefaultAsync(cancellationToken);
+
+ // If the DB doesn't have it, check the local set
+ if (revisionInfo == default)
+ revisionInfo = databaseContext
+ .RevisionInformations
+ .Local
+ .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
+ .FirstOrDefault();
+
+ var needsDbUpdate = revisionInfo == default;
+ if (needsDbUpdate)
+ {
+ // needs insertion
+ revisionInfo = new Models.RevisionInformation
+ {
+ Instance = instance,
+ CommitSha = repoSha,
+ Timestamp = await repository.TimestampCommit(repoSha, cancellationToken),
+ CompileJobs = new List(),
+ ActiveTestMerges = new List(), // non null vals for api returns
+ };
+
+ lock (databaseContext) // cleaner this way
+ databaseContext.RevisionInformations.Add(revisionInfo);
+ }
+
+ revisionInfo.OriginCommitSha ??= lastOriginCommitSha;
+ if (revisionInfo.OriginCommitSha == null)
+ {
+ revisionInfo.OriginCommitSha = repoSha;
+ logger.LogInformation(Repository.OriginTrackingErrorTemplate, repoSha);
+ }
+
+ revInfoSink?.Invoke(revisionInfo);
+ return needsDbUpdate;
+ }
+
+ ///
+ /// The job entrypoint used by to update the repository's current HEAD.
+ ///
+ /// The the job is running on. only when performing an instance move operation.
+ /// The for the operation.
+ /// The running , ignored.
+ /// The for the job.
+ /// The for the operation.
+ /// A representing the running operation.
+#pragma warning disable CA1502, CA1506 // TODO: Decomplexify
+ public async Task RepositoryUpdateJob(
+ IInstanceCore instance,
+ IDatabaseContextFactory databaseContextFactory,
+ Job job,
+ JobProgressReporter progressReporter,
+ CancellationToken cancellationToken)
+#pragma warning restore CA1502, CA1506
+ {
+ var repoManager = instance.RepositoryManager;
+ using var repo = await repoManager.LoadRepository(cancellationToken);
+ if (repo == null)
+ throw new JobException(ErrorCode.RepoMissing);
+
+ var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
+
+ var startReference = repo.Reference;
+ var startSha = repo.Head;
+ string postUpdateSha = null;
+
+ var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
+
+ if (newTestMerges && repo.RemoteGitProvider == RemoteGitProvider.Unknown)
+ throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
+
+ var committerName = currentModel.ShowTestMergeCommitters.Value
+ ? initiatingUser.Name
+ : currentModel.CommitterName;
+
+ 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 progressFactor = 1.0 / numSteps;
+
+ JobProgressReporter NextProgressReporter(string stage)
+ {
+ return progressReporter.CreateSection(stage, progressFactor);
+ }
+
+ progressReporter.ReportProgress(0);
+
+ // get a base line for where we are
+ Models.RevisionInformation lastRevisionInfo = null;
+
+ var attachedInstance = new Models.Instance
+ {
+ Id = instanceId,
+ };
+
+ Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
+ .UseContext(
+ async databaseContext =>
+ {
+ databaseContext.Instances.Attach(attachedInstance);
+ var previousRevInfo = lastRevisionInfo;
+ var needsUpdate = await LoadRevisionInformation(
+ repo,
+ databaseContext,
+ logger,
+ attachedInstance,
+ lastOriginCommitSha,
+ x => lastRevisionInfo = x,
+ cancellationToken);
+
+ if (testMergeToAdd != null)
+ {
+ // rev info may have already loaded the user
+ var mergedBy = databaseContext.Users.Local.FirstOrDefault(x => x.Id == initiatingUser.Id);
+ if (mergedBy == default)
+ {
+ mergedBy = new User
+ {
+ Id = initiatingUser.Id,
+ };
+
+ databaseContext.Users.Attach(mergedBy);
+ }
+
+ testMergeToAdd.MergedBy = mergedBy;
+ testMergeToAdd.MergedAt = DateTimeOffset.UtcNow;
+
+ foreach (var activeTestMerge in previousRevInfo.ActiveTestMerges)
+ lastRevisionInfo.ActiveTestMerges.Add(activeTestMerge);
+
+ lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
+ {
+ TestMerge = testMergeToAdd,
+ });
+ lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
+
+ needsUpdate = true;
+ }
+
+ if (needsUpdate)
+ await databaseContext.Save(cancellationToken);
+ });
+
+ await CallLoadRevInfo();
+
+ // apply new rev info, tracking applied test merges
+ Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null) => CallLoadRevInfo(testMergeToAdd, lastRevisionInfo.OriginCommitSha);
+
+ try
+ {
+ // fetch/pull
+ if (model.UpdateFromOrigin == true)
+ {
+ if (!repo.Tracking)
+ throw new JobException(ErrorCode.RepoReferenceRequired);
+ await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter("Fetch Origin"), cancellationToken);
+
+ if (!modelHasShaOrReference)
+ {
+ var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter("Merge Origin"), cancellationToken);
+ if (!fastForward.HasValue)
+ throw new JobException(ErrorCode.RepoMergeConflict);
+ lastRevisionInfo.OriginCommitSha = await repo.GetOriginSha(cancellationToken);
+ await UpdateRevInfo();
+ if (fastForward.Value)
+ {
+ await repo.Sychronize(
+ currentModel.AccessUser,
+ currentModel.AccessToken,
+ currentModel.CommitterName,
+ currentModel.CommitterEmail,
+ NextProgressReporter("Sychronize"),
+ true,
+ cancellationToken)
+ ;
+ postUpdateSha = repo.Head;
+ }
+ else
+ NextProgressReporter(null).ReportProgress(1.0);
+ }
+ }
+
+ var updateSubmodules = currentModel.UpdateSubmodules.Value;
+
+ // checkout/hard reset
+ if (modelHasShaOrReference)
+ {
+ var validCheckoutSha =
+ model.CheckoutSha != null
+ && !repo.Head.StartsWith(model.CheckoutSha, StringComparison.OrdinalIgnoreCase);
+ var validCheckoutReference =
+ model.Reference != null
+ && !repo.Reference.Equals(model.Reference, StringComparison.OrdinalIgnoreCase);
+
+ if (validCheckoutSha || validCheckoutReference)
+ {
+ var committish = model.CheckoutSha ?? model.Reference;
+ var isSha = await repo.IsSha(committish, cancellationToken);
+
+ if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
+ throw new JobException(ErrorCode.RepoSwappedShaOrReference);
+
+ await repo.CheckoutObject(
+ committish,
+ currentModel.AccessUser,
+ currentModel.AccessToken,
+ updateSubmodules,
+ NextProgressReporter("Checkout"),
+ cancellationToken)
+ ;
+ await CallLoadRevInfo(); // we've either seen origin before or what we're checking out is on origin
+ }
+ else
+ NextProgressReporter(null).ReportProgress(1.0);
+
+ if (hardResettingToOriginReference)
+ {
+ if (!repo.Tracking)
+ throw new JobException(ErrorCode.RepoReferenceNotTracking);
+ await repo.ResetToOrigin(
+ currentModel.AccessUser,
+ currentModel.AccessToken,
+ updateSubmodules,
+ NextProgressReporter("Reset to Origin"),
+ cancellationToken)
+ ;
+ await repo.Sychronize(
+ currentModel.AccessUser,
+ currentModel.AccessToken,
+ currentModel.CommitterName,
+ currentModel.CommitterEmail,
+ NextProgressReporter("Synchronize"),
+ true,
+ cancellationToken)
+ ;
+ await CallLoadRevInfo();
+
+ // repo head is on origin so force this
+ // will update the db if necessary
+ lastRevisionInfo.OriginCommitSha = repo.Head;
+ }
+ }
+
+ // test merging
+ if (newTestMerges)
+ {
+ if (repo.RemoteGitProvider == RemoteGitProvider.Unknown)
+ throw new JobException(ErrorCode.RepoTestMergeInvalidRemote);
+
+ // bit of sanitization
+ foreach (var newTestMergeWithoutTargetCommitSha in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.TargetCommitSha)))
+ newTestMergeWithoutTargetCommitSha.TargetCommitSha = null;
+
+ var repoOwner = repo.RemoteRepositoryOwner;
+ var repoName = repo.RemoteRepositoryName;
+
+ // optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
+ Models.RevisionInformation revInfoWereLookingFor = null;
+ bool needToApplyRemainingPrs = true;
+ if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
+ {
+ bool cantSearch = false;
+ foreach (var newTestMerge in model.NewTestMerges)
+ {
+ if (newTestMerge.TargetCommitSha != null)
+#pragma warning disable CA1308 // Normalize strings to uppercase
+ newTestMerge.TargetCommitSha = newTestMerge.TargetCommitSha?.ToLowerInvariant(); // ala libgit2
+#pragma warning restore CA1308 // Normalize strings to uppercase
+ else
+ try
+ {
+ // retrieve the latest sha
+ var pr = await repo.GetTestMerge(newTestMerge, currentModel, cancellationToken);
+
+ // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
+ newTestMerge.TargetCommitSha = pr.TargetCommitSha;
+ }
+ catch
+ {
+ cantSearch = true;
+ break;
+ }
+ }
+
+ if (!cantSearch)
+ {
+ List dbPull = null;
+
+ await databaseContextFactory.UseContext(
+ async databaseContext =>
+ dbPull = await databaseContext.RevisionInformations
+ .AsQueryable()
+ .Where(x => x.Instance.Id == instanceId
+ && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
+ && x.ActiveTestMerges.Count <= model.NewTestMerges.Count
+ && x.ActiveTestMerges.Count > 0)
+ .Include(x => x.ActiveTestMerges)
+ .ThenInclude(x => x.TestMerge)
+ .ToListAsync(cancellationToken));
+
+ // split here cause this bit has to be done locally
+ revInfoWereLookingFor = dbPull
+ .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
+ && x.ActiveTestMerges.Select(y => y.TestMerge)
+ .All(y => model.NewTestMerges.Any(z =>
+ y.Number == z.Number
+ && y.TargetCommitSha.StartsWith(z.TargetCommitSha, StringComparison.Ordinal)
+ && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
+ .FirstOrDefault();
+
+ if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
+ {
+ // okay try to add at least SOME prs we've seen before
+ var listedNewTestMerges = model.NewTestMerges.ToList();
+
+ var appliedTestMergeIds = new List();
+
+ Models.RevisionInformation lastGoodRevInfo = null;
+ do
+ {
+ foreach (var newTestMergeParameters in listedNewTestMerges)
+ {
+ revInfoWereLookingFor = dbPull
+ .Where(testRevInfo =>
+ {
+ if (testRevInfo.PrimaryTestMerge == null)
+ return false;
+
+ var testMergeMatch = model.NewTestMerges.Any(testTestMerge =>
+ {
+ var numberMatch = testRevInfo.PrimaryTestMerge.Number == testTestMerge.Number;
+ if (!numberMatch)
+ return false;
+
+ var shaMatch = testRevInfo.PrimaryTestMerge.TargetCommitSha.StartsWith(
+ testTestMerge.TargetCommitSha,
+ StringComparison.Ordinal);
+ if (!shaMatch)
+ return false;
+
+ var commentMatch = testRevInfo.PrimaryTestMerge.Comment == testTestMerge.Comment;
+ return commentMatch;
+ });
+
+ if (!testMergeMatch)
+ return false;
+
+ var previousTestMergesMatch = testRevInfo
+ .ActiveTestMerges
+ .Select(previousRevInfoTestMerge => previousRevInfoTestMerge.TestMerge)
+ .All(previousTestMerge => appliedTestMergeIds.Contains(previousTestMerge.Id));
+
+ return previousTestMergesMatch;
+ })
+ .FirstOrDefault();
+
+ if (revInfoWereLookingFor != null)
+ {
+ lastGoodRevInfo = revInfoWereLookingFor;
+ appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id);
+ listedNewTestMerges.Remove(newTestMergeParameters);
+ break;
+ }
+ }
+ }
+ while (revInfoWereLookingFor != null && listedNewTestMerges.Count > 0);
+
+ revInfoWereLookingFor = lastGoodRevInfo;
+ needToApplyRemainingPrs = listedNewTestMerges.Count != 0;
+ if (needToApplyRemainingPrs)
+ model.NewTestMerges = listedNewTestMerges;
+ }
+ else if (revInfoWereLookingFor != null)
+ needToApplyRemainingPrs = false;
+ }
+ }
+
+ if (revInfoWereLookingFor != null)
+ {
+ // goteem
+ logger.LogDebug("Reusing existing SHA {0}...", revInfoWereLookingFor.CommitSha);
+ await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter($"Reset to {revInfoWereLookingFor.CommitSha[..7]}"), cancellationToken);
+ lastRevisionInfo = revInfoWereLookingFor;
+ }
+
+ if (needToApplyRemainingPrs)
+ {
+ foreach (var newTestMerge in model.NewTestMerges)
+ {
+ if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == newTestMerge.Number))
+ throw new JobException(ErrorCode.RepoDuplicateTestMerge);
+
+ var fullTestMergeTask = repo.GetTestMerge(newTestMerge, currentModel, cancellationToken);
+
+ var mergeResult = await repo.AddTestMerge(
+ newTestMerge,
+ committerName,
+ currentModel.CommitterEmail,
+ currentModel.AccessUser,
+ currentModel.AccessToken,
+ updateSubmodules,
+ NextProgressReporter($"Test merge #{newTestMerge.Number}"),
+ cancellationToken);
+
+ if (mergeResult == null)
+ throw new JobException(
+ ErrorCode.RepoTestMergeConflict,
+ new JobException(
+ $"Test Merge #{newTestMerge.Number} at {newTestMerge.TargetCommitSha[..7]} conflicted!"));
+
+ Models.TestMerge fullTestMerge;
+ try
+ {
+ fullTestMerge = await fullTestMergeTask;
+ }
+ catch (Exception ex)
+ {
+ logger.LogWarning("Error retrieving metadata for test merge #{testMergeNumber}!", newTestMerge.Number);
+
+ fullTestMerge = new Models.TestMerge
+ {
+ Author = ex.Message,
+ BodyAtMerge = ex.Message,
+ TitleAtMerge = ex.Message,
+ Comment = newTestMerge.Comment,
+ Number = newTestMerge.Number,
+ Url = ex.Message,
+ };
+ }
+
+ // Ensure we're getting the full sha from git itself
+ fullTestMerge.TargetCommitSha = newTestMerge.TargetCommitSha;
+
+ await UpdateRevInfo(fullTestMerge);
+ }
+ }
+ }
+
+ var currentHead = repo.Head;
+ if (currentModel.PushTestMergeCommits.Value && (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead)))
+ {
+ await repo.Sychronize(
+ currentModel.AccessUser,
+ currentModel.AccessToken,
+ currentModel.CommitterName,
+ currentModel.CommitterEmail,
+ NextProgressReporter("Synchronize"),
+ false,
+ cancellationToken)
+ ;
+ await UpdateRevInfo();
+ }
+
+ return null;
+ }
+ catch
+ {
+ 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,
+ 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;
+ }
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index efd7df793f..27bacaab39 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
@@ -17,7 +16,7 @@ using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components;
-using Tgstation.Server.Host.Core;
+using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
@@ -33,9 +32,9 @@ namespace Tgstation.Server.Host.Controllers
public sealed class RepositoryController : InstanceRequiredController
{
///
- /// The for the .
+ /// The for the .
///
- readonly IGitHubClientFactory gitHubClientFactory;
+ readonly ILoggerFactory loggerFactory;
///
/// The for the .
@@ -48,14 +47,14 @@ namespace Tgstation.Server.Host.Controllers
/// The for the .
/// The for the .
/// The for the .
- /// The value of .
+ /// The value of .
/// The value of .
/// The for the .
public RepositoryController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
IInstanceManager instanceManager,
- IGitHubClientFactory gitHubClientFactory,
+ ILoggerFactory loggerFactory,
IJobManager jobManager,
ILogger logger)
: base(
@@ -64,7 +63,7 @@ namespace Tgstation.Server.Host.Controllers
authenticationContextFactory,
logger)
{
- this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
+ this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
}
@@ -315,8 +314,9 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(RepositoryResponse), 200)]
[ProducesResponseType(typeof(RepositoryResponse), 202)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
-#pragma warning disable CA1502, CA1505 // TODO: Decomplexify
+#pragma warning disable CA1502 // TODO: Decomplexify
public async Task Update([FromBody] RepositoryUpdateRequest model, CancellationToken cancellationToken)
+#pragma warning restore CA1502
{
if (model == null)
throw new ArgumentNullException(nameof(model));
@@ -462,439 +462,6 @@ namespace Tgstation.Server.Host.Controllers
if (description == null)
return Json(api); // no git changes
- async Task RepositoryUpdateJobOhGodPleaseSomeoneRefactorThisItsTooFuckingBig(
- IInstanceCore instance,
- IDatabaseContextFactory databaseContextFactory,
- JobProgressReporter progressReporter,
- CancellationToken ct)
- {
- var repoManager = instance.RepositoryManager;
- using var repo = await repoManager.LoadRepository(ct);
- if (repo == null)
- throw new JobException(ErrorCode.RepoMissing);
-
- var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
-
- var startReference = repo.Reference;
- var startSha = repo.Head;
- string postUpdateSha = null;
-
- if (newTestMerges && repo.RemoteGitProvider == RemoteGitProvider.Unknown)
- throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
-
- var committerName = currentModel.ShowTestMergeCommitters.Value
- ? AuthenticationContext.User.Name
- : currentModel.CommitterName;
-
- 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 progressFactor = 1.0 / numSteps;
-
- JobProgressReporter NextProgressReporter(string stage)
- {
- return progressReporter.CreateSection(stage, progressFactor);
- }
-
- progressReporter.ReportProgress(0);
-
- // get a base line for where we are
- Models.RevisionInformation lastRevisionInfo = null;
-
- var attachedInstance = new Models.Instance
- {
- Id = Instance.Id,
- };
-
- Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
- .UseContext(
- async databaseContext =>
- {
- databaseContext.Instances.Attach(attachedInstance);
- var previousRevInfo = lastRevisionInfo;
- var needsUpdate = await LoadRevisionInformation(
- repo,
- databaseContext,
- attachedInstance,
- lastOriginCommitSha,
- x => lastRevisionInfo = x,
- ct)
- ;
-
- if (testMergeToAdd != null)
- {
- // rev info may have already loaded the user
- var mergedBy = databaseContext.Users.Local.FirstOrDefault(x => x.Id == AuthenticationContext.User.Id);
- if (mergedBy == default)
- {
- mergedBy = new User
- {
- Id = AuthenticationContext.User.Id,
- };
-
- databaseContext.Users.Attach(mergedBy);
- }
-
- testMergeToAdd.MergedBy = mergedBy;
- testMergeToAdd.MergedAt = DateTimeOffset.UtcNow;
-
- foreach (var activeTestMerge in previousRevInfo.ActiveTestMerges)
- lastRevisionInfo.ActiveTestMerges.Add(activeTestMerge);
-
- lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
- {
- TestMerge = testMergeToAdd,
- });
- lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
-
- needsUpdate = true;
- }
-
- if (needsUpdate)
- await databaseContext.Save(ct);
- });
-
- await CallLoadRevInfo();
-
- // apply new rev info, tracking applied test merges
- Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null) => CallLoadRevInfo(testMergeToAdd, lastRevisionInfo.OriginCommitSha);
-
- try
- {
- // fetch/pull
- if (model.UpdateFromOrigin == true)
- {
- if (!repo.Tracking)
- throw new JobException(ErrorCode.RepoReferenceRequired);
- await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter("Fetch Origin"), ct);
-
- if (!modelHasShaOrReference)
- {
- 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(ct);
- await UpdateRevInfo();
- if (fastForward.Value)
- {
- await repo.Sychronize(
- currentModel.AccessUser,
- currentModel.AccessToken,
- currentModel.CommitterName,
- currentModel.CommitterEmail,
- NextProgressReporter("Sychronize"),
- true,
- ct)
- ;
- postUpdateSha = repo.Head;
- }
- else
- NextProgressReporter(null).ReportProgress(1.0);
- }
- }
-
- var updateSubmodules = currentModel.UpdateSubmodules.Value;
-
- // checkout/hard reset
- if (modelHasShaOrReference)
- {
- var validCheckoutSha =
- model.CheckoutSha != null
- && !repo.Head.StartsWith(model.CheckoutSha, StringComparison.OrdinalIgnoreCase);
- var validCheckoutReference =
- model.Reference != null
- && !repo.Reference.Equals(model.Reference, StringComparison.OrdinalIgnoreCase);
-
- if (validCheckoutSha || validCheckoutReference)
- {
- var committish = model.CheckoutSha ?? model.Reference;
- var isSha = await repo.IsSha(committish, ct);
-
- if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
- throw new JobException(ErrorCode.RepoSwappedShaOrReference);
-
- await repo.CheckoutObject(
- committish,
- currentModel.AccessUser,
- currentModel.AccessToken,
- updateSubmodules,
- NextProgressReporter("Checkout"),
- ct)
- ;
- await CallLoadRevInfo(); // we've either seen origin before or what we're checking out is on origin
- }
- else
- NextProgressReporter(null).ReportProgress(1.0);
-
- if (hardResettingToOriginReference)
- {
- if (!repo.Tracking)
- throw new JobException(ErrorCode.RepoReferenceNotTracking);
- await repo.ResetToOrigin(
- currentModel.AccessUser,
- currentModel.AccessToken,
- updateSubmodules,
- NextProgressReporter("Reset to Origin"),
- ct)
- ;
- await repo.Sychronize(
- currentModel.AccessUser,
- currentModel.AccessToken,
- currentModel.CommitterName,
- currentModel.CommitterEmail,
- NextProgressReporter("Synchronize"),
- true,
- ct)
- ;
- await CallLoadRevInfo();
-
- // repo head is on origin so force this
- // will update the db if necessary
- lastRevisionInfo.OriginCommitSha = repo.Head;
- }
- }
-
- // test merging
- if (newTestMerges)
- {
- if (repo.RemoteGitProvider == RemoteGitProvider.Unknown)
- throw new JobException(ErrorCode.RepoTestMergeInvalidRemote);
-
- // bit of sanitization
- foreach (var newTestMergeWithoutTargetCommitSha in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.TargetCommitSha)))
- newTestMergeWithoutTargetCommitSha.TargetCommitSha = null;
-
- var gitHubClient = currentModel.AccessToken != null
- ? gitHubClientFactory.CreateClient(currentModel.AccessToken)
- : gitHubClientFactory.CreateClient();
-
- var repoOwner = repo.RemoteRepositoryOwner;
- var repoName = repo.RemoteRepositoryName;
-
- // optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
- Models.RevisionInformation revInfoWereLookingFor = null;
- bool needToApplyRemainingPrs = true;
- if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
- {
- bool cantSearch = false;
- foreach (var newTestMerge in model.NewTestMerges)
- {
- if (newTestMerge.TargetCommitSha != null)
-#pragma warning disable CA1308 // Normalize strings to uppercase
- newTestMerge.TargetCommitSha = newTestMerge.TargetCommitSha?.ToLowerInvariant(); // ala libgit2
-#pragma warning restore CA1308 // Normalize strings to uppercase
- else
- try
- {
- // retrieve the latest sha
- var pr = await repo.GetTestMerge(newTestMerge, currentModel, ct);
-
- // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
- newTestMerge.TargetCommitSha = pr.TargetCommitSha;
- }
- catch
- {
- cantSearch = true;
- break;
- }
- }
-
- if (!cantSearch)
- {
- List dbPull = null;
-
- await databaseContextFactory.UseContext(
- async databaseContext =>
- dbPull = await databaseContext.RevisionInformations
- .AsQueryable()
- .Where(x => x.Instance.Id == Instance.Id
- && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
- && x.ActiveTestMerges.Count <= model.NewTestMerges.Count
- && x.ActiveTestMerges.Count > 0)
- .Include(x => x.ActiveTestMerges)
- .ThenInclude(x => x.TestMerge)
- .ToListAsync(ct));
-
- // split here cause this bit has to be done locally
- revInfoWereLookingFor = dbPull
- .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
- && x.ActiveTestMerges.Select(y => y.TestMerge)
- .All(y => model.NewTestMerges.Any(z =>
- y.Number == z.Number
- && y.TargetCommitSha.StartsWith(z.TargetCommitSha, StringComparison.Ordinal)
- && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
- .FirstOrDefault();
-
- if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
- {
- // okay try to add at least SOME prs we've seen before
- var listedNewTestMerges = model.NewTestMerges.ToList();
-
- var appliedTestMergeIds = new List();
-
- Models.RevisionInformation lastGoodRevInfo = null;
- do
- {
- foreach (var newTestMergeParameters in listedNewTestMerges)
- {
- revInfoWereLookingFor = dbPull
- .Where(testRevInfo =>
- {
- if (testRevInfo.PrimaryTestMerge == null)
- return false;
-
- var testMergeMatch = model.NewTestMerges.Any(testTestMerge =>
- {
- var numberMatch = testRevInfo.PrimaryTestMerge.Number == testTestMerge.Number;
- if (!numberMatch)
- return false;
-
- var shaMatch = testRevInfo.PrimaryTestMerge.TargetCommitSha.StartsWith(
- testTestMerge.TargetCommitSha,
- StringComparison.Ordinal);
- if (!shaMatch)
- return false;
-
- var commentMatch = testRevInfo.PrimaryTestMerge.Comment == testTestMerge.Comment;
- return commentMatch;
- });
-
- if (!testMergeMatch)
- return false;
-
- var previousTestMergesMatch = testRevInfo
- .ActiveTestMerges
- .Select(previousRevInfoTestMerge => previousRevInfoTestMerge.TestMerge)
- .All(previousTestMerge => appliedTestMergeIds.Contains(previousTestMerge.Id));
-
- return previousTestMergesMatch;
- })
- .FirstOrDefault();
-
- if (revInfoWereLookingFor != null)
- {
- lastGoodRevInfo = revInfoWereLookingFor;
- appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id);
- listedNewTestMerges.Remove(newTestMergeParameters);
- break;
- }
- }
- }
- while (revInfoWereLookingFor != null && listedNewTestMerges.Count > 0);
-
- revInfoWereLookingFor = lastGoodRevInfo;
- needToApplyRemainingPrs = listedNewTestMerges.Count != 0;
- if (needToApplyRemainingPrs)
- model.NewTestMerges = listedNewTestMerges;
- }
- else if (revInfoWereLookingFor != null)
- needToApplyRemainingPrs = false;
- }
- }
-
- if (revInfoWereLookingFor != null)
- {
- // goteem
- Logger.LogDebug("Reusing existing SHA {0}...", revInfoWereLookingFor.CommitSha);
- await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter($"Reset to {revInfoWereLookingFor.CommitSha[..7]}"), ct);
- lastRevisionInfo = revInfoWereLookingFor;
- }
-
- if (needToApplyRemainingPrs)
- {
- foreach (var newTestMerge in model.NewTestMerges)
- {
- if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == newTestMerge.Number))
- throw new JobException(ErrorCode.RepoDuplicateTestMerge);
-
- var fullTestMergeTask = repo.GetTestMerge(newTestMerge, currentModel, ct);
-
- var mergeResult = await repo.AddTestMerge(
- newTestMerge,
- committerName,
- currentModel.CommitterEmail,
- currentModel.AccessUser,
- currentModel.AccessToken,
- updateSubmodules,
- NextProgressReporter($"Test merge #{newTestMerge.Number}"),
- ct);
-
- if (mergeResult == null)
- throw new JobException(
- ErrorCode.RepoTestMergeConflict,
- new JobException(
- $"Test Merge #{newTestMerge.Number} at {newTestMerge.TargetCommitSha[..7]} conflicted!"));
-
- Models.TestMerge fullTestMerge;
- try
- {
- fullTestMerge = await fullTestMergeTask;
- }
- catch (Exception ex)
- {
- Logger.LogWarning("Error retrieving metadata for test merge #{testMergeNumber}!", newTestMerge.Number);
-
- fullTestMerge = new Models.TestMerge
- {
- Author = ex.Message,
- BodyAtMerge = ex.Message,
- TitleAtMerge = ex.Message,
- Comment = newTestMerge.Comment,
- Number = newTestMerge.Number,
- Url = ex.Message,
- };
- }
-
- // Ensure we're getting the full sha from git itself
- fullTestMerge.TargetCommitSha = newTestMerge.TargetCommitSha;
-
- await UpdateRevInfo(fullTestMerge);
- }
- }
- }
-
- var currentHead = repo.Head;
- if (currentModel.PushTestMergeCommits.Value && (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead)))
- {
- await repo.Sychronize(
- currentModel.AccessUser,
- currentModel.AccessToken,
- currentModel.CommitterName,
- currentModel.CommitterEmail,
- NextProgressReporter("Synchronize"),
- false,
- ct)
- ;
- await UpdateRevInfo();
- }
-
- return null;
- }
- catch
- {
- 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,
- 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;
- }
- }
-
var job = new Job
{
Description = description,
@@ -904,97 +471,35 @@ namespace Tgstation.Server.Host.Controllers
CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
};
+ var repositoryUpdater = new RepositoryUpdateService(
+ model,
+ currentModel,
+ AuthenticationContext.User,
+ loggerFactory.CreateLogger(),
+ Instance.Id.Value);
+
// Time to access git, do it in a job
await jobManager.RegisterOperation(
job,
- (core, databaseContextFactory, paramJob, progressReporter, ct) =>
- RepositoryUpdateJobOhGodPleaseSomeoneRefactorThisItsTooFuckingBig(
- core,
- databaseContextFactory,
- progressReporter,
- ct),
+ repositoryUpdater.RepositoryUpdateJob,
cancellationToken);
api.ActiveJob = job.ToApi();
return Accepted(api);
}
-#pragma warning restore CA1502, CA1505
-
- ///
- /// Load the for the current state into a given .
- ///
- /// The .
- /// The active .
- /// The active .
- /// The last known origin commit SHA of the if any.
- /// An optional to receive the loaded .
- /// The for the operation.
- /// A resulting in if the was modified in a way that requires saving, otherwise.
- async Task LoadRevisionInformation(
- Components.Repository.IRepository repository,
- IDatabaseContext databaseContext,
- Models.Instance instance,
- string lastOriginCommitSha,
- Action revInfoSink,
- CancellationToken cancellationToken)
- {
- var repoSha = repository.Head;
-
- IQueryable ApplyQuery(IQueryable query) => query
- .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
- .Include(x => x.CompileJobs)
- .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy);
-
- var revisionInfo = await ApplyQuery(databaseContext.RevisionInformations).FirstOrDefaultAsync(cancellationToken);
-
- // If the DB doesn't have it, check the local set
- if (revisionInfo == default)
- revisionInfo = databaseContext
- .RevisionInformations
- .Local
- .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
- .FirstOrDefault();
-
- var needsDbUpdate = revisionInfo == default;
- if (needsDbUpdate)
- {
- // needs insertion
- revisionInfo = new Models.RevisionInformation
- {
- Instance = instance,
- CommitSha = repoSha,
- Timestamp = await repository.TimestampCommit(repoSha, cancellationToken),
- CompileJobs = new List(),
- ActiveTestMerges = new List(), // non null vals for api returns
- };
-
- lock (databaseContext) // cleaner this way
- databaseContext.RevisionInformations.Add(revisionInfo);
- }
-
- revisionInfo.OriginCommitSha ??= lastOriginCommitSha;
- if (revisionInfo.OriginCommitSha == null)
- {
- revisionInfo.OriginCommitSha = repoSha;
- Logger.LogInformation(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha);
- }
-
- revInfoSink?.Invoke(revisionInfo);
- return needsDbUpdate;
- }
///
/// Populate a given with the current state of a given .
///
/// The to populate.
- /// The .
+ /// The .
/// The active .
/// The active .
/// The for the operation.
/// A resulting in if the was modified in a way that requires saving, otherwise.
async Task PopulateApi(
RepositoryResponse apiResponse,
- Components.Repository.IRepository repository,
+ IRepository repository,
IDatabaseContext databaseContext,
Models.Instance instance,
CancellationToken cancellationToken)
@@ -1008,7 +513,14 @@ namespace Tgstation.Server.Host.Controllers
// rev info stuff
Models.RevisionInformation revisionInfo = null;
- var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken);
+ var needsDbUpdate = await RepositoryUpdateService.LoadRevisionInformation(
+ repository,
+ databaseContext,
+ Logger,
+ instance,
+ null,
+ newRevInfo => revisionInfo = newRevInfo,
+ cancellationToken);
apiResponse.RevisionInformation = revisionInfo.ToApi();
return needsDbUpdate;
}
diff --git a/src/Tgstation.Server.Host/Setup/SetupWizard.cs b/src/Tgstation.Server.Host/Setup/SetupWizard.cs
index f07f33d4be..7618c7986f 100644
--- a/src/Tgstation.Server.Host/Setup/SetupWizard.cs
+++ b/src/Tgstation.Server.Host/Setup/SetupWizard.cs
@@ -801,7 +801,7 @@ namespace Tgstation.Server.Host.Setup
{
var config = new ControlPanelConfiguration
{
- Enable = await PromptYesNo("Enable the web control panel (Incomplete)? (y/n): ", cancellationToken),
+ Enable = await PromptYesNo("Enable the web control panel? (y/n): ", cancellationToken),
AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken),
};