diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs
index c5ace7499c..14dac247ab 100644
--- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs
@@ -48,5 +48,19 @@ namespace Tgstation.Server.Api.Models.Internal
[Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)]
[Required]
public bool? ShowTestMergeCommitters { get; set; }
+
+ ///
+ /// If test merge commits should be kept when auto updating. May cause merge conflicts which will block the update
+ ///
+ [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)]
+ [Required]
+ public bool? AutoUpdatesKeepTestMerges { get; set; }
+
+ ///
+ /// If synchronization should occur when auto updating
+ ///
+ [Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)]
+ [Required]
+ public bool? AutoUpdatesSynchronize { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs
index 23703fd446..faf745fa79 100644
--- a/src/Tgstation.Server.Api/Rights/RepositoryRights.cs
+++ b/src/Tgstation.Server.Api/Rights/RepositoryRights.cs
@@ -48,5 +48,9 @@ namespace Tgstation.Server.Api.Rights
/// User may read all fields in the with the exception of
///
Read = 512,
+ ///
+ /// User may change and
+ ///
+ ChangeAutoUpdateSettings = 1024
}
}
diff --git a/src/Tgstation.Server.Host/Components/EventType.cs b/src/Tgstation.Server.Host/Components/EventType.cs
index 615c7ed3bb..e9442b0d10 100644
--- a/src/Tgstation.Server.Host/Components/EventType.cs
+++ b/src/Tgstation.Server.Host/Components/EventType.cs
@@ -10,7 +10,7 @@
///
RepoResetOrigin = 0,
///
- /// Parameters: Reference name, commit sha
+ /// Parameters: Checkout target
///
RepoCheckout = 1,
///
@@ -18,18 +18,18 @@
///
RepoFetch = 2,
///
- /// Parameters: Comma separated list in form of "#{Pull Request Number} @ {7 character SHA}
+ /// Parameters: Pull request number, pull request sha, merger name, merger message
///
- RepoMergePullRequests = 3,
+ RepoMergePullRequest = 3,
+ ///
+ /// Parameters: Absolute path to repository root, committer name, committer email
+ ///
+ RepoPreSynchronize = 4,
///
/// Parameters: Current version, new version
///
- ByondChangeStart = 4,
- ///
- /// No parameters
- ///
- ByondChangeCancelled = 5,
+ ByondChangeStart = 5,
///
/// Parameters: Error string
///
@@ -37,36 +37,42 @@
///
/// No parameters
///
- ByondStageComplete = 7,
- ///
- /// No parameters
- ///
- ByondChangeComplete = 8,
+ ByondChangeComplete = 7,
///
/// Parameters: Origin commit sha
///
- CompileStart = 9,
+ CompileStart = 8,
///
/// No parameters
///
- CompileCancelled = 10,
+ CompileCancelled = 9,
///
/// Parameters: "1" if compile succeeded and api validation failed, "0" otherwise
///
- CompileFailure = 11,
+ CompileFailure = 10,
///
/// No parameters
///
- CompileComplete = 12,
+ CompileComplete = 11,
///
/// Parameters: Exit code
///
- DDOtherCrash = 13,
+ DDOtherCrash = 12,
///
/// No parameters
///
- DDOtherExit = 14,
+ DDOtherExit = 13,
+
+ ///
+ /// No parameters
+ ///
+ InstanceAutoUpdateStart = 14,
+
+ ///
+ /// Parameters: Base sha, target sha, base reference, target reference
+ ///
+ RepoMergeConflict = 15,
}
}
diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs
index d134a34639..10412a6458 100644
--- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs
@@ -44,11 +44,11 @@ namespace Tgstation.Server.Host.Components.Repository
/// The commit in the pull request to merge
/// The name of the merge committer
/// The e-mail of the merge committer
- /// The body of the commit message
/// The access string to fetch from the origin repository
/// The for the operation
+ /// A string to identify the user that merged
/// A resulting in the SHA of the new HEAD on success, on merge conflict
- Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessString, CancellationToken cancellationToken);
+ Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken);
///
/// Fetch commits from the origin repository
@@ -68,9 +68,11 @@ namespace Tgstation.Server.Host.Components.Repository
///
/// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository
///
+ /// The name of the merge committer
+ /// The e-mail of the merge committer
/// The for the operation
/// A resulting in the SHA of the new HEAD. if the merge resulted in conflict
- Task MergeOrigin(CancellationToken cancellationToken);
+ Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken);
///
/// Force push the current repository HEAD to ;
diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
index 52565c0a34..3e26156303 100644
--- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
@@ -12,6 +12,8 @@ namespace Tgstation.Server.Host.Components.Repository
///
sealed class Repository : IRepository
{
+ const string UnknownReference = "";
+
///
/// The branch name used for publishing testmerge commits
///
@@ -39,6 +41,11 @@ namespace Tgstation.Server.Host.Components.Repository
///
readonly IIOManager ioMananger;
+ ///
+ /// The for the
+ ///
+ readonly IEventConsumer eventConsumer;
+
///
/// to be taken when is called
///
@@ -49,11 +56,13 @@ namespace Tgstation.Server.Host.Components.Repository
///
/// The value of
/// The value of
+ /// The value of
/// The value if
- public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, Action onDispose)
+ public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, Action onDispose)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger));
+ this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
IsGitHubRepository = Origin.ToUpperInvariant().Contains("://GITHUB.COM/");
}
@@ -97,79 +106,104 @@ namespace Tgstation.Server.Host.Components.Repository
}
///
- public Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string commitBody, string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
+ public async Task AddTestMerge(int pullRequestNumber, string targetCommit, string committerName, string committerEmail, string accessString, string mergerIdentifier, CancellationToken cancellationToken)
{
+
+ if (!IsGitHubRepository)
+ throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
+
var Refspec = new List();
var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", pullRequestNumber);
var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", pullRequestNumber, prBranchName);
Refspec.Add(String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", pullRequestNumber, prBranchName));
var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", pullRequestNumber);
- var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(Origin, accessString));
- try
- {
- cancellationToken.ThrowIfCancellationRequested();
- Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions
- {
- Prune = true,
- OnProgress = (a) => !cancellationToken.IsCancellationRequested,
- OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested,
- OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested
- }, logMessage);
- }
- catch (UserCancelledException) { }
- finally
- {
- repository.Network.Remotes.Remove(remote.Name);
- //commit is there and we never gc so
- repository.Branches.Remove(localBranchName);
- repository.Branches.Remove(prBranchName);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
var originalCommit = repository.Head;
- var result = repository.Merge(targetCommit, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
+ MergeResult result = null;
+ await Task.Factory.StartNew(() =>
{
- CommitOnSuccess = true,
- FailOnConflict = true,
- FastForwardStrategy = FastForwardStrategy.NoFastForward,
- SkipReuc = true,
- });
+ var remote = repository.Network.Remotes.Add("temp_pr_fetch", GenerateAuthUrl(Origin, accessString));
+ try
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, Refspec, new FetchOptions
+ {
+ Prune = true,
+ OnProgress = (a) => !cancellationToken.IsCancellationRequested,
+ OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested,
+ OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested
+ }, logMessage);
+ }
+ catch (UserCancelledException) { }
+ finally
+ {
+ repository.Network.Remotes.Remove(remote.Name);
+ //commit is there and we never gc so
+ repository.Branches.Remove(localBranchName);
+ repository.Branches.Remove(prBranchName);
+ }
- if (result.Status != MergeStatus.NonFastForward)
+ cancellationToken.ThrowIfCancellationRequested();
+
+ result = repository.Merge(targetCommit, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
+ {
+ CommitOnSuccess = true,
+ FailOnConflict = true,
+ FastForwardStrategy = FastForwardStrategy.NoFastForward,
+ SkipReuc = true
+ });
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (result.Status == MergeStatus.Conflicts)
+ {
+ RawCheckout(originalCommit.CanonicalName ?? originalCommit.Tip.Sha);
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+
+ repository.RemoveUntrackedFiles();
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
+
+ if (result.Status == MergeStatus.Conflicts)
{
- RawCheckout(originalCommit.FriendlyName ?? originalCommit.Tip.Sha);
+ await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { originalCommit.Tip.Sha, targetCommit, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false);
return null;
}
return result.Commit.Sha;
-
- }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
+ }
///
- public Task CheckoutObject(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() => RawCheckout(committish), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
-
- ///
- public Task FetchOrigin(string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
+ public async Task CheckoutObject(string committish, CancellationToken cancellationToken)
{
- var remote = repository.Network.Remotes.First();
- try
+ if (committish == null)
+ throw new ArgumentNullException(nameof(committish));
+ await eventConsumer.HandleEvent(EventType.RepoCheckout, new List { committish }, cancellationToken).ConfigureAwait(false);
+ await Task.Factory.StartNew(() => RawCheckout(committish), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
+ }
+
+ ///
+ public Task FetchOrigin(string accessString, CancellationToken cancellationToken) => Task.WhenAll(
+ eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty(), cancellationToken),
+ Task.Factory.StartNew(() =>
{
- Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions
+ var remote = repository.Network.Remotes.First();
+ try
{
- Prune = true,
- OnProgress = (a) => !cancellationToken.IsCancellationRequested,
- OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested,
- OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested
- }, "Fetch origin commits");
- }
- catch (UserCancelledException)
- {
- cancellationToken.ThrowIfCancellationRequested();
- }
- }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
+ Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions
+ {
+ Prune = true,
+ OnProgress = (a) => !cancellationToken.IsCancellationRequested,
+ OnTransferProgress = (a) => !cancellationToken.IsCancellationRequested,
+ OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested
+ }, "Fetch origin commits");
+ }
+ catch (UserCancelledException)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current));
///
public Task PushHeadToTemporaryBranch(string accessString, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
@@ -205,22 +239,26 @@ namespace Tgstation.Server.Host.Components.Repository
{
repository.Branches.Remove(branch);
}
-
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
///
- public Task ResetToOrigin(CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
+ public async Task ResetToOrigin(CancellationToken cancellationToken)
{
if (!repository.Head.IsTracking)
throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!");
var trackedBranch = repository.Head.TrackedBranch;
- Commands.Checkout((LibGit2Sharp.Repository)repository, repository.Head.TrackedBranch, new CheckoutOptions
+ await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
+ return await Task.Factory.StartNew(() =>
{
- CheckoutModifiers = CheckoutModifiers.Force
- });
- repository.RemoveUntrackedFiles();
- return trackedBranch.Tip.Sha;
- }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
+ Commands.Checkout((LibGit2Sharp.Repository)repository, repository.Head.TrackedBranch, new CheckoutOptions
+ {
+ CheckoutModifiers = CheckoutModifiers.Force
+ });
+ cancellationToken.ThrowIfCancellationRequested();
+ repository.RemoveUntrackedFiles();
+ return trackedBranch.Tip.Sha;
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
+ }
///
public async Task CopyTo(string path, CancellationToken cancellationToken)
@@ -231,15 +269,84 @@ namespace Tgstation.Server.Host.Components.Repository
}
///
- public Task MergeOrigin(CancellationToken cancellationToken)
+ public async Task MergeOrigin(string committerName, string committerEmail, CancellationToken cancellationToken)
{
- throw new NotImplementedException();
+ MergeResult result = null;
+ Branch trackedBranch = null;
+
+ var oldHead = repository.Head;
+
+ await Task.Factory.StartNew(() =>
+ {
+ if (!repository.Head.IsTracking)
+ throw new InvalidOperationException("Cannot reset to origin while not on a tracked reference!");
+ trackedBranch = repository.Head.TrackedBranch;
+
+ result = repository.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
+ {
+ CommitOnSuccess = true,
+ FailOnConflict = true,
+ FastForwardStrategy = FastForwardStrategy.Default,
+ SkipReuc = true,
+ });
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (result.Status == MergeStatus.Conflicts)
+ {
+ RawCheckout(oldHead.CanonicalName);
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+
+ repository.RemoveUntrackedFiles();
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
+
+ if (result.Status == MergeStatus.Conflicts)
+ {
+ await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List { oldHead.Tip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false);
+ return null;
+ }
+
+ return Head;
}
///
- public Task Sychronize(string accessString, CancellationToken cancellationToken)
+ public async Task Sychronize(string accessString, CancellationToken cancellationToken)
{
- throw new NotImplementedException();
+ var startHead = Head;
+
+ if (!await eventConsumer.HandleEvent(EventType.RepoPreSynchronize, new List { ioMananger.ResolvePath(".") }, 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));
+ try
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ try
+ {
+ repository.Network.Push(repository.Head, new PushOptions
+ {
+ OnPackBuilderProgress = (a, b, c) => !cancellationToken.IsCancellationRequested,
+ OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
+ OnPushTransferProgress = (a, b, c) => !cancellationToken.IsCancellationRequested
+ });
+ }
+ catch (UserCancelledException)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ }
+ finally
+ {
+ repository.Network.Remotes.Remove(remote.Name);
+ }
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
}
}
}