refactoring the Rcheckin command to lean on the GenericCaretaker via a CheckinOptionsHelper when processing special actions in commit messages to associate and resolve work items
This commit is contained in:
+32
-47
@@ -7,6 +7,7 @@ using NDesk.Options;
|
||||
using Sep.Git.Tfs.Core;
|
||||
using StructureMap;
|
||||
using System.Text.RegularExpressions;
|
||||
using Sep.Git.Tfs.Util;
|
||||
|
||||
namespace Sep.Git.Tfs.Commands
|
||||
{
|
||||
@@ -16,6 +17,7 @@ namespace Sep.Git.Tfs.Commands
|
||||
{
|
||||
private readonly TextWriter _stdout;
|
||||
private readonly CheckinOptions _checkinOptions;
|
||||
private readonly CheckinOptionsHelper _checkinOptionsHelper;
|
||||
private readonly TfsWriter _writer;
|
||||
|
||||
private bool Quick { get; set; }
|
||||
@@ -24,6 +26,7 @@ namespace Sep.Git.Tfs.Commands
|
||||
{
|
||||
_stdout = stdout;
|
||||
_checkinOptions = checkinOptions;
|
||||
_checkinOptionsHelper = new CheckinOptionsHelper(_stdout);
|
||||
_writer = writer;
|
||||
}
|
||||
|
||||
@@ -45,30 +48,6 @@ namespace Sep.Git.Tfs.Commands
|
||||
return _writer.Write("HEAD", PerformRCheckin);
|
||||
}
|
||||
|
||||
private String ProcessWorkItems(string commitMessage)
|
||||
{
|
||||
MatchCollection workitemMatches;
|
||||
if ((workitemMatches = Sep.Git.Tfs.GitTfsConstants.TfsWorkItemRegex.Matches(commitMessage)).Count > 0)
|
||||
{
|
||||
foreach (Match match in workitemMatches)
|
||||
{
|
||||
switch (match.Groups["action"].Value)
|
||||
{
|
||||
case "associate":
|
||||
_stdout.WriteLine("Associating with work item {0}", match.Groups["item_id"]);
|
||||
_checkinOptions.WorkItemsToAssociate.Add(match.Groups["item_id"].Value);
|
||||
break;
|
||||
case "resolve":
|
||||
_stdout.WriteLine("Resolving work item {0}", match.Groups["item_id"]);
|
||||
_checkinOptions.WorkItemsToResolve.Add(match.Groups["item_id"].Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Sep.Git.Tfs.GitTfsConstants.TfsWorkItemRegex.Replace(commitMessage, "").Trim(' ', '\r', '\n');
|
||||
}
|
||||
return commitMessage;
|
||||
}
|
||||
|
||||
private int PerformRCheckin(TfsChangesetInfo parentChangeset)
|
||||
{
|
||||
var tfsRemote = parentChangeset.Remote;
|
||||
@@ -109,19 +88,19 @@ namespace Sep.Git.Tfs.Commands
|
||||
string target = strs[0];
|
||||
string[] gitParents = strs.AsEnumerable().Skip(1).Where(hash => hash != currentParent).ToArray();
|
||||
|
||||
string commitMessage = ProcessWorkItems(repo.GetCommitMessage(target, currentParent).Trim(' ', '\r', '\n'));
|
||||
_stdout.WriteLine("Starting checkin of {0} '{1}'", target.Substring(0, 8), commitMessage);
|
||||
_checkinOptions.CheckinComment = commitMessage;
|
||||
long newChangesetId = tfsRemote.Checkin(target, currentParent, parentChangeset);
|
||||
tfsRemote.FetchWithMerge(newChangesetId, gitParents);
|
||||
if (tfsRemote.MaxChangesetId != newChangesetId)
|
||||
throw new GitTfsException("error: New TFS changesets were found. Rcheckin was not finished.");
|
||||
string commitMessage = repo.GetCommitMessage(target, currentParent).Trim(' ', '\r', '\n');
|
||||
using (var caretaker = _checkinOptionsHelper.UpdateCheckinOptionsForThisCommit(_checkinOptions, commitMessage))
|
||||
{
|
||||
_stdout.WriteLine("Starting checkin of {0} '{1}'", target.Substring(0, 8), _checkinOptions.CheckinComment);
|
||||
long newChangesetId = tfsRemote.Checkin(target, currentParent, parentChangeset);
|
||||
tfsRemote.FetchWithMerge(newChangesetId, gitParents);
|
||||
if (tfsRemote.MaxChangesetId != newChangesetId)
|
||||
throw new GitTfsException("error: New TFS changesets were found. Rcheckin was not finished.");
|
||||
|
||||
currentParent = target;
|
||||
parentChangeset = new TfsChangesetInfo { ChangesetId = newChangesetId, GitCommit = tfsRemote.MaxCommitHash, Remote = tfsRemote };
|
||||
_stdout.WriteLine("Done with {0}.", target);
|
||||
_checkinOptions.WorkItemsToAssociate.Clear();
|
||||
_checkinOptions.WorkItemsToResolve.Clear();
|
||||
currentParent = target;
|
||||
parentChangeset = new TfsChangesetInfo { ChangesetId = newChangesetId, GitCommit = tfsRemote.MaxCommitHash, Remote = tfsRemote };
|
||||
_stdout.WriteLine("Done with {0}.", target);
|
||||
}
|
||||
}
|
||||
|
||||
_stdout.WriteLine("No more to rcheckin.");
|
||||
@@ -143,22 +122,28 @@ namespace Sep.Git.Tfs.Commands
|
||||
string target = strs[0];
|
||||
string[] gitParents = strs.AsEnumerable().Skip(1).Where(hash => hash != tfsLatest).ToArray();
|
||||
|
||||
string commitMessage = ProcessWorkItems(repo.GetCommitMessage(target, tfsLatest).Trim(' ', '\r', '\n'));
|
||||
_stdout.WriteLine("Starting checkin of {0} '{1}'", target.Substring(0, 8), commitMessage);
|
||||
_checkinOptions.CheckinComment = commitMessage;
|
||||
long newChangesetId = tfsRemote.Checkin(target, parentChangeset);
|
||||
tfsRemote.FetchWithMerge(newChangesetId, gitParents);
|
||||
if (tfsRemote.MaxChangesetId != newChangesetId)
|
||||
throw new GitTfsException("error: New TFS changesets were found. Rcheckin was not finished.");
|
||||
string commitMessage = repo.GetCommitMessage(target, tfsLatest).Trim(' ', '\r', '\n');
|
||||
using (var caretaker = _checkinOptionsHelper.UpdateCheckinOptionsForThisCommit(_checkinOptions, commitMessage))
|
||||
{
|
||||
_stdout.WriteLine("Starting checkin of {0} '{1}'", target.Substring(0, 8), _checkinOptions.CheckinComment);
|
||||
long newChangesetId = tfsRemote.Checkin(target, parentChangeset);
|
||||
tfsRemote.FetchWithMerge(newChangesetId, gitParents);
|
||||
if (tfsRemote.MaxChangesetId != newChangesetId)
|
||||
throw new GitTfsException("error: New TFS changesets were found. Rcheckin was not finished.");
|
||||
|
||||
tfsLatest = tfsRemote.MaxCommitHash;
|
||||
parentChangeset = new TfsChangesetInfo { ChangesetId = newChangesetId, GitCommit = tfsLatest, Remote = tfsRemote };
|
||||
_stdout.WriteLine("Done with {0}, rebasing tail onto new TFS-commit...", target);
|
||||
tfsLatest = tfsRemote.MaxCommitHash;
|
||||
parentChangeset = new TfsChangesetInfo {ChangesetId = newChangesetId, GitCommit = tfsLatest, Remote = tfsRemote};
|
||||
_stdout.WriteLine("Done with {0}, rebasing tail onto new TFS-commit...", target);
|
||||
tfsLatest = tfsRemote.MaxCommitHash;
|
||||
parentChangeset = new TfsChangesetInfo { ChangesetId = newChangesetId, GitCommit = tfsLatest, Remote = tfsRemote };
|
||||
_stdout.WriteLine("Done with {0}, rebasing tail onto new TFS-commit...", target);
|
||||
|
||||
repo.CommandNoisy("rebase", "--preserve-merges", "--onto", tfsLatest, target);
|
||||
_stdout.WriteLine("Rebase done successfully.");
|
||||
_checkinOptions.WorkItemsToAssociate.Clear();
|
||||
_checkinOptions.WorkItemsToResolve.Clear();
|
||||
repo.CommandNoisy("rebase", "--preserve-merges", "--onto", tfsLatest, target);
|
||||
_stdout.WriteLine("Rebase done successfully.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
<Compile Include="Core\TfsWriter.cs" />
|
||||
<Compile Include="Core\GitTfsException.cs" />
|
||||
<Compile Include="Util\AuthorsFile.cs" />
|
||||
<Compile Include="Util\CheckinOptionsHelper.cs" />
|
||||
<Compile Include="Util\ExceptionFormattingExtensions.cs" />
|
||||
<Compile Include="Util\GitTfsCommandFactory.cs" />
|
||||
<Compile Include="Util\GitTfsCommandRunner.cs" />
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Sep.Git.Tfs.Commands;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Sep.Git.Tfs.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the state of the <see cref="CheckinOptions"/> before parsing
|
||||
/// a git commit message for special actions. Packages up restore
|
||||
/// actions inside of a <see cref="GenericCaretaker"/> so that any
|
||||
/// special actions in the commit message can be temporarily
|
||||
/// set in the semi-global <see cref="CheckinOptions"/> without
|
||||
/// affecting state later on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class extracts the pre-checkin commit message parsing that
|
||||
/// enables special git-tfs commands:
|
||||
/// https://github.com/git-tfs/git-tfs/wiki/Special-actions-in-commit-messages
|
||||
/// </remarks>
|
||||
public class CheckinOptionsHelper
|
||||
{
|
||||
TextWriter writer;
|
||||
|
||||
public CheckinOptionsHelper(TextWriter writer)
|
||||
{
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public GenericCaretaker UpdateCheckinOptionsForThisCommit(CheckinOptions checkinOptions, string commitMessage)
|
||||
{
|
||||
Action restoreCheckinComment = ApplyCommitMessage(checkinOptions, commitMessage);
|
||||
Action undoWorkItemCommands = ProcessWorkItemCommands(checkinOptions, writer);
|
||||
|
||||
return new GenericCaretaker(() =>
|
||||
{
|
||||
restoreCheckinComment();
|
||||
undoWorkItemCommands();
|
||||
});
|
||||
}
|
||||
|
||||
private Action ApplyCommitMessage(CheckinOptions checkinOptions, string commitMessage)
|
||||
{
|
||||
// store existing state
|
||||
string originalCheckinComment = checkinOptions.CheckinComment;
|
||||
|
||||
// operate
|
||||
checkinOptions.CheckinComment = commitMessage;
|
||||
|
||||
// delegate the restoration of state
|
||||
return () =>
|
||||
{
|
||||
checkinOptions.CheckinComment = originalCheckinComment;
|
||||
};
|
||||
}
|
||||
|
||||
private Action ProcessWorkItemCommands(CheckinOptions checkinOptions, TextWriter writer)
|
||||
{
|
||||
MatchCollection workitemMatches;
|
||||
if ((workitemMatches = Sep.Git.Tfs.GitTfsConstants.TfsWorkItemRegex.Matches(checkinOptions.CheckinComment)).Count > 0)
|
||||
{
|
||||
foreach (Match match in workitemMatches)
|
||||
{
|
||||
switch (match.Groups["action"].Value)
|
||||
{
|
||||
case "associate":
|
||||
writer.WriteLine("Associating with work item {0}", match.Groups["item_id"]);
|
||||
checkinOptions.WorkItemsToAssociate.Add(match.Groups["item_id"].Value);
|
||||
break;
|
||||
case "resolve":
|
||||
writer.WriteLine("Resolving work item {0}", match.Groups["item_id"]);
|
||||
checkinOptions.WorkItemsToResolve.Add(match.Groups["item_id"].Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
checkinOptions.CheckinComment = Sep.Git.Tfs.GitTfsConstants.TfsWorkItemRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n');
|
||||
}
|
||||
|
||||
// delegate the restoration of state
|
||||
return () =>
|
||||
{
|
||||
checkinOptions.WorkItemsToAssociate.Clear();
|
||||
checkinOptions.WorkItemsToResolve.Clear();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,7 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TestHelpers\ExtensionMethods.cs" />
|
||||
<Compile Include="Util\AuthorsFileUnitTest.cs" />
|
||||
<Compile Include="Util\CheckinOptionsHelperTests.cs" />
|
||||
<Compile Include="Util\GitTfsCommandRunnerTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Sep.Git.Tfs.Commands;
|
||||
using Sep.Git.Tfs.Util;
|
||||
using Xunit;
|
||||
|
||||
namespace Sep.Git.Tfs.Test.Util
|
||||
{
|
||||
public class CheckinOptionsHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void Update_preserves_original_commit()
|
||||
{
|
||||
TextWriter writer = new StringWriter();
|
||||
CheckinOptionsHelper helper = new CheckinOptionsHelper(writer);
|
||||
|
||||
string originalCheckinComment = "command-line input";
|
||||
CheckinOptions checkinOptions = new CheckinOptions()
|
||||
{
|
||||
CheckinComment = originalCheckinComment
|
||||
};
|
||||
|
||||
string commitMessage =
|
||||
@"test message
|
||||
|
||||
formatted git commit message";
|
||||
|
||||
string expectedCheckinComment =
|
||||
@"test message
|
||||
|
||||
formatted git commit message";
|
||||
|
||||
using (var caretaker = helper.UpdateCheckinOptionsForThisCommit(checkinOptions, commitMessage))
|
||||
{
|
||||
Assert.Equal(expectedCheckinComment, checkinOptions.CheckinComment);
|
||||
}
|
||||
|
||||
Assert.Equal(originalCheckinComment, checkinOptions.CheckinComment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_associates_and_clears_work_items()
|
||||
{
|
||||
StringWriter textWriter = new StringWriter();
|
||||
CheckinOptionsHelper helper = new CheckinOptionsHelper(textWriter);
|
||||
|
||||
CheckinOptions checkinOptions = new CheckinOptions();
|
||||
|
||||
string commitMessage =
|
||||
@"test message
|
||||
|
||||
formatted git commit message
|
||||
|
||||
git-tfs-work-item: 1234 associate";
|
||||
|
||||
using (var caretaker = helper.UpdateCheckinOptionsForThisCommit(checkinOptions, commitMessage))
|
||||
{
|
||||
Assert.Equal(1, checkinOptions.WorkItemsToAssociate.Count);
|
||||
Assert.Equal("1234", checkinOptions.WorkItemsToAssociate.First());
|
||||
}
|
||||
|
||||
Assert.Equal(0, checkinOptions.WorkItemsToAssociate.Count);
|
||||
Assert.Equal("Associating with work item 1234" + textWriter.NewLine, textWriter.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_resolves_and_clears_work_items()
|
||||
{
|
||||
StringWriter textWriter = new StringWriter();
|
||||
CheckinOptionsHelper helper = new CheckinOptionsHelper(textWriter);
|
||||
|
||||
CheckinOptions checkinOptions = new CheckinOptions();
|
||||
|
||||
string commitMessage =
|
||||
@"test message
|
||||
|
||||
formatted git commit message
|
||||
|
||||
git-tfs-work-item: 1234 resolve";
|
||||
|
||||
using (var caretaker = helper.UpdateCheckinOptionsForThisCommit(checkinOptions, commitMessage))
|
||||
{
|
||||
Assert.Equal(1, checkinOptions.WorkItemsToResolve.Count);
|
||||
Assert.Equal("1234", checkinOptions.WorkItemsToResolve.First());
|
||||
}
|
||||
|
||||
Assert.Equal(0, checkinOptions.WorkItemsToResolve.Count);
|
||||
Assert.Equal("Resolving work item 1234" + textWriter.NewLine, textWriter.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user