Merge pull request #555 from Cyberboss/OkayWhatNow

Repository reporting 100%
This commit is contained in:
Jordan Brown
2018-08-02 08:45:46 -04:00
committed by GitHub
12 changed files with 345 additions and 150 deletions
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
@@ -11,6 +10,7 @@ using Tgstation.Server.Host.Components.Compiler;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Components.Watchdog;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
@@ -122,14 +122,15 @@ namespace Tgstation.Server.Host.Components
{
await Task.Delay(new TimeSpan(0, minutes, 0), cancellationToken).ConfigureAwait(false);
string accessToken = null, projectName = null;
RepositorySettings repositorySettings = null;
string projectName = null;
int timeout = 0;
var dbTask = databaseContextFactory.UseContext(async (db) =>
{
var instanceQuery = db.Instances.Where(x => x.Id == metadata.Id);
var timeoutTask = instanceQuery.Select(x => x.DreamDaemonSettings.StartupTimeout).FirstAsync(cancellationToken);
var projectNameTask = instanceQuery.Select(x => x.DreamMakerSettings.ProjectName).FirstOrDefaultAsync(cancellationToken);
accessToken = await instanceQuery.Select(x => x.RepositorySettings.AccessToken).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
repositorySettings = await instanceQuery.Select(x => x.RepositorySettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
projectName = await projectNameTask.ConfigureAwait(false);
timeout = (await timeoutTask.ConfigureAwait(false)).Value;
});
@@ -150,8 +151,27 @@ namespace Tgstation.Server.Host.Components
}
if (repo == null)
continue;
await repo.FetchOrigin(accessToken, null, cancellationToken).ConfigureAwait(false);
await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false);
await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, null, cancellationToken).ConfigureAwait(false);
var startSha = repo.Head;
bool shouldSyncTracked;
if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
{
var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, cancellationToken).ConfigureAwait(false);
if (!result.HasValue)
continue;
shouldSyncTracked = result.Value;
}
else
{
await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false);
shouldSyncTracked = true;
}
if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, shouldSyncTracked, cancellationToken).ConfigureAwait(false);
var job = await DreamMaker.Compile(projectName, timeout, repo, cancellationToken).ConfigureAwait(false);
}
}
@@ -59,20 +59,23 @@ namespace Tgstation.Server.Host.Components.Repository
/// <param name="targetCommit">The commit in the pull request to merge</param>
/// <param name="committerName">The name of the merge committer</param>
/// <param name="committerEmail">The e-mail of the merge committer</param>
/// <param name="accessString">The access string to fetch from the origin repository</param>
/// <param name="commitMessage">The commit message</param>
/// <param name="username">The username to fetch from the origin repository</param>
/// <param name="password">The password to fetch from the origin repository</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <param name="progressReporter">Optional function to report 0-100 progress of the clone</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward or up to date, <see langword="false"/> on a merge, <see langword="null"/> on a conflict</returns>
Task<bool?> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action<int> progressReporter, CancellationToken cancellationToken);
Task<bool?> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitMessage, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken);
/// <summary>
/// Fetch commits from the origin repository
/// </summary>
/// <param name="accessString">The access string to fetch from the origin repository</param>
/// <param name="username">The username to fetch from the origin repository</param>
/// <param name="password">The password to fetch from the origin repository</param>
/// <param name="progressReporter">Optional function to report 0-100 progress of the clone</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task FetchOrigin(string accessString, Action<int> progressReporter, CancellationToken cancellationToken);
Task FetchOrigin(string username, string password, Action<int> 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
@@ -81,6 +84,14 @@ namespace Tgstation.Server.Host.Components.Repository
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD</returns>
Task ResetToOrigin(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="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD</returns>
Task ResetToSha(string sha, CancellationToken cancellationToken);
/// <summary>
/// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository
/// </summary>
@@ -90,21 +101,15 @@ namespace Tgstation.Server.Host.Components.Repository
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward or up to date, <see langword="false"/> on a merge, <see langword="null"/> on a conflict</returns>
Task<bool?> MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken);
/// <summary>
/// Force push the current repository HEAD to <see cref="Repository.RemoteTemporaryBranchName"/>;
/// </summary>
/// <param name="accessString">The access string to fetch from the origin repository</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 accessString, 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
/// </summary>
/// <param name="accessString">The access string to push to the origin repository</param>
/// <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="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"/> representing the running operation</returns>
Task Sychronize(string accessString, CancellationToken cancellationToken);
Task Sychronize(string username, string password, bool synchronizeTrackedBranch, CancellationToken cancellationToken);
/// <summary>
/// Copies the current working directory to a given <paramref name="path"/>
@@ -21,11 +21,12 @@ namespace Tgstation.Server.Host.Components.Repository
/// </summary>
/// <param name="url">The <see cref="Uri"/> of the remote repository to clone</param>
/// <param name="initialBranch">The branch to clone</param>
/// <param name="accessString">The access string to clone from <paramref name="url"/></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="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>The newly cloned <see cref="IRepository"/>, <see langword="null"/> if one already exists</returns>
Task<IRepository> CloneRepository(Uri url, string initialBranch, string accessString, Action<int> progressReporter, CancellationToken cancellationToken);
Task<IRepository> CloneRepository(Uri url, string initialBranch, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken);
/// <summary>
/// Delete the current repository
@@ -106,24 +106,6 @@ namespace Tgstation.Server.Host.Components.Repository
repository.Dispose();
onDispose.Invoke();
}
/// <summary>
/// Convert <paramref name="url"/> to an "https://<paramref name="accessString"/>@{url} equivalent
/// </summary>
/// <param name="url">The URL to convert</param>
/// <param name="accessString">The <see cref="string"/> containing authentication info for the remote repository</param>
/// <returns>An authenticated URL for accessing the remote repository</returns>
public static string GenerateAuthUrl(string url, string accessString)
{
if (url == null)
throw new ArgumentNullException(nameof(url));
if (String.IsNullOrWhiteSpace(accessString))
return url;
const string HttProtocolSecure = "HTTPS://";
if (!url.ToUpperInvariant().StartsWith(HttProtocolSecure, StringComparison.InvariantCulture))
throw new InvalidOperationException("Cannot use access string without HTTPS remote!");
//ONLY support https urls
return url.ToUpperInvariant().Replace(HttProtocolSecure, String.Concat(HttProtocolSecure, accessString, '@'), StringComparison.Ordinal);
}
/// <summary>
/// Runs a blocking force checkout to <paramref name="committish"/>
@@ -139,7 +121,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
/// <inheritdoc />
public async Task<bool?> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task<bool?> AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitMessage, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
{
if (!IsGitHubRepository)
@@ -154,12 +136,13 @@ namespace Tgstation.Server.Host.Components.Repository
var originalCommit = repository.Head;
MergeResult result = null;
var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now);
await Task.Factory.StartNew(() =>
{
var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(Origin, accessString));
try
{
cancellationToken.ThrowIfCancellationRequested();
var remote = repository.Network.Remotes.First();
Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions
{
Prune = true,
@@ -170,23 +153,24 @@ namespace Tgstation.Server.Host.Components.Repository
progressReporter?.Invoke((int)percentage);
return !cancellationToken.IsCancellationRequested;
},
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
{
Username = username,
Password = password
} : new DefaultCredentials()
}, logMessage);
}
catch (UserCancelledException) { }
finally
catch (UserCancelledException)
{
repository.Network.Remotes.Remove(remote.Name);
//commit is there and we never gc so
repository.Branches.Remove(localBranchName);
repository.Branches.Remove(prBranchName);
cancellationToken.ThrowIfCancellationRequested();
}
cancellationToken.ThrowIfCancellationRequested();
result = repository.Merge(targetCommit, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
result = repository.Merge(targetCommit, sig, new MergeOptions
{
CommitOnSuccess = true,
CommitOnSuccess = commitMessage == null,
FailOnConflict = true,
FastForwardStrategy = FastForwardStrategy.NoFastForward,
SkipReuc = true
@@ -209,6 +193,12 @@ namespace Tgstation.Server.Host.Components.Repository
return false;
}
if (commitMessage != null)
repository.Commit(commitMessage, sig, sig, new CommitOptions
{
PrettifyMessage = true
});
return true;
}
@@ -222,7 +212,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
/// <inheritdoc />
public Task FetchOrigin(string accessString, Action<int> progressReporter, CancellationToken cancellationToken) => Task.WhenAll(
public Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken) => Task.WhenAll(
eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty<string>(), cancellationToken),
Task.Factory.StartNew(() =>
{
@@ -239,7 +229,12 @@ namespace Tgstation.Server.Host.Components.Repository
progressReporter?.Invoke((int)percentage);
return !cancellationToken.IsCancellationRequested;
},
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
{
Username = username,
Password = password
} : new DefaultCredentials()
}, "Fetch origin commits");
}
catch (UserCancelledException)
@@ -248,34 +243,37 @@ namespace Tgstation.Server.Host.Components.Repository
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current));
/// <inheritdoc />
public Task PushHeadToTemporaryBranch(string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
/// <summary>
/// Force push the current repository HEAD to <see cref="Repository.RemoteTemporaryBranchName"/>;
/// </summary>
/// <param name="username">The username to fetch from the origin repository</param>
/// <param name="password">The password to fetch from the origin repository</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task PushHeadToTemporaryBranch(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
var branch = repository.CreateBranch(RemoteTemporaryBranchName);
try
{
cancellationToken.ThrowIfCancellationRequested();
var remote = repository.Network.Remotes.Add("temp_push", GenerateAuthUrl(Origin, accessString));
var remote = repository.Network.Remotes.First();
try
{
cancellationToken.ThrowIfCancellationRequested();
try
repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), new PushOptions
{
repository.Network.Push(remote, String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName), new PushOptions
OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
{
OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested
});
}
catch (UserCancelledException)
{
cancellationToken.ThrowIfCancellationRequested();
}
Username = username,
Password = password
} : new DefaultCredentials()
});
}
finally
catch (UserCancelledException)
{
repository.Network.Remotes.Remove(remote.Name);
cancellationToken.ThrowIfCancellationRequested();
}
}
finally
@@ -291,17 +289,17 @@ namespace Tgstation.Server.Host.Components.Repository
throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!");
var trackedBranch = repository.Head.TrackedBranch;
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
await Task.Factory.StartNew(() =>
{
Commands.Checkout((LibGit2Sharp.Repository)repository, repository.Head.TrackedBranch, new CheckoutOptions
{
CheckoutModifiers = CheckoutModifiers.Force
});
cancellationToken.ThrowIfCancellationRequested();
repository.RemoveUntrackedFiles();
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
await ResetToSha(trackedBranch.Tip.Sha, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public Task ResetToSha(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
repository.Reset(ResetMode.Hard, sha);
cancellationToken.ThrowIfCancellationRequested();
repository.RemoveUntrackedFiles();
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
/// <inheritdoc />
public async Task CopyTo(string path, CancellationToken cancellationToken)
{
@@ -353,40 +351,43 @@ namespace Tgstation.Server.Host.Components.Repository
}
/// <inheritdoc />
public async Task Sychronize(string accessString, CancellationToken cancellationToken)
public async Task Sychronize(string username, string password, bool synchronizeTrackedBranch, CancellationToken cancellationToken)
{
var startHead = Head;
if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List<string> { ioMananger.ResolvePath(".") }, cancellationToken).ConfigureAwait(false))
return;
if (!synchronizeTrackedBranch)
{
await PushHeadToTemporaryBranch(username, password, cancellationToken).ConfigureAwait(false);
return;
}
if (Head == startHead || !repository.Head.IsTracking)
return;
await Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
var remote = repository.Network.Remotes.Add("temp_push", GenerateAuthUrl(Origin, accessString));
var remote = repository.Network.Remotes.First();
try
{
cancellationToken.ThrowIfCancellationRequested();
try
repository.Network.Push(repository.Head, new PushOptions
{
repository.Network.Push(repository.Head, new PushOptions
OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
{
OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested
});
}
catch (UserCancelledException)
{
cancellationToken.ThrowIfCancellationRequested();
}
Username = username,
Password = password
} : new DefaultCredentials()
});
}
finally
catch (UserCancelledException)
{
repository.Network.Remotes.Remove(remote.Name);
cancellationToken.ThrowIfCancellationRequested();
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
}
@@ -49,7 +49,7 @@ namespace Tgstation.Server.Host.Components.Repository
public void Dispose() => semaphore.Dispose();
/// <inheritdoc />
public async Task<IRepository> CloneRepository(Uri url, string initialBranch, string accessString, Action<int> progressReporter, CancellationToken cancellationToken)
public async Task<IRepository> CloneRepository(Uri url, string initialBranch, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
{
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false))
@@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.Components.Repository
string path = null;
try
{
path = LibGit2Sharp.Repository.Clone(Repository.GenerateAuthUrl(url.ToString(), accessString), ioManager.ResolvePath("."), new CloneOptions
path = LibGit2Sharp.Repository.Clone(url.ToString(), ioManager.ResolvePath("."), new CloneOptions
{
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = (a) =>
@@ -72,7 +72,12 @@ namespace Tgstation.Server.Host.Components.Repository
RecurseSubmodules = true,
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
BranchName = initialBranch
BranchName = initialBranch,
CredentialsProvider = (a, b, c) => username != null ? (Credentials)new UsernamePasswordCredentials
{
Username = username,
Password = password
} : new DefaultCredentials()
});
}
catch (UserCancelledException) { }
@@ -158,7 +158,7 @@ namespace Tgstation.Server.Host.Controllers
return;
}
Logger.LogInformation("Request made by User ID {0}. Api version: {1}. User-Agent: {2}", AuthenticationContext.User?.Id.ToString(CultureInfo.InvariantCulture) ?? "NULL", ApiHeaders.ApiVersion, ApiHeaders.UserAgent);
Logger.LogInformation("Request made by User ID {0}. Api version: {1}. User-Agent: {2}", AuthenticationContext?.User.Id.ToString(CultureInfo.InvariantCulture) ?? "NULL", ApiHeaders.ApiVersion, ApiHeaders.UserAgent);
await base.OnActionExecutionAsync(context, next).ConfigureAwait(false);
}
}
@@ -124,7 +124,7 @@ namespace Tgstation.Server.Host.Controllers
Path = model.Path,
RepositorySettings = new RepositorySettings
{
CommitterEmail = "tgstation-server@user.noreply.github.com",
CommitterEmail = "tgstation-server@users.noreply.github.com",
CommitterName = application.VersionString,
PushTestMergeCommits = false,
ShowTestMergeCommitters = true,
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@@ -56,36 +57,41 @@ namespace Tgstation.Server.Host.Controllers
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
}
static string GetAccessString(Api.Models.Internal.RepositorySettings repositorySettings) => repositorySettings.AccessUser != null ? String.Concat(repositorySettings.AccessUser, '@', repositorySettings.AccessToken) : null;
async Task<bool> LoadRevisionInformation(Components.Repository.IRepository repository, string lastOriginCommitSha, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
static async Task<bool> LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
{
var repoSha = repository.Head;
var revisionInfo = await DatabaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha)
IQueryable<Models.RevisionInformation> queryTarget = databaseContext.RevisionInformations;
var revisionInfo = await databaseContext.RevisionInformations.Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
.Include(x => x.CompileJobs)
.Include(x => x.ActiveTestMerges) //minimal info, they can query the rest if they're allowed
.Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge) //minimal info, they can query the rest if they're allowed
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); //search every rev info because LOL SHA COLLISIONS
if (revisionInfo == default)
revisionInfo = databaseContext.RevisionInformations.Local.Where(x => x.CommitSha == repoSha).FirstOrDefault();
var needsDbUpdate = revisionInfo == default;
if (needsDbUpdate)
{
//needs insertion
revisionInfo = new Models.RevisionInformation
{
Instance = instance,
CommitSha = repoSha,
CompileJobs = new List<Models.CompileJob>(),
ActiveTestMerges = new List<RevInfoTestMerge>() //non null vals for api returns
};
lock (DatabaseContext) //cleaner this way
DatabaseContext.RevisionInformations.Add(revisionInfo);
lock (databaseContext) //cleaner this way
databaseContext.RevisionInformations.Add(revisionInfo);
}
revisionInfo.OriginCommitSha = revisionInfo.OriginCommitSha ?? lastOriginCommitSha ?? repository.Head;
revInfoSink?.Invoke(revisionInfo);
return needsDbUpdate;
}
async Task<bool> PopulateApi(Repository model, Components.Repository.IRepository repository, string lastOriginCommitSha, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
static async Task<bool> PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
{
model.IsGitHub = repository.IsGitHubRepository;
model.Origin = repository.Origin;
@@ -93,7 +99,7 @@ namespace Tgstation.Server.Host.Controllers
//rev info stuff
Models.RevisionInformation revisionInfo = null;
var needsDbUpdate = await LoadRevisionInformation(repository, lastOriginCommitSha, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, lastOriginCommitSha, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
model.RevisionInformation = revisionInfo.ToApi();
revInfoSink?.Invoke(revisionInfo);
return needsDbUpdate;
@@ -149,11 +155,11 @@ namespace Tgstation.Server.Host.Controllers
var api = currentModel.ToApi();
await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) =>
{
using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, GetAccessString(currentModel), progressReporter, cancellationToken).ConfigureAwait(false))
using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, cancellationToken).ConfigureAwait(false))
{
if (repos == null)
throw new Exception("Filesystem conflict while cloning repository!");
await PopulateApi(api, repo, null, null, cancellationToken).ConfigureAwait(false);
await PopulateApi(api, repo, DatabaseContext, Instance, null, null, cancellationToken).ConfigureAwait(false);
}
}, cancellationToken).ConfigureAwait(false);
@@ -211,7 +217,7 @@ namespace Tgstation.Server.Host.Controllers
using (var repo = await instanceManager.GetInstance(Instance).RepositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
{
if (repo != null && await PopulateApi(api, repo, null, null, cancellationToken).ConfigureAwait(false))
if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, null, null, cancellationToken).ConfigureAwait(false))
//user may have fucked with the repo without telling us, do what we can
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
return Json(api);
@@ -273,8 +279,15 @@ namespace Tgstation.Server.Host.Controllers
|| (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch)))
return Forbid();
//no, just one save changes at the end for sanity
//await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
if (currentModel.AccessToken.Length == 0 || currentModel.AccessUser.Length == 0)
{
//setting an empty string clears everything
currentModel.AccessUser = null;
currentModel.AccessToken = null;
}
//this is just db stuf so stow it away
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
var job = new Models.Job
{
@@ -294,13 +307,13 @@ namespace Tgstation.Server.Host.Controllers
var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
var startReference = repo.Reference;
var startSha = repo.Head;
if (newTestMerges && !repo.IsGitHubRepository)
throw new InvalidOperationException("Cannot test merge on a non GitHub based repository!");
var committerName = currentModel.ShowTestMergeCommitters.Value ? AuthenticationContext.User.Name : currentModel.CommitterName;
var accessString = GetAccessString(currentModel);
var numFetches = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0);
var doneFetches = 0;
@@ -309,13 +322,21 @@ namespace Tgstation.Server.Host.Controllers
//get a base line for where we are
Models.RevisionInformation lastRevisionInfo = null;
await LoadRevisionInformation(repo, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false);
var databaseContext = serviceProvider.GetRequiredService<IDatabaseContext>();
var attachedInstance = new Models.Instance
{
Id = Instance.Id
};
databaseContext.Instances.Attach(attachedInstance);
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false);
//apply new rev info, tracking applied test merges
async Task UpdateRevInfo()
{
var last = lastRevisionInfo;
await LoadRevisionInformation(repo, last.OriginCommitSha, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false);
await LoadRevisionInformation(repo, databaseContext, attachedInstance, last.OriginCommitSha, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false);
lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges);
};
@@ -326,7 +347,7 @@ namespace Tgstation.Server.Host.Controllers
{
if (!repo.Tracking && model.Reference == null)
throw new InvalidOperationException("Not on an updatable reference!");
await repo.FetchOrigin(accessString, x => progressReporter(x / numFetches), cancellationToken).ConfigureAwait(false);
await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, x => progressReporter(x / numFetches), cancellationToken).ConfigureAwait(false);
doneFetches = 1;
if (!modelHasShaOrReference)
{
@@ -338,7 +359,7 @@ namespace Tgstation.Server.Host.Controllers
lastRevisionInfo.OriginCommitSha = repo.Head;
}
}
//checkout/hard reset
if (modelHasShaOrReference)
{
@@ -346,7 +367,7 @@ namespace Tgstation.Server.Host.Controllers
|| (model.Reference != null && repo.Reference != model.Reference))
{
await repo.CheckoutObject(model.CheckoutSha ?? model.Reference, cancellationToken).ConfigureAwait(false);
await LoadRevisionInformation(repo, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false); //we've either seen origin before or what we're checking out is on origin
}
if (model.UpdateFromOrigin == true && model.Reference != null)
@@ -354,7 +375,8 @@ namespace Tgstation.Server.Host.Controllers
if (!repo.Tracking)
throw new InvalidOperationException("Checked out reference does not track a remote object!");
await repo.ResetToOrigin(cancellationToken).ConfigureAwait(false);
await LoadRevisionInformation(repo, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false);
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, true, cancellationToken).ConfigureAwait(false);
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, cancellationToken).ConfigureAwait(false);
//repo head is on origin so force this
//will update the db if necessary
lastRevisionInfo.OriginCommitSha = repo.Head;
@@ -364,26 +386,21 @@ namespace Tgstation.Server.Host.Controllers
//test merging
if (newTestMerges)
{
var contextUser = new Models.User
{
Id = AuthenticationContext.User.Id
};
databaseContext.Users.Attach(contextUser);
var repoOwner = repo.GitHubOwner;
var repoName = repo.GitHubRepoName;
foreach (var I in model.NewTestMerges)
{
var prTask = gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number);
var mergeResult = await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, accessString, x => progressReporter((x + 100 * doneFetches) / numFetches), cancellationToken).ConfigureAwait(false);
if (!mergeResult.HasValue) //conflict, we don't care, dd already knows
continue;
++doneFetches;
var revInfoUpdateTask = UpdateRevInfo();
Octokit.PullRequest pr = null;
string errorMessage = null;
try
{
pr = await prTask.ConfigureAwait(false);
pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
}
catch (Octokit.RateLimitExceededException)
{
@@ -396,20 +413,27 @@ namespace Tgstation.Server.Host.Controllers
errorMessage = "P.R.E. NOT FOUND";
}
var mergeResult = await repo.AddTestMerge(I.Number, I.PullRequestRevision, committerName, currentModel.CommitterEmail, String.Format(CultureInfo.InvariantCulture, "Test merge of pull request #{0}{1}{2}", I.Number, I.Comment != null ? Environment.NewLine : null, I.Comment), currentModel.AccessUser, currentModel.AccessToken, x => progressReporter((x + 100 * doneFetches) / numFetches), cancellationToken).ConfigureAwait(false);
if (!mergeResult.HasValue) //conflict, we don't care, dd already knows
continue;
++doneFetches;
var revInfoUpdateTask = UpdateRevInfo();
var tm = new Models.TestMerge
{
Author = pr?.User.Login ?? errorMessage,
BodyAtMerge = pr?.Body,
BodyAtMerge = pr?.Body ?? errorMessage ?? String.Empty,
MergedAt = DateTimeOffset.Now,
TitleAtMerge = pr?.Title ?? errorMessage,
TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty,
Comment = I.Comment,
Number = I.Number,
MergedBy = AuthenticationContext.User,
MergedBy = contextUser,
PullRequestRevision = I.PullRequestRevision,
Url = pr?.HtmlUrl ?? errorMessage
};
if (pr == null)
tm.BodyAtMerge = errorMessage;
await revInfoUpdateTask.ConfigureAwait(false);
@@ -423,15 +447,17 @@ namespace Tgstation.Server.Host.Controllers
if (startSha != repo.Head)
{
await repo.Sychronize(accessString, ct).ConfigureAwait(false);
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, false, ct).ConfigureAwait(false);
await UpdateRevInfo().ConfigureAwait(false);
}
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
}
catch
{
//the stuff didn't make it into the db, forget what we've done and abort
await repo.CheckoutObject(startSha, default).ConfigureAwait(false);
await repo.CheckoutObject(startReference ?? startSha, default).ConfigureAwait(false);
if (startReference != null && repo.Head != startSha)
await repo.ResetToSha(startSha, default).ConfigureAwait(false);
throw;
}
}
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel.DataAnnotations;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Models
@@ -21,19 +22,19 @@ namespace Tgstation.Server.Host.Models
/// </summary>
[Required]
public Instance Instance { get; set; }
/// <inheritdoc />
public Repository ToApi() => new Repository {
public Repository ToApi() => new Repository
{
//AccessToken = AccessToken, //never show this
AccessUser = AccessUser,
AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges,
AutoUpdatesSynchronize = AutoUpdatesSynchronize,
CommitterEmail = CommitterEmail,
CommitterName = CommitterName,
//intentionally don't populate origin just in case
PushTestMergeCommits = PushTestMergeCommits,
//revision information and the rest retrieved by controller
ShowTestMergeCommitters = ShowTestMergeCommitters
//revision information and the rest retrieved by controller
};
}
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Tgstation.Server.Host.Models
@@ -14,6 +15,7 @@ namespace Tgstation.Server.Host.Models
/// <summary>
/// The <see cref="Models.Instance"/> the <see cref="RevisionInformation"/> belongs to
/// </summary>
[Required]
public Instance Instance { get; set; }
/// <summary>
@@ -1,8 +1,6 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.DirectoryServices.AccountManagement;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
+138 -2
View File
@@ -1658,7 +1658,93 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"updateReference\": true\n}"
"raw": "{\n\t\"updateFromOrigin\": true\n}"
},
"url": {
"raw": "localhost:5000/Repository",
"host": [
"localhost"
],
"port": "5000",
"path": [
"Repository"
]
}
},
"response": []
},
{
"name": "Set access info",
"request": {
"method": "POST",
"header": [
{
"key": "Accept",
"value": "application/json"
},
{
"key": "User-Agent",
"value": "Postman/1.0"
},
{
"key": "User-Agent",
"value": "Tgstation.Server.Api/4.0.0.0"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "InstanceId",
"value": "1"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"accessUser\": \"Cyberboss\",\n\t\"accessToken\": \"this can be a password or personal access token, you should use the latter\"\n}"
},
"url": {
"raw": "localhost:5000/Repository",
"host": [
"localhost"
],
"port": "5000",
"path": [
"Repository"
]
}
},
"response": []
},
{
"name": "Reset to origin",
"request": {
"method": "POST",
"header": [
{
"key": "Accept",
"value": "application/json"
},
{
"key": "User-Agent",
"value": "Postman/1.0"
},
{
"key": "User-Agent",
"value": "Tgstation.Server.Api/4.0.0.0"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "InstanceId",
"value": "1"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"updateFromOrigin\": true,\n\t\"reference\": \"master\"\n}"
},
"url": {
"raw": "localhost:5000/Repository",
@@ -1701,7 +1787,7 @@
],
"body": {
"mode": "raw",
"raw": "{\n\t\"updateReference\": true,\n\t\"reference\": \"master\",\n\t\"newTestMerges\": [\n\t\t{\n\t\t\t\"number\": 39476,\n\t\t\t\"pullRequestRevision\": \"11edfe5\"\n\t\t},\n\t\t{\n\t\t\t\"number\": 39469,\n\t\t\t\"pullRequestRevision\": \"ee4f00d\",\n\t\t\t\"comment\": \"babby's first pr\"\n\t\t}\n\t\t]\n}"
"raw": "{\n\t\"updateFromOrigin\": true,\n\t\"reference\": \"master\",\n\t\"newTestMerges\": [\n\t\t{\n\t\t\t\"number\": 39514,\n\t\t\t\"pullRequestRevision\": \"28c6762\",\n\t\t\t\"comment\": \"babby's first pr\"\n\t\t},\n\t\t{\n\t\t\t\"number\": 39512,\n\t\t\t\"pullRequestRevision\": \"09be343791fd233444fb3985ef54496e277397f7\",\n\t\t\t\"comment\": \"gud boi antur\"\n\t\t}\n\t\t]\n}"
},
"url": {
"raw": "localhost:5000/Repository",
@@ -1805,6 +1891,56 @@
],
"_postman_isSubFolder": true
},
{
"name": "Compiler",
"description": "",
"item": [
{
"name": "Read Info Copy",
"request": {
"method": "GET",
"header": [
{
"key": "Accept",
"value": "application/json"
},
{
"key": "User-Agent",
"value": "Postman/1.0"
},
{
"key": "User-Agent",
"value": "Tgstation.Server.Api/4.0.0.0"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "InstanceId",
"value": "1"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"id\": 1,\n \"online\": false\n}"
},
"url": {
"raw": "localhost:5000/Repository",
"host": [
"localhost"
],
"port": "5000",
"path": [
"Repository"
]
}
},
"response": []
}
],
"_postman_isSubFolder": true
},
{
"name": "Online Instance ID 1",
"request": {