From a7adafa261e97338de02f91bfca4f46c729592c2 Mon Sep 17 00:00:00 2001 From: NathanLBCooper Date: Fri, 5 Feb 2016 17:43:02 +0000 Subject: [PATCH] Automatically Associate work items with a shelve command. --- GitTfs/Commands/Shelve.cs | 25 ++- GitTfs/Core/DerivedGitTfsRemote.cs | 5 + GitTfs/Core/GitTfsRemote.cs | 8 +- GitTfs/Core/IGitTfsRemote.cs | 2 +- GitTfs/Core/ITfsWorkspace.cs | 2 +- GitTfs/Core/TfsWorkspace.cs | 7 +- GitTfs/GitTfs.csproj | 2 + GitTfs/Util/CheckinOptionsExtensions.cs | 146 ++++++++++++++++ .../CommitSpecificCheckinOptionsFactory.cs | 162 +----------------- .../ShelveSpecificCheckinOptionsFactory.cs | 29 ++++ GitTfsTest/Commands/ShelveTest.cs | 12 +- GitTfsTest/GitTfsTest.csproj | 1 + ...helveSpecificCheckinOptionsFactoryTests.cs | 47 +++++ doc/commands/shelve.md | 4 + doc/special-actions-in-commit-messages.md | 6 +- 15 files changed, 286 insertions(+), 172 deletions(-) create mode 100644 GitTfs/Util/CheckinOptionsExtensions.cs create mode 100644 GitTfs/Util/ShelveSpecificCheckinOptionsFactory.cs create mode 100644 GitTfsTest/Util/ShelveSpecificCheckinOptionsFactoryTests.cs diff --git a/GitTfs/Commands/Shelve.cs b/GitTfs/Commands/Shelve.cs index 6cf96244..21cb5ae3 100644 --- a/GitTfs/Commands/Shelve.cs +++ b/GitTfs/Commands/Shelve.cs @@ -2,6 +2,7 @@ using System.IO; using NDesk.Options; using Sep.Git.Tfs.Core; +using Sep.Git.Tfs.Util; using StructureMap; namespace Sep.Git.Tfs.Commands @@ -13,14 +14,18 @@ namespace Sep.Git.Tfs.Commands { private readonly TextWriter _stdout; private readonly CheckinOptions _checkinOptions; + private readonly ShelveSpecificCheckinOptionsFactory _checkinOptionsFactory; private readonly TfsWriter _writer; + private readonly Globals _globals; private bool EvaluateCheckinPolicies { get; set; } - public Shelve(TextWriter stdout, CheckinOptions checkinOptions, TfsWriter writer) + public Shelve(TextWriter stdout, CheckinOptions checkinOptions, TfsWriter writer, Globals globals) { _stdout = stdout; + _globals = globals; _checkinOptions = checkinOptions; + _checkinOptionsFactory = new ShelveSpecificCheckinOptionsFactory(_stdout, _globals); _writer = writer; } @@ -52,9 +57,25 @@ namespace Sep.Git.Tfs.Commands _stdout.WriteLine("Shelveset \"" + shelvesetName + "\" already exists. Use -f to replace it."); return GitTfsExitCodes.ForceRequired; } - changeset.Remote.Shelve(shelvesetName, referenceToShelve, changeset, EvaluateCheckinPolicies); + + var commit = _globals.Repository.GetCommit(refToShelve); + var message = commit != null + ? BuildCommitMessage(commit, !_checkinOptions.NoGenerateCheckinComment, + changeset.Remote.MaxCommitHash) + : string.Empty; + + var shelveSpecificCheckinOptions = _checkinOptionsFactory.BuildShelveSetSpecificCheckinOptions(_checkinOptions, message); + + changeset.Remote.Shelve(shelvesetName, referenceToShelve, changeset, shelveSpecificCheckinOptions, EvaluateCheckinPolicies); return GitTfsExitCodes.OK; }); } + + public string BuildCommitMessage(GitCommit commit, bool generateCheckinComment, string latest) + { + return generateCheckinComment + ? _globals.Repository.GetCommitMessage(commit.Sha, latest) + : _globals.Repository.GetCommit(commit.Sha).Message; + } } } diff --git a/GitTfs/Core/DerivedGitTfsRemote.cs b/GitTfs/Core/DerivedGitTfsRemote.cs index 7f6b9cfd..31507bd1 100644 --- a/GitTfs/Core/DerivedGitTfsRemote.cs +++ b/GitTfs/Core/DerivedGitTfsRemote.cs @@ -230,6 +230,11 @@ namespace Sep.Git.Tfs.Core throw DerivedRemoteException; } + public void Shelve(string shelvesetName, string treeish, TfsChangesetInfo parentChangeset, CheckinOptions options, bool evaluateCheckinPolicies) + { + throw DerivedRemoteException; + } + public void Shelve(string shelvesetName, string treeish, TfsChangesetInfo parentChangeset, bool evaluateCheckinPolicies) { throw DerivedRemoteException; diff --git a/GitTfs/Core/GitTfsRemote.cs b/GitTfs/Core/GitTfsRemote.cs index 55e04a29..469f9b5c 100644 --- a/GitTfs/Core/GitTfsRemote.cs +++ b/GitTfs/Core/GitTfsRemote.cs @@ -846,9 +846,9 @@ namespace Sep.Git.Tfs.Core Repository.UpdateRef(destinationRef, commit, "Shelveset " + shelvesetName + " from " + shelvesetOwner); } - public void Shelve(string shelvesetName, string head, TfsChangesetInfo parentChangeset, bool evaluateCheckinPolicies) + public void Shelve(string shelvesetName, string head, TfsChangesetInfo parentChangeset, CheckinOptions options, bool evaluateCheckinPolicies) { - WithWorkspace(parentChangeset, workspace => Shelve(shelvesetName, head, parentChangeset, evaluateCheckinPolicies, workspace)); + WithWorkspace(parentChangeset, workspace => Shelve(shelvesetName, head, parentChangeset, options, evaluateCheckinPolicies, workspace)); } public bool HasShelveset(string shelvesetName) @@ -856,10 +856,10 @@ namespace Sep.Git.Tfs.Core return Tfs.HasShelveset(shelvesetName); } - private void Shelve(string shelvesetName, string head, TfsChangesetInfo parentChangeset, bool evaluateCheckinPolicies, ITfsWorkspace workspace) + private void Shelve(string shelvesetName, string head, TfsChangesetInfo parentChangeset, CheckinOptions options, bool evaluateCheckinPolicies, ITfsWorkspace workspace) { PendChangesToWorkspace(head, parentChangeset.GitCommit, workspace); - workspace.Shelve(shelvesetName, evaluateCheckinPolicies, () => Repository.GetCommitMessage(head, parentChangeset.GitCommit)); + workspace.Shelve(shelvesetName, evaluateCheckinPolicies, options); } public int CheckinTool(string head, TfsChangesetInfo parentChangeset) diff --git a/GitTfs/Core/IGitTfsRemote.cs b/GitTfs/Core/IGitTfsRemote.cs index 0e7310e6..e222d7e1 100644 --- a/GitTfs/Core/IGitTfsRemote.cs +++ b/GitTfs/Core/IGitTfsRemote.cs @@ -56,7 +56,7 @@ namespace Sep.Git.Tfs.Core void QuickFetch(); void QuickFetch(int changesetId); void Unshelve(string shelvesetOwner, string shelvesetName, string destinationBranch, Action ignorableErrorHandler, bool force); - void Shelve(string shelvesetName, string treeish, TfsChangesetInfo parentChangeset, bool evaluateCheckinPolicies); + void Shelve(string shelvesetName, string head, TfsChangesetInfo parentChangeset, CheckinOptions options, bool evaluateCheckinPolicies); bool HasShelveset(string shelvesetName); int CheckinTool(string head, TfsChangesetInfo parentChangeset); int Checkin(string treeish, TfsChangesetInfo parentChangeset, CheckinOptions options, string sourceTfsPath = null); diff --git a/GitTfs/Core/ITfsWorkspace.cs b/GitTfs/Core/ITfsWorkspace.cs index 92998f8f..6e4f0a14 100644 --- a/GitTfs/Core/ITfsWorkspace.cs +++ b/GitTfs/Core/ITfsWorkspace.cs @@ -25,7 +25,7 @@ namespace Sep.Git.Tfs.Core /// /// Shelves all pending changes, with the given shelveset name. /// - void Shelve(string shelvesetName, bool evaluateCheckinPolicies, Func generateCheckinComment); + void Shelve(string shelvesetName, bool evaluateCheckinPolicies, CheckinOptions checkinOptions); /// /// Evaluates check-in policies and checks in all pending changes. /// diff --git a/GitTfs/Core/TfsWorkspace.cs b/GitTfs/Core/TfsWorkspace.cs index ac2b83dd..541cc645 100644 --- a/GitTfs/Core/TfsWorkspace.cs +++ b/GitTfs/Core/TfsWorkspace.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; using System.Linq; using Sep.Git.Tfs.Commands; @@ -33,7 +34,7 @@ namespace Sep.Git.Tfs.Core this.Remote = remote; } - public void Shelve(string shelvesetName, bool evaluateCheckinPolicies, Func generateCheckinComment) + public void Shelve(string shelvesetName, bool evaluateCheckinPolicies, CheckinOptions checkinOptions) { var pendingChanges = _workspace.GetPendingChanges(); @@ -41,8 +42,8 @@ namespace Sep.Git.Tfs.Core throw new GitTfsException("Nothing to shelve!"); var shelveset = _tfsHelper.CreateShelveset(_workspace, shelvesetName); - shelveset.Comment = string.IsNullOrWhiteSpace(_checkinOptions.CheckinComment) && !_checkinOptions.NoGenerateCheckinComment ? generateCheckinComment() : _checkinOptions.CheckinComment; - shelveset.WorkItemInfo = GetWorkItemInfos().ToArray(); + shelveset.Comment = checkinOptions.CheckinComment; + shelveset.WorkItemInfo = GetWorkItemInfos(checkinOptions).ToArray(); if (evaluateCheckinPolicies) { foreach (var message in _policyEvaluator.EvaluateCheckin(_workspace, pendingChanges, shelveset.Comment, null, shelveset.WorkItemInfo).Messages) diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index bdd85d38..ae8171ef 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -202,6 +202,7 @@ + @@ -211,6 +212,7 @@ + diff --git a/GitTfs/Util/CheckinOptionsExtensions.cs b/GitTfs/Util/CheckinOptionsExtensions.cs new file mode 100644 index 00000000..a427d77e --- /dev/null +++ b/GitTfs/Util/CheckinOptionsExtensions.cs @@ -0,0 +1,146 @@ +using System; +using System.IO; +using System.Text.RegularExpressions; +using Sep.Git.Tfs.Commands; +using Sep.Git.Tfs.Core; + +namespace Sep.Git.Tfs.Util +{ + public static class CheckinOptionsExtensions + { + public static CheckinOptions Clone(this CheckinOptions source, Globals globals) + { + CheckinOptions clone = new CheckinOptions(); + + clone.CheckinComment = source.CheckinComment; + clone.NoGenerateCheckinComment = source.NoGenerateCheckinComment; + clone.NoMerge = source.NoMerge; + clone.OverrideReason = source.OverrideReason; + clone.Force = source.Force; + clone.OverrideGatedCheckIn = source.OverrideGatedCheckIn; + clone.WorkItemsToAssociate.AddRange(source.WorkItemsToAssociate); + clone.WorkItemsToResolve.AddRange(source.WorkItemsToResolve); + clone.AuthorTfsUserId = source.AuthorTfsUserId; + try + { + string re = globals.Repository.GetConfig(GitTfsConstants.WorkItemAssociateRegexConfigKey); + if (String.IsNullOrEmpty(re)) + clone.WorkItemAssociateRegex = GitTfsConstants.TfsWorkItemAssociateRegex; + else + clone.WorkItemAssociateRegex = new Regex(re); + } + catch (Exception) + { + clone.WorkItemAssociateRegex = null; + } + foreach (var note in source.CheckinNotes) + { + clone.CheckinNotes[note.Key] = note.Value; + } + + return clone; + } + + public static void ProcessWorkItemCommands(this CheckinOptions checkinOptions, TextWriter writer, bool isResolvable = true) + { + MatchCollection workitemMatches; + if ((workitemMatches = GitTfsConstants.TfsWorkItemRegex.Matches(checkinOptions.CheckinComment)).Count > 0) + { + foreach (Match match in workitemMatches) + { + if (isResolvable && match.Groups["action"].Value == "resolve") + { + writer.WriteLine("Resolving work item {0}", match.Groups["item_id"]); + checkinOptions.WorkItemsToResolve.Add(match.Groups["item_id"].Value); + } + else { + writer.WriteLine("Associating with work item {0}", match.Groups["item_id"]); + checkinOptions.WorkItemsToAssociate.Add(match.Groups["item_id"].Value); + } + } + checkinOptions.CheckinComment = GitTfsConstants.TfsWorkItemRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); + } + + if (checkinOptions.WorkItemAssociateRegex != null) + { + var workitemAssociatedMatches = checkinOptions.WorkItemAssociateRegex.Matches(checkinOptions.CheckinComment); + if (workitemAssociatedMatches.Count != 0) + { + foreach (Match match in workitemAssociatedMatches) + { + var workitem = match.Groups["item_id"].Value; + if (!checkinOptions.WorkItemsToAssociate.Contains(workitem)) + { + writer.WriteLine("Associating with work item {0}", workitem); + checkinOptions.WorkItemsToAssociate.Add(workitem); + } + } + } + } + } + + public static void ProcessCheckinNoteCommands(this CheckinOptions checkinOptions, TextWriter writer) + { + foreach (Match match in GitTfsConstants.TfsReviewerRegex.Matches(checkinOptions.CheckinComment)) + { + string reviewer = match.Groups["reviewer"].Value; + if (!string.IsNullOrWhiteSpace(reviewer)) + { + switch (match.Groups["type"].Value) + { + case "code": + writer.WriteLine("Code reviewer: {0}", reviewer); + checkinOptions.CheckinNotes.Add("Code Reviewer", reviewer); + break; + case "security": + writer.WriteLine("Security reviewer: {0}", reviewer); + checkinOptions.CheckinNotes.Add("Security Reviewer", reviewer); + break; + case "performance": + writer.WriteLine("Performance reviewer: {0}", reviewer); + checkinOptions.CheckinNotes.Add("Performance Reviewer", reviewer); + break; + } + } + } + checkinOptions.CheckinComment = GitTfsConstants.TfsReviewerRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); + } + + + + public static void ProcessForceCommand(this CheckinOptions checkinOptions, TextWriter writer) + { + MatchCollection workitemMatches; + if ((workitemMatches = GitTfsConstants.TfsForceRegex.Matches(checkinOptions.CheckinComment)).Count == 1) + { + string overrideReason = workitemMatches[0].Groups["reason"].Value; + + if (!string.IsNullOrWhiteSpace(overrideReason)) + { + writer.WriteLine("Forcing the checkin: {0}", overrideReason); + checkinOptions.Force = true; + checkinOptions.OverrideReason = overrideReason; + } + checkinOptions.CheckinComment = GitTfsConstants.TfsForceRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); + } + } + + + + public static void ProcessAuthor(this CheckinOptions checkinOptions, TextWriter writer, GitCommit commit, AuthorsFile authors) + { + if (!authors.IsParseSuccessfull) + return; + + Author a = authors.FindAuthor(commit.AuthorAndEmail); + if (a == null) + { + checkinOptions.AuthorTfsUserId = null; + return; + } + + checkinOptions.AuthorTfsUserId = a.TfsUserId; + writer.WriteLine("Commit was authored by git user {0} {1} ({2})", a.Name, a.Email, a.TfsUserId); + } + } +} diff --git a/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs b/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs index 1c02ddd2..c09926f1 100644 --- a/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs +++ b/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs @@ -1,9 +1,6 @@ -using System; using System.IO; using Sep.Git.Tfs.Commands; using Sep.Git.Tfs.Core; -using System.Text.RegularExpressions; -using StructureMap; namespace Sep.Git.Tfs.Util { @@ -19,181 +16,38 @@ namespace Sep.Git.Tfs.Util public class CommitSpecificCheckinOptionsFactory { private readonly TextWriter writer; + private readonly AuthorsFile authors; private readonly Globals globals; - private AuthorsFile authors; public CommitSpecificCheckinOptionsFactory(TextWriter writer, Globals globals, AuthorsFile authors) { this.writer = writer; - this.globals = globals; this.authors = authors; + this.globals = globals; } public CheckinOptions BuildCommitSpecificCheckinOptions(CheckinOptions sourceCheckinOptions, string commitMessage) { - var customCheckinOptions = Clone(sourceCheckinOptions); + var customCheckinOptions = sourceCheckinOptions.Clone(this.globals); customCheckinOptions.CheckinComment = commitMessage; - ProcessWorkItemCommands(customCheckinOptions, writer); + customCheckinOptions.ProcessWorkItemCommands(writer); - ProcessCheckinNoteCommands(customCheckinOptions, writer); + customCheckinOptions.ProcessCheckinNoteCommands(writer); - ProcessForceCommand(customCheckinOptions, writer); + customCheckinOptions.ProcessForceCommand(writer); return customCheckinOptions; } public CheckinOptions BuildCommitSpecificCheckinOptions(CheckinOptions sourceCheckinOptions, string commitMessage, GitCommit commit) { - var customCheckinOptions = Clone(sourceCheckinOptions); + var customCheckinOptions = BuildCommitSpecificCheckinOptions(sourceCheckinOptions, commitMessage); - customCheckinOptions.CheckinComment = commitMessage; - - ProcessWorkItemCommands(customCheckinOptions, writer); - - ProcessCheckinNoteCommands(customCheckinOptions, writer); - - ProcessForceCommand(customCheckinOptions, writer); - - ProcessAuthor(customCheckinOptions, writer, commit); + customCheckinOptions.ProcessAuthor(writer, commit, authors); return customCheckinOptions; } - - private CheckinOptions Clone(CheckinOptions source) - { - CheckinOptions clone = new CheckinOptions(); - - clone.CheckinComment = source.CheckinComment; - clone.NoGenerateCheckinComment = source.NoGenerateCheckinComment; - clone.NoMerge = source.NoMerge; - clone.OverrideReason = source.OverrideReason; - clone.Force = source.Force; - clone.OverrideGatedCheckIn = source.OverrideGatedCheckIn; - clone.WorkItemsToAssociate.AddRange(source.WorkItemsToAssociate); - clone.WorkItemsToResolve.AddRange(source.WorkItemsToResolve); - clone.AuthorTfsUserId = source.AuthorTfsUserId; - try - { - string re = globals.Repository.GetConfig(GitTfsConstants.WorkItemAssociateRegexConfigKey); - if (String.IsNullOrEmpty(re)) - clone.WorkItemAssociateRegex = GitTfsConstants.TfsWorkItemAssociateRegex; - else - clone.WorkItemAssociateRegex = new Regex(re); - } - catch (Exception) - { - clone.WorkItemAssociateRegex = null; - } - foreach (var note in source.CheckinNotes) - { - clone.CheckinNotes[note.Key] = note.Value; - } - - return clone; - } - - private void ProcessWorkItemCommands(CheckinOptions checkinOptions, TextWriter writer) - { - MatchCollection workitemMatches; - if ((workitemMatches = GitTfsConstants.TfsWorkItemRegex.Matches(checkinOptions.CheckinComment)).Count > 0) - { - foreach (Match match in workitemMatches) - { - if (match.Groups["action"].Value == "resolve") - { - writer.WriteLine("Resolving work item {0}", match.Groups["item_id"]); - checkinOptions.WorkItemsToResolve.Add(match.Groups["item_id"].Value); - }else{ - writer.WriteLine("Associating with work item {0}", match.Groups["item_id"]); - checkinOptions.WorkItemsToAssociate.Add(match.Groups["item_id"].Value); - } - } - checkinOptions.CheckinComment = GitTfsConstants.TfsWorkItemRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); - } - - if (checkinOptions.WorkItemAssociateRegex != null) - { - var workitemAssociatedMatches = checkinOptions.WorkItemAssociateRegex.Matches(checkinOptions.CheckinComment); - if (workitemAssociatedMatches.Count != 0) - { - foreach (Match match in workitemAssociatedMatches) - { - var workitem = match.Groups["item_id"].Value; - if (!checkinOptions.WorkItemsToAssociate.Contains(workitem)) - { - writer.WriteLine("Associating with work item {0}", workitem); - checkinOptions.WorkItemsToAssociate.Add(workitem); - } - } - } - } - } - - private void ProcessCheckinNoteCommands(CheckinOptions checkinOptions, TextWriter writer) - { - foreach (Match match in GitTfsConstants.TfsReviewerRegex.Matches(checkinOptions.CheckinComment)) - { - string reviewer = match.Groups["reviewer"].Value; - if (!string.IsNullOrWhiteSpace(reviewer)) - { - switch (match.Groups["type"].Value) - { - case "code": - writer.WriteLine("Code reviewer: {0}", reviewer); - checkinOptions.CheckinNotes.Add("Code Reviewer", reviewer); - break; - case "security": - writer.WriteLine("Security reviewer: {0}", reviewer); - checkinOptions.CheckinNotes.Add("Security Reviewer", reviewer); - break; - case "performance": - writer.WriteLine("Performance reviewer: {0}", reviewer); - checkinOptions.CheckinNotes.Add("Performance Reviewer", reviewer); - break; - } - } - } - checkinOptions.CheckinComment = GitTfsConstants.TfsReviewerRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); - } - - - - private void ProcessForceCommand(CheckinOptions checkinOptions, TextWriter writer) - { - MatchCollection workitemMatches; - if ((workitemMatches = GitTfsConstants.TfsForceRegex.Matches(checkinOptions.CheckinComment)).Count == 1) - { - string overrideReason = workitemMatches[0].Groups["reason"].Value; - - if (!string.IsNullOrWhiteSpace(overrideReason)) - { - writer.WriteLine("Forcing the checkin: {0}", overrideReason); - checkinOptions.Force = true; - checkinOptions.OverrideReason = overrideReason; - } - checkinOptions.CheckinComment = GitTfsConstants.TfsForceRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); - } - } - - - - private void ProcessAuthor(CheckinOptions checkinOptions, TextWriter writer, GitCommit commit) - { - if (!authors.IsParseSuccessfull) - return; - - Author a = authors.FindAuthor(commit.AuthorAndEmail); - if (a == null) - { - checkinOptions.AuthorTfsUserId = null; - return; - } - - checkinOptions.AuthorTfsUserId = a.TfsUserId; - writer.WriteLine("Commit was authored by git user {0} {1} ({2})", a.Name, a.Email, a.TfsUserId); - } - } } diff --git a/GitTfs/Util/ShelveSpecificCheckinOptionsFactory.cs b/GitTfs/Util/ShelveSpecificCheckinOptionsFactory.cs new file mode 100644 index 00000000..ca14da7b --- /dev/null +++ b/GitTfs/Util/ShelveSpecificCheckinOptionsFactory.cs @@ -0,0 +1,29 @@ +using System.IO; +using Sep.Git.Tfs.Commands; + +namespace Sep.Git.Tfs.Util +{ + public class ShelveSpecificCheckinOptionsFactory + { + private readonly TextWriter writer; + private readonly Globals globals; + + public ShelveSpecificCheckinOptionsFactory(TextWriter writer, Globals globals) + { + this.writer = writer; + this.globals = globals; + } + + public CheckinOptions BuildShelveSetSpecificCheckinOptions(CheckinOptions sourceCheckinOptions, + string commitMessage) + { + var customCheckinOptions = sourceCheckinOptions.Clone(this.globals); + + customCheckinOptions.CheckinComment = commitMessage; + + customCheckinOptions.ProcessWorkItemCommands(writer, false); + + return customCheckinOptions; + } + } +} diff --git a/GitTfsTest/Commands/ShelveTest.cs b/GitTfsTest/Commands/ShelveTest.cs index cfa7035d..e323acd0 100644 --- a/GitTfsTest/Commands/ShelveTest.cs +++ b/GitTfsTest/Commands/ShelveTest.cs @@ -118,8 +118,8 @@ namespace Sep.Git.Tfs.Test.Commands mocks.ClassUnderTest.Run("shelveset name"); - remote.AssertWasCalled(x => x.Shelve(null, null, null, false), - y => y.Constraints(Is.Equal("shelveset name"), Is.Equal("HEAD"), Is.Anything(), Is.Anything())); + remote.AssertWasCalled(x => x.Shelve(null, null, null, null, false), + y => y.Constraints(Is.Equal("shelveset name"), Is.Equal("HEAD"), Is.Anything(), Is.Anything(), Is.Anything())); } [Fact] @@ -135,8 +135,8 @@ namespace Sep.Git.Tfs.Test.Commands mocks.ClassUnderTest.Run("shelveset name", "treeish"); - remote.AssertWasCalled(x => x.Shelve(null, null, null, false), - y => y.Constraints(Is.Equal("shelveset name"), Is.Equal("treeish"), Is.Anything(), Is.Anything())); + remote.AssertWasCalled(x => x.Shelve(null, null, null, null, false), + y => y.Constraints(Is.Equal("shelveset name"), Is.Equal("treeish"), Is.Anything(), Is.Anything(), Is.Anything())); } [Fact] @@ -158,7 +158,7 @@ namespace Sep.Git.Tfs.Test.Commands mocks.ClassUnderTest.Run("shelveset name", "treeish"); mocks.Get().AssertWasNotCalled( - x => x.Shelve(Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything)); + x => x.Shelve(Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything)); } [Fact] @@ -171,7 +171,7 @@ namespace Sep.Git.Tfs.Test.Commands mocks.ClassUnderTest.Run("shelveset name", "treeish"); mocks.Get().AssertWasCalled( - x => x.Shelve(Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything)); + x => x.Shelve(Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything)); } private TfsChangesetInfo ChangesetForRemote(string remoteId) diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index dfc26180..b4a9d6ea 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -136,6 +136,7 @@ + diff --git a/GitTfsTest/Util/ShelveSpecificCheckinOptionsFactoryTests.cs b/GitTfsTest/Util/ShelveSpecificCheckinOptionsFactoryTests.cs new file mode 100644 index 00000000..7527b725 --- /dev/null +++ b/GitTfsTest/Util/ShelveSpecificCheckinOptionsFactoryTests.cs @@ -0,0 +1,47 @@ +using System.IO; +using Sep.Git.Tfs.Commands; +using Sep.Git.Tfs.Core; +using Sep.Git.Tfs.Util; +using StructureMap.AutoMocking; +using Xunit; + +namespace Sep.Git.Tfs.Test.Util +{ + public class ShelveSpecificCheckinOptionsFactoryTests + { + private RhinoAutoMocker mocks; + + public ShelveSpecificCheckinOptionsFactoryTests() + { + mocks = new RhinoAutoMocker(); + mocks.Get().Repository = mocks.Get(); + } + + [Fact] + public void Adds_work_item_to_associate_and_removes_checkin_command_comment() + { + string commitMessage = @"test message + + formatted git commit message + + git-tfs-work-item: 1234 associate"; + + string expectedCheckinComment = @"test message + + formatted git commit message + + "; + + var specificCheckinOptions = GetShelveSpecificCheckinOptions().BuildShelveSetSpecificCheckinOptions(new CheckinOptions(), commitMessage); + + Assert.Equal(1, specificCheckinOptions.WorkItemsToAssociate.Count); + Assert.Contains("1234", specificCheckinOptions.WorkItemsToAssociate); + Assert.Equal(expectedCheckinComment, specificCheckinOptions.CheckinComment); + } + + private ShelveSpecificCheckinOptionsFactory GetShelveSpecificCheckinOptions() + { + return new ShelveSpecificCheckinOptionsFactory(new StringWriter(), mocks.Get()); + } + } +} diff --git a/doc/commands/shelve.md b/doc/commands/shelve.md index d392c56d..cfa4819c 100644 --- a/doc/commands/shelve.md +++ b/doc/commands/shelve.md @@ -1,6 +1,10 @@ ## Summary Creates a TFS shelveset from a Git branch. +## Features +[Special actions in commit messages](../special-actions-in-commit-messages.md) can be inserted, to associate TFS work items. + +## Synopsis Usage: git-tfs shelve [options] shelveset-name [ref-to-shelve] where options are: diff --git a/doc/special-actions-in-commit-messages.md b/doc/special-actions-in-commit-messages.md index 98995f12..384e4f5f 100644 --- a/doc/special-actions-in-commit-messages.md +++ b/doc/special-actions-in-commit-messages.md @@ -1,4 +1,4 @@ -The [rcheckin](commands/rcheckin.md) command examines the commmit message for additional TFS specific +The [rcheckin](commands/rcheckin.md) command examines the commit message for additional TFS specific notifications. The following may be appended as separate lines to the end of the commit message: * `git-tfs-work-item: ` will link the new changeset with the given work item and the default action type. @@ -8,6 +8,10 @@ notifications. The following may be appended as separate lines to the end of the * `git-tfs-performance-reviewer: ` sets the Performance Reviewer field. * `git-tfs-force: ` will force the checkin, overriding TFS checkin policies with the given reason. +The [shelve](commands/shelve.md) command also examines the commit message for the following TFS specific notifications, in the same matter as the rcheckin command: + +* `git-tfs-work-item: ` will link the new changeset with the given work item and the default action type. + Additionally the text of the message is searched for work item IDs. If a string matching a # followed by a valid work-item ID number is found, then the commit will be associated with the specified TFS work-item. For cases where this may be undesirable, the default match may be overridden by setting the