diff --git a/TGCommandLine/Properties/AssemblyInfo.cs b/TGCommandLine/Properties/AssemblyInfo.cs index 4b2ae228ae..27af13774e 100644 --- a/TGCommandLine/Properties/AssemblyInfo.cs +++ b/TGCommandLine/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -14,3 +15,6 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("9ad1f086-a83e-4d14-a844-58a9471106b6")] + +//allow the unit tester to peek inside us +[assembly: InternalsVisibleTo("TGServiceTests", AllInternalsVisible = true)] diff --git a/TGCommandLine/RepoCommands.cs b/TGCommandLine/RepoCommands.cs index 8847452874..9e3fe3e658 100644 --- a/TGCommandLine/RepoCommands.cs +++ b/TGCommandLine/RepoCommands.cs @@ -10,7 +10,7 @@ namespace TGCommandLine public RepoCommand() { Keyword = "repo"; - Children = new Command[] { new RepoSetupCommand(), new RepoUpdateCommand(), new RepoGenChangelogCommand(), new RepoPushChangelogCommand(), new RepoSetEmailCommand(), new RepoSetNameCommand(), new RepoMergePRCommand(), new RepoListPRsCommand(), new RepoStatusCommand(), new RepoListBackupsCommand(), new RepoCheckoutCommand(), new RepoResetCommand(), new RepoUpdateJsonCommand() }; + Children = new Command[] { new RepoSetupCommand(), new RepoUpdateCommand(), new RepoGenChangelogCommand(), new RepoPushChangelogCommand(), new RepoSetEmailCommand(), new RepoSetNameCommand(), new RepoMergePRCommand(), new RepoListPRsCommand(), new RepoStatusCommand(), new RepoListBackupsCommand(), new RepoCheckoutCommand(), new RepoResetCommand(), new RepoUpdateJsonCommand(), new RepoSetPushTestmergeCommitsCommand() }; } public override string GetHelpText() { @@ -18,6 +18,41 @@ namespace TGCommandLine } } + class RepoSetPushTestmergeCommitsCommand : ConsoleCommand + { + public RepoSetPushTestmergeCommitsCommand() + { + Keyword = "push-testmerges"; + RequiredParameters = 1; + } + public override string GetHelpText() + { + return "Set if a temporary branch is to the remote when we make testmerge commits and then delete it"; + } + + public override string GetArgumentString() + { + return ""; + } + + protected override ExitCode Run(IList parameters) + { + switch (parameters[0].ToLower()) + { + case "on": + Interface.GetComponent().SetPushTestmergeCommits(true); + break; + case "off": + Interface.GetComponent().SetPushTestmergeCommits(false); + break; + default: + OutputProc("Invalid option!"); + return ExitCode.BadCommand; + } + return ExitCode.Normal; + } + } + class RepoUpdateJsonCommand : ConsoleCommand { public RepoUpdateJsonCommand() @@ -98,6 +133,7 @@ namespace TGCommandLine OutputProc("Remote: " + remote + " (" + remotehead + ")"); OutputProc("Branch: " + branch); OutputProc("HEAD: " + head); + OutputProc("Push testmerge commits: " + (Repo.PushTestmergeCommits() ? "ON" : "OFF")); OutputProc(String.Format("Committer Identity: {0} ({1})", Repo.GetCommitterName(), Repo.GetCommitterEmail())); } else diff --git a/TGControlPanel/ControlPanel/ControlPanel.Designer.cs b/TGControlPanel/ControlPanel/ControlPanel.Designer.cs index 198c3edfba..766093f7f5 100644 --- a/TGControlPanel/ControlPanel/ControlPanel.Designer.cs +++ b/TGControlPanel/ControlPanel/ControlPanel.Designer.cs @@ -156,6 +156,7 @@ this.StaticFileCreateButton = new System.Windows.Forms.Button(); this.label6 = new System.Windows.Forms.Label(); this.StaticFileListBox = new System.Windows.Forms.ListBox(); + this.SyncCommitsCheckBox = new System.Windows.Forms.CheckBox(); this.ChatPanel.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ChatPortSelector)).BeginInit(); this.ChatProviderSelectorPanel.SuspendLayout(); @@ -1308,6 +1309,7 @@ // RepoPanel // this.RepoPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.RepoPanel.Controls.Add(this.SyncCommitsCheckBox); this.RepoPanel.Controls.Add(this.TGSJsonUpdate); this.RepoPanel.Controls.Add(this.RepoRefreshButton); this.RepoPanel.Controls.Add(this.BackupTagsList); @@ -1765,6 +1767,20 @@ this.StaticFileListBox.TabIndex = 0; this.StaticFileListBox.SelectedIndexChanged += new System.EventHandler(this.StaticFileListBox_SelectedIndexChanged); // + // SyncCommitsCheckBox + // + this.SyncCommitsCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.SyncCommitsCheckBox.AutoSize = true; + this.SyncCommitsCheckBox.Font = new System.Drawing.Font("Verdana", 12F); + this.SyncCommitsCheckBox.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.SyncCommitsCheckBox.Location = new System.Drawing.Point(574, 16); + this.SyncCommitsCheckBox.Name = "SyncCommitsCheckBox"; + this.SyncCommitsCheckBox.Size = new System.Drawing.Size(142, 22); + this.SyncCommitsCheckBox.TabIndex = 46; + this.SyncCommitsCheckBox.Text = "Sync Commits"; + this.SyncCommitsCheckBox.UseVisualStyleBackColor = true; + this.SyncCommitsCheckBox.Visible = false; + // // Main // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1929,5 +1945,6 @@ private System.Windows.Forms.CheckBox AutoUpdateCheckbox; private System.Windows.Forms.Label AutoUpdateMLabel; private System.Windows.Forms.TextBox projectNameText; + private System.Windows.Forms.CheckBox SyncCommitsCheckBox; } } diff --git a/TGControlPanel/ControlPanel/RepoPage.cs b/TGControlPanel/ControlPanel/RepoPage.cs index bdae725354..fd4d775e9a 100644 --- a/TGControlPanel/ControlPanel/RepoPage.cs +++ b/TGControlPanel/ControlPanel/RepoPage.cs @@ -86,6 +86,8 @@ namespace TGControlPanel BranchNameTitle.Visible = true; RepoBranchTextBox.Visible = true; RepoRefreshButton.Visible = true; + SyncCommitsCheckBox.Visible = true; + SyncCommitsCheckBox.Checked = Repo.PushTestmergeCommits(); if (!Repo.Exists()) { @@ -233,6 +235,7 @@ namespace TGControlPanel if (RepoBGW.IsBusy || (ra != RepoAction.Wait && RepoBusyCheck())) return; + SyncCommitsCheckBox.Visible = false; CurrentRevisionLabel.Visible = false; CurrentRevisionTitle.Visible = false; TestMergeListLabel.Visible = false; @@ -292,6 +295,7 @@ namespace TGControlPanel if (!Reclone) { + Repo.SetPushTestmergeCommits(SyncCommitsCheckBox.Checked); var branch = Repo.GetBranch(out error); if(branch == null) { diff --git a/TGControlPanel/ControlPanel/ServerPage.cs b/TGControlPanel/ControlPanel/ServerPage.cs index 9491b8db2a..895f3d87ac 100644 --- a/TGControlPanel/ControlPanel/ServerPage.cs +++ b/TGControlPanel/ControlPanel/ServerPage.cs @@ -93,6 +93,7 @@ namespace TGControlPanel WorldAnnounceField.Visible = RepoExists; WorldAnnounceButton.Visible = RepoExists; WorldAnnounceLabel.Visible = RepoExists; + SyncCommitsCheckBox.Visible = RepoExists; if (updatingFields) return; diff --git a/TGServerService/EventID.cs b/TGServerService/EventID.cs index c36abca48d..1c4f893bdc 100644 --- a/TGServerService/EventID.cs +++ b/TGServerService/EventID.cs @@ -326,5 +326,10 @@ namespace TGServerService /// Info: When an instance's logging ID is first assigned /// InstanceIDAssigned = 7600, + /// + /// Info: When a testmerge commit is published + /// Warning: When a testmerge commit failed to be published + /// + ReferencePush = 7700, } } diff --git a/TGServerService/InstanceConfig.cs b/TGServerService/InstanceConfig.cs index 7a4892e687..a7afc8ab5f 100644 --- a/TGServerService/InstanceConfig.cs +++ b/TGServerService/InstanceConfig.cs @@ -7,7 +7,7 @@ namespace TGServerService /// /// Configuration settings for a /// - interface IInstanceConfig + public interface IInstanceConfig { /// /// The directory this is for @@ -107,6 +107,12 @@ namespace TGServerService /// The auto update interval for the /// ulong AutoUpdateInterval { get; set; } + + /// + /// Whether or not testmerge commits are published to a temporary remote branch + /// + bool PushTestmergeCommits { get; set; } + /// /// Saves the to it's /// @@ -189,6 +195,9 @@ namespace TGServerService /// public ulong AutoUpdateInterval { get; set; } = 0; + /// + public bool PushTestmergeCommits { get; set; } = false; + /// /// Construct a for a at /// diff --git a/TGServerService/ServerInstance/Repository.cs b/TGServerService/ServerInstance/Repository.cs index b39e697567..682b239346 100644 --- a/TGServerService/ServerInstance/Repository.cs +++ b/TGServerService/ServerInstance/Repository.cs @@ -19,6 +19,10 @@ namespace TGServerService /// const string RepoPath = "Repository"; /// + /// The branch name used for publishing testmerge commits + /// + const string RemoteTempBranchName = "___TGS3TempBranch"; + /// /// The path to the Repository's json /// const string RepoTGS3SettingsPath = RepoPath + "/TGS3.json"; @@ -542,14 +546,21 @@ namespace TGServerService /// Merges given into the current branch /// /// The sha/branch/tag to merge + /// The commit message for the merge commit /// on success, error message on failure - string MergeBranch(string committish) + string MergeBranch(string committish, string mergeMessage) { var mo = new MergeOptions() { OnCheckoutProgress = HandleCheckoutProgress }; - var Result = Repo.Merge(committish, MakeSig()); + if (mergeMessage != null) + { + mo.CommitOnSuccess = false; + mo.FastForwardStrategy = FastForwardStrategy.NoFastForward; + } + var sig = MakeSig(); + var Result = Repo.Merge(committish, sig, mo); currentProgress = -1; switch (Result.Status) { @@ -560,9 +571,58 @@ namespace TGServerService case MergeStatus.UpToDate: return RepoErrorUpToDate; } + if(mergeMessage != null) + Repo.Commit(mergeMessage, sig, sig); return null; } + void PushTestmergeCommit() + { + if (Config.PushTestmergeCommits && SSHAuth()) + { + string NewB = null; + var targetRemote = Repo.Network.Remotes[SSHPushRemote]; + var options = new PushOptions() + { + CredentialsProvider = GenerateGitCredentials + }; + try + { + //now try and push the commit to the remote so they can be referenced + NewB = Repo.CreateBranch(RemoteTempBranchName).CanonicalName; + Repo.Network.Push(targetRemote, NewB, options); //push the branch + Repo.Branches.Remove(NewB); + var removalString = String.Format(":{0}", NewB); + NewB = null; + //we need to delay the second operation a LOT otherwise we get ssh errors + Thread.Sleep(10000); + Repo.Network.Push(targetRemote, removalString, options); //delete the branch + WriteInfo("Pushed reference commit: " + Repo.Head.Tip.Sha, EventID.ReferencePush); + } + catch (Exception e) + { + WriteWarning(String.Format("Failed to push reference commit: {0}. Error: {1}", Repo.Head.Tip.Sha, e.ToString()), EventID.ReferencePush); + } + finally + { + if (NewB != null) + { + //Try to delete the branches regardless + try + { + Repo.Branches.Remove(NewB); + } + catch { } + try + { + Repo.Network.Push(targetRemote, String.Format(":{0}", NewB), options); + } + catch { } + } + } + } + } + /// public string Update(bool reset) { @@ -607,7 +667,9 @@ namespace TGServerService WriteInfo("Repo hard updated to " + originBranch.Tip.Sha, EventID.RepoHardUpdate); return error; } - res = MergeBranch(originBranch.FriendlyName); + res = MergeBranch(originBranch.FriendlyName, "Merge origin into current testmerge"); + if (!LocalIsRemote()) //might be fast forward + PushTestmergeCommit(); if (res != null) throw new Exception(res); UpdateSubmodules(); @@ -844,7 +906,7 @@ namespace TGServerService } //so we'll know if this fails - var Result = MergeBranch(LocalBranchName); + var Result = MergeBranch(LocalBranchName, String.Format("Testmerge commit for pull request #{0}", PRNumber)); if (Result == null) try @@ -894,6 +956,8 @@ namespace TGServerService WriteError("Failed to update PR list", EventID.RepoPRListError); return "PR Merged, JSON update failed: " + e.ToString(); } + + PushTestmergeCommit(); } return Result; } @@ -1314,5 +1378,17 @@ namespace TGServerService var B = Repo.Branches[LiveTrackingBranch]; return B != null ? B.Tip.Sha : "UNKNOWN"; } + + /// + public bool PushTestmergeCommits() + { + return Config.PushTestmergeCommits; + } + + /// + public void SetPushTestmergeCommits(bool newValue) + { + Config.PushTestmergeCommits = newValue; + } } } diff --git a/TGServiceInterface/Components/Repository.cs b/TGServiceInterface/Components/Repository.cs index 0b591b1dff..d722304328 100644 --- a/TGServiceInterface/Components/Repository.cs +++ b/TGServiceInterface/Components/Repository.cs @@ -175,5 +175,19 @@ namespace TGServiceInterface.Components /// The current auto update interval or 0 if it's disabled [OperationContract] ulong AutoUpdateInterval(); + + /// + /// Check if we push a temporary branch to the remote when we make testmerge commits + /// + /// if we publish testmerge commits to the remote, otherwise + [OperationContract] + bool PushTestmergeCommits(); + + /// + /// Set if we push a temporary branch to the remote when we make testmerge commits + /// + /// if we testmerge commits should be published to the remote, otherwise + [OperationContract] + void SetPushTestmergeCommits(bool newValue); } } diff --git a/TGServiceTests/CommandLine/Commands/Repository/RepositoryCommandTest.cs b/TGServiceTests/CommandLine/Commands/Repository/RepositoryCommandTest.cs new file mode 100644 index 0000000000..48e3355c1a --- /dev/null +++ b/TGServiceTests/CommandLine/Commands/Repository/RepositoryCommandTest.cs @@ -0,0 +1,22 @@ +using Moq; +using TGServiceInterface; +using TGServiceInterface.Components; +using TGServiceTests; + +namespace TGCommandLine.Commands.Repository.Tests +{ + public abstract class RepositoryCommandTest : OutputProcOverriderTest + { + /// + /// Creates a mock that returns a specific when that component is requested + /// + /// The the resulting should return + /// A mock + protected IInterface MockInterfaceToRepo(ITGRepository repo) + { + var mock = new Mock(); + mock.Setup(foo => foo.GetComponent()).Returns(repo); + return mock.Object; + } + } +} diff --git a/TGServiceTests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs b/TGServiceTests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs new file mode 100644 index 0000000000..9810387d9c --- /dev/null +++ b/TGServiceTests/CommandLine/Commands/Repository/TestRepoSetPushTestmergeCommitsCommand.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Collections.Generic; +using TGServiceInterface; +using TGServiceInterface.Components; + +namespace TGCommandLine.Commands.Repository.Tests +{ + /// + /// Tests for + /// + [TestClass] + public class TestRepoSetPushTestmergeCommitsCommand : RepositoryCommandTest + { + /// + /// Ensure it can be turned on + /// + [TestMethod] + public void TestTurnOn() + { + var ran = false; + var repo = new Mock(); + repo.Setup(foo => foo.SetPushTestmergeCommits(true)).Callback(() => { ran = true; }); + ConsoleCommand.Interface = MockInterfaceToRepo(repo.Object); + Assert.AreEqual(new RepoSetPushTestmergeCommitsCommand().DoRun(new List { "on" }), Command.ExitCode.Normal); + Assert.IsTrue(ran); + } + + /// + /// Ensure that it can be turned off + /// + [TestMethod] + public void TestTurnOff() + { + + var ran = false; + var repo = new Mock(); + repo.Setup(foo => foo.SetPushTestmergeCommits(false)).Callback(() => { ran = true; }); + ConsoleCommand.Interface = MockInterfaceToRepo(repo.Object); + Assert.AreEqual(new RepoSetPushTestmergeCommitsCommand().DoRun(new List { "off" }), Command.ExitCode.Normal); + Assert.IsTrue(ran); + } + + /// + /// Ensure that is returned for gibberish parameters + /// + [TestMethod] + public void TestGibberishParam() + { + Assert.AreEqual(new RepoSetPushTestmergeCommitsCommand().DoRun(new List { "gibberish" }), Command.ExitCode.BadCommand); + } + } +} diff --git a/TGServiceTests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs b/TGServiceTests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs new file mode 100644 index 0000000000..10b4656086 --- /dev/null +++ b/TGServiceTests/CommandLine/Commands/Repository/TestRepoStatusCommand.cs @@ -0,0 +1,46 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Collections.Generic; +using TGServiceInterface; +using TGServiceInterface.Components; + +namespace TGCommandLine.Commands.Repository.Tests +{ + /// + /// Tests for + /// + [TestClass] + public class TestRepoStatusCommand : RepositoryCommandTest + { + /// + /// Set the default returns for the mock to be + /// + /// A of with default return values + Mock GetDefaultMock() + { + var mock = new Mock(); + mock.SetReturnsDefault(null); + return mock; + } + + /// + /// Tests the results of + /// + [TestMethod] + public void TestPushTestmergeCommits() + { + var ran = false; + var mock = GetDefaultMock(); + mock.Setup(foo => foo.PushTestmergeCommits()).Returns(false).Callback(() => ran = true); + ConsoleCommand.Interface = MockInterfaceToRepo(mock.Object); + Assert.AreEqual(new RepoStatusCommand().DoRun(new List { }), Command.ExitCode.Normal); + Assert.IsTrue(ran); + + ran = false; + mock.Setup(foo => foo.PushTestmergeCommits()).Returns(true).Callback(() => ran = true); + ConsoleCommand.Interface = MockInterfaceToRepo(mock.Object); + Assert.AreEqual(new RepoStatusCommand().DoRun(new List { }), Command.ExitCode.Normal); + Assert.IsTrue(ran); + } + } +} diff --git a/TGServiceTests/OutputProcOverriderTest.cs b/TGServiceTests/OutputProcOverriderTest.cs new file mode 100644 index 0000000000..d118f07faf --- /dev/null +++ b/TGServiceTests/OutputProcOverriderTest.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TGServiceInterface; + +namespace TGServiceTests +{ + /// + /// For tests that need to override + /// + public abstract class OutputProcOverriderTest + { + /// + /// Set to + /// + [TestInitialize] + public void Setup() + { + Command.OutputProcVar.Value = OutputProc; + } + + /// + /// Whe + /// + /// + protected virtual void OutputProc(string message) { } + } +} diff --git a/TGServiceTests/Service/TestServerInstance.cs b/TGServiceTests/Service/TestServerInstance.cs index 27b79b6c4b..e8c8629549 100644 --- a/TGServiceTests/Service/TestServerInstance.cs +++ b/TGServiceTests/Service/TestServerInstance.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using TGServiceTests; namespace TGServerService.Tests @@ -17,5 +18,33 @@ namespace TGServerService.Tests { new ServerInstance(new InstanceConfig(TempPath), 1).Dispose(); } + + /// + /// Test the return value of + /// + [TestMethod] + public void TestPushTestMergeCommits() + { + var ic = new InstanceConfig(TempPath) { PushTestmergeCommits = false }; + using (var si = new ServerInstance(ic, 1)) + { + Assert.IsFalse(si.PushTestmergeCommits()); + ic.PushTestmergeCommits = true; + Assert.IsTrue(si.PushTestmergeCommits()); + } + } + + [TestMethod] + public void TestSetPushTestMergeCommits() + { + var ic = new InstanceConfig(TempPath) { PushTestmergeCommits = false }; + using (var si = new ServerInstance(ic, 1)) + { + si.SetPushTestmergeCommits(true); + Assert.IsTrue(ic.PushTestmergeCommits); + si.SetPushTestmergeCommits(false); + Assert.IsFalse(ic.PushTestmergeCommits); + } + } } } diff --git a/TGServiceTests/TGServiceTests.csproj b/TGServiceTests/TGServiceTests.csproj index 23dccc15c1..e8ad0a9acc 100644 --- a/TGServiceTests/TGServiceTests.csproj +++ b/TGServiceTests/TGServiceTests.csproj @@ -38,19 +38,30 @@ 4 + + ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll ..\packages\MSTest.TestFramework.1.1.18\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Moq.4.7.145\lib\net45\Moq.dll + + + + + + Component @@ -65,6 +76,10 @@ + + {89191f69-b18e-4b59-b72e-e12f9b6811a0} + TGCommandLine + {f32eda25-0855-411c-af5e-f0d042917e2d} TGServerService diff --git a/TGServiceTests/TempDirectoryRequiredTest.cs b/TGServiceTests/TempDirectoryRequiredTest.cs index f3dcacd255..8a2dbbb4e7 100644 --- a/TGServiceTests/TempDirectoryRequiredTest.cs +++ b/TGServiceTests/TempDirectoryRequiredTest.cs @@ -7,7 +7,7 @@ namespace TGServiceTests /// To be the parent of test classes that required a temporary directory /// [TestClass] - public class TempDirectoryRequiredTest + public abstract class TempDirectoryRequiredTest { /// /// The path to the temporary directory diff --git a/TGServiceTests/packages.config b/TGServiceTests/packages.config index d8c1b9099c..968620fd09 100644 --- a/TGServiceTests/packages.config +++ b/TGServiceTests/packages.config @@ -1,5 +1,7 @@  + + \ No newline at end of file