diff --git a/GitTfs.Vs11/Wrappers.Vs11.cs b/GitTfs.Vs11/Wrappers.Vs11.cs index 9d735d3d..5a229aa6 100644 --- a/GitTfs.Vs11/Wrappers.Vs11.cs +++ b/GitTfs.Vs11/Wrappers.Vs11.cs @@ -6,7 +6,7 @@ namespace Sep.Git.Tfs.VsCommon { public partial class WrapperForWorkspace { - public int Checkin(IPendingChange[] changes, string comment, ICheckinNote checkinNote, IEnumerable workItemChanges, + public int Checkin(IPendingChange[] changes, string comment, string author, ICheckinNote checkinNote, IEnumerable workItemChanges, TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn) { var checkinParameters = new WorkspaceCheckInParameters(_bridge.Unwrap(changes), comment) @@ -14,9 +14,12 @@ namespace Sep.Git.Tfs.VsCommon CheckinNotes = _bridge.Unwrap(checkinNote), AssociatedWorkItems = _bridge.Unwrap(workItemChanges), PolicyOverride = ToTfs(policyOverrideInfo), - OverrideGatedCheckIn = overrideGatedCheckIn + OverrideGatedCheckIn = overrideGatedCheckIn, }; + if (author != null) + checkinParameters.Author = author; + return _workspace.CheckIn(checkinParameters); } } diff --git a/GitTfs.Vs2008/Wrappers.Vs2008.cs b/GitTfs.Vs2008/Wrappers.Vs2008.cs index 1f9dd04d..4355daf1 100644 --- a/GitTfs.Vs2008/Wrappers.Vs2008.cs +++ b/GitTfs.Vs2008/Wrappers.Vs2008.cs @@ -6,9 +6,20 @@ namespace Sep.Git.Tfs.VsCommon { public partial class WrapperForWorkspace { - public int Checkin(IPendingChange[] changes, string comment, ICheckinNote checkinNote, IEnumerable workItemChanges, + public int Checkin(IPendingChange[] changes, string comment, string author, ICheckinNote checkinNote, IEnumerable workItemChanges, TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn) { + if (author != null) + { + return _workspace.CheckIn( + _bridge.Unwrap(changes), + comment, + author, + _bridge.Unwrap(checkinNote), + _bridge.Unwrap(workItemChanges), + ToTfs(policyOverrideInfo)); + } + return _workspace.CheckIn( _bridge.Unwrap(changes), comment, @@ -16,5 +27,7 @@ namespace Sep.Git.Tfs.VsCommon _bridge.Unwrap(workItemChanges), ToTfs(policyOverrideInfo)); } + + } } diff --git a/GitTfs.Vs2010/Wrappers.Vs2010.cs b/GitTfs.Vs2010/Wrappers.Vs2010.cs index 9d735d3d..aebd82d7 100644 --- a/GitTfs.Vs2010/Wrappers.Vs2010.cs +++ b/GitTfs.Vs2010/Wrappers.Vs2010.cs @@ -6,8 +6,8 @@ namespace Sep.Git.Tfs.VsCommon { public partial class WrapperForWorkspace { - public int Checkin(IPendingChange[] changes, string comment, ICheckinNote checkinNote, IEnumerable workItemChanges, - TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn) + public int Checkin(IPendingChange[] changes, string comment, string author, ICheckinNote checkinNote, IEnumerable workItemChanges, + TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn) { var checkinParameters = new WorkspaceCheckInParameters(_bridge.Unwrap(changes), comment) { @@ -17,7 +17,12 @@ namespace Sep.Git.Tfs.VsCommon OverrideGatedCheckIn = overrideGatedCheckIn }; + if (author != null) + checkinParameters.Author = author; + return _workspace.CheckIn(checkinParameters); } + + } } diff --git a/GitTfs.VsCommon/Wrappers.cs b/GitTfs.VsCommon/Wrappers.cs index c2a3a775..50c1455e 100644 --- a/GitTfs.VsCommon/Wrappers.cs +++ b/GitTfs.VsCommon/Wrappers.cs @@ -416,8 +416,9 @@ namespace Sep.Git.Tfs.VsCommon _bridge.Unwrap(policyOverrideInfo.Failures)); } + public ICheckinEvaluationResult EvaluateCheckin(TfsCheckinEvaluationOptions options, IPendingChange[] allChanges, IPendingChange[] changes, - string comment, ICheckinNote checkinNote, IEnumerable workItemChanges) + string comment, string author, ICheckinNote checkinNote, IEnumerable workItemChanges) { return _bridge.Wrap(_workspace.EvaluateCheckin( _bridge.Convert(options), diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 7388ebb2..3aef6f5f 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -238,12 +238,17 @@ namespace Sep.Git.Tfs.VsFake throw new NotImplementedException(); } + public ICheckinEvaluationResult EvaluateCheckin(TfsCheckinEvaluationOptions options, IPendingChange[] allChanges, IPendingChange[] changes, string comment, string authors, ICheckinNote checkinNote, IEnumerable workItemChanges) + { + throw new NotImplementedException(); + } + public void Shelve(IShelveset shelveset, IPendingChange[] changes, TfsShelvingOptions options) { throw new NotImplementedException(); } - public int Checkin(IPendingChange[] changes, string comment, ICheckinNote checkinNote, IEnumerable workItemChanges, TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn) + public int Checkin(IPendingChange[] changes, string comment, string author, ICheckinNote checkinNote, IEnumerable workItemChanges, TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn) { throw new NotImplementedException(); } diff --git a/GitTfs/Commands/CheckinOptions.cs b/GitTfs/Commands/CheckinOptions.cs index 466fe4b5..a44ff34c 100644 --- a/GitTfs/Commands/CheckinOptions.cs +++ b/GitTfs/Commands/CheckinOptions.cs @@ -34,13 +34,14 @@ namespace Sep.Git.Tfs.Commands (reviewer) => { CheckinNotes["Performance Reviewer"] = reviewer; } }, { "no-gate", "Disables gated checkin.", v => { OverrideGatedCheckIn = true; } }, + { "A|authors=", "Path to an Authors file to map TFS users to Git users", v => AuthorsFilePath = v }, }; } } private List _workItemsToAssociate = new List(); private List _workItemsToResolve = new List(); - private Dictionary _checkinNotes = new Dictionary(); + private Dictionary _checkinNotes = new Dictionary(); public string CheckinComment { get; set; } // This can be extended to checkin when the $EDITOR is invoked. @@ -52,5 +53,9 @@ namespace Sep.Git.Tfs.Commands public List WorkItemsToAssociate { get { return _workItemsToAssociate; } } public List WorkItemsToResolve { get { return _workItemsToResolve; } } public Dictionary CheckinNotes { get { return _checkinNotes; } } + + public string AuthorsFilePath { get; set; } + public string AuthorTfsUserId { get; set; } + } } diff --git a/GitTfs/Commands/Rcheckin.cs b/GitTfs/Commands/Rcheckin.cs index e9bd96d9..70286342 100644 --- a/GitTfs/Commands/Rcheckin.cs +++ b/GitTfs/Commands/Rcheckin.cs @@ -158,7 +158,11 @@ namespace Sep.Git.Tfs.Commands string commitMessage = _checkinOptions.NoGenerateCheckinComment ? repo.GetCommitMessage(target) : repo.GetCommitMessage(target, tfsLatest); - var commitSpecificCheckinOptions = _checkinOptionsFactory.BuildCommitSpecificCheckinOptions(_checkinOptions, commitMessage); + + GitCommit commit = repo.GetCommit(target); + + var commitSpecificCheckinOptions = _checkinOptionsFactory.BuildCommitSpecificCheckinOptions(_checkinOptions, commitMessage, commit); + _stdout.WriteLine("Starting checkin of {0} '{1}'", target.Substring(0, 8), commitSpecificCheckinOptions.CheckinComment); long newChangesetId = tfsRemote.Checkin(target, parentChangeset, commitSpecificCheckinOptions); tfsRemote.FetchWithMerge(newChangesetId, gitParents); diff --git a/GitTfs/Core/CheckinPolicyEvaluator.cs b/GitTfs/Core/CheckinPolicyEvaluator.cs index 4d6d36eb..8d330be1 100644 --- a/GitTfs/Core/CheckinPolicyEvaluator.cs +++ b/GitTfs/Core/CheckinPolicyEvaluator.cs @@ -9,7 +9,7 @@ namespace Sep.Git.Tfs.Core public CheckinPolicyEvaluationResult EvaluateCheckin(IWorkspace workspace, IPendingChange[] pendingChanges, string comment, ICheckinNote checkinNote, IEnumerable workItemInfo) { var result = workspace.EvaluateCheckin(TfsCheckinEvaluationOptions.All, pendingChanges, - pendingChanges, comment, checkinNote, + pendingChanges, comment, null, checkinNote, workItemInfo); return new CheckinPolicyEvaluationResult(result); } diff --git a/GitTfs/Core/GitCommit.cs b/GitTfs/Core/GitCommit.cs index e2adcd83..eab3bdfd 100644 --- a/GitTfs/Core/GitCommit.cs +++ b/GitTfs/Core/GitCommit.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using LibGit2Sharp; @@ -37,5 +38,14 @@ namespace Sep.Git.Tfs.Core } } } + + public Tuple AuthorAndEmail + { + get + { + return new Tuple(_commit.Author.Name, _commit.Author.Email); + } + } } -} \ No newline at end of file +} + diff --git a/GitTfs/Core/TfsInterop/IWorkspace.cs b/GitTfs/Core/TfsInterop/IWorkspace.cs index 24b0e4a8..87580c8d 100644 --- a/GitTfs/Core/TfsInterop/IWorkspace.cs +++ b/GitTfs/Core/TfsInterop/IWorkspace.cs @@ -5,9 +5,9 @@ namespace Sep.Git.Tfs.Core.TfsInterop public interface IWorkspace { IPendingChange[] GetPendingChanges(); - ICheckinEvaluationResult EvaluateCheckin(TfsCheckinEvaluationOptions options, IPendingChange[] allChanges, IPendingChange[] changes, string comment, ICheckinNote checkinNote, IEnumerable workItemChanges); + ICheckinEvaluationResult EvaluateCheckin(TfsCheckinEvaluationOptions options, IPendingChange[] allChanges, IPendingChange[] changes, string comment, string author, ICheckinNote checkinNote, IEnumerable workItemChanges); void Shelve(IShelveset shelveset, IPendingChange[] changes, TfsShelvingOptions options); - int Checkin(IPendingChange[] changes, string comment, ICheckinNote checkinNote, IEnumerable workItemChanges, TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn); + int Checkin(IPendingChange[] changes, string comment, string author, ICheckinNote checkinNote, IEnumerable workItemChanges, TfsPolicyOverrideInfo policyOverrideInfo, bool overrideGatedCheckIn); int PendAdd(string path); int PendEdit(string path); int PendDelete(string path); diff --git a/GitTfs/Core/TfsWorkspace.cs b/GitTfs/Core/TfsWorkspace.cs index 49f0bdec..b199eae7 100644 --- a/GitTfs/Core/TfsWorkspace.cs +++ b/GitTfs/Core/TfsWorkspace.cs @@ -104,7 +104,7 @@ namespace Sep.Git.Tfs.Core } var policyOverride = GetPolicyOverrides(options, checkinProblems.Result); - var newChangeset = _workspace.Checkin(pendingChanges, options.CheckinComment, checkinNote, workItemInfos, policyOverride, options.OverrideGatedCheckIn); + var newChangeset = _workspace.Checkin(pendingChanges, options.CheckinComment, options.AuthorTfsUserId, checkinNote, workItemInfos, policyOverride, options.OverrideGatedCheckIn); if (newChangeset == 0) { throw new GitTfsException("Checkin failed!"); diff --git a/GitTfs/Util/AuthorsFile.cs b/GitTfs/Util/AuthorsFile.cs index bc249963..7948b277 100644 --- a/GitTfs/Util/AuthorsFile.cs +++ b/GitTfs/Util/AuthorsFile.cs @@ -9,27 +9,96 @@ namespace Sep.Git.Tfs.Util { public class Author { - public string Name; - public string Email; + public Author(string tfsUserId, string name, string email) + { + TfsUserId = tfsUserId; + _gitAuthor = new Tuple(name, email); + _gitUserId = BuildGitUserId(_gitAuthor); + } + + public string Name + { + get + { + return _gitAuthor.Item1; + } + } + + + public string Email + { + get + { + return _gitAuthor.Item2; + } + } + + public string TfsUserId { get; set; } + + public string GitUserId + { + get + { + return _gitUserId; + } + } + + // we only use the trimmed email address as identity + // (dictionary key) to avoid mismatches because of + // active directory name formatting rules. + public static string BuildGitUserId(string email) + { + return email.Trim(); + } + + public static string BuildGitUserId(Tuple gitUser) + { + return BuildGitUserId(gitUser.Item2); + } + + #region (private) + private Tuple _gitAuthor; + private string _gitUserId; + #endregion } [StructureMapSingleton] public class AuthorsFile { - private readonly Dictionary authors = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _authorsByTfsUserId = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _authorsByGitUserId = new Dictionary(StringComparer.OrdinalIgnoreCase); public AuthorsFile() - { } + {} public Dictionary Authors { get { - return this.authors; + return this._authorsByTfsUserId; } } + + public Dictionary AuthorsByGitUserId + { + get + { + return this._authorsByGitUserId; + } + } + + + public Author FindAuthor(Tuple gitUser) + { + string key = Author.BuildGitUserId(gitUser); + Author a; + return _authorsByGitUserId.TryGetValue(key, out a) ? a : null; + } + + + // The first time a tfs user id or a git id is encountered, it is used as lookup key. public void Parse(TextReader authorsFileStream) { if (authorsFileStream != null) @@ -50,11 +119,19 @@ namespace Sep.Git.Tfs.Util } else { - if (!authors.ContainsKey(match.Groups[1].Value)) - { - //git svn doesn't trim, but maybe this should? - authors.Add(match.Groups[1].Value, new Author() { Name = match.Groups[2].Value, Email = match.Groups[3].Value }); - } + //git svn doesn't trim, but maybe this should? + string tfsUserId = match.Groups[1].Value;//.Trim(); + string name = match.Groups[2].Value;//.Trim(); + string email = match.Groups[3].Value;//.Trim(); + + Author a = new Author(tfsUserId, name, email); + + if (!_authorsByTfsUserId.ContainsKey(a.TfsUserId)) + _authorsByTfsUserId.Add(a.TfsUserId, a); + + if (!_authorsByGitUserId.ContainsKey(a.GitUserId)) + _authorsByGitUserId.Add(a.GitUserId, a); + } } line = authorsFileStream.ReadLine(); diff --git a/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs b/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs index e0e3af00..5e652988 100644 --- a/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs +++ b/GitTfs/Util/CommitSpecificCheckinOptionsFactory.cs @@ -1,7 +1,9 @@ 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 { @@ -38,6 +40,23 @@ namespace Sep.Git.Tfs.Util return customCheckinOptions; } + public CheckinOptions BuildCommitSpecificCheckinOptions(CheckinOptions sourceCheckinOptions, string commitMessage, GitCommit commit) + { + var customCheckinOptions = Clone(sourceCheckinOptions); + + customCheckinOptions.CheckinComment = commitMessage; + + ProcessWorkItemCommands(customCheckinOptions, writer); + + ProcessCheckinNoteCommands(customCheckinOptions, writer); + + ProcessForceCommand(customCheckinOptions, writer); + + ProcessAuthor(customCheckinOptions, writer, commit); + + return customCheckinOptions; + } + private CheckinOptions Clone(CheckinOptions source) { CheckinOptions clone = new CheckinOptions(); @@ -50,6 +69,8 @@ namespace Sep.Git.Tfs.Util clone.OverrideGatedCheckIn = source.OverrideGatedCheckIn; clone.WorkItemsToAssociate.AddRange(source.WorkItemsToAssociate); clone.WorkItemsToResolve.AddRange(source.WorkItemsToResolve); + clone.AuthorsFilePath = source.AuthorsFilePath; + clone.AuthorTfsUserId = source.AuthorTfsUserId; foreach (var note in source.CheckinNotes) { clone.CheckinNotes[note.Key] = note.Value; @@ -108,6 +129,8 @@ namespace Sep.Git.Tfs.Util checkinOptions.CheckinComment = GitTfsConstants.TfsReviewerRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); } + + private void ProcessForceCommand(CheckinOptions checkinOptions, TextWriter writer) { MatchCollection workitemMatches; @@ -124,5 +147,32 @@ namespace Sep.Git.Tfs.Util checkinOptions.CheckinComment = GitTfsConstants.TfsForceRegex.Replace(checkinOptions.CheckinComment, "").Trim(' ', '\r', '\n'); } } + + + + private void ProcessAuthor(CheckinOptions checkinOptions, TextWriter writer, GitCommit commit) + { + if (checkinOptions.AuthorsFilePath == null) + { + writer.WriteLine("Author file was not set."); + return; + } + + // get authors file FIXME + AuthorsFile af = new AuthorsFile(); + TextReader tr = new StreamReader(checkinOptions.AuthorsFilePath); + af.Parse(tr); + + Author a = af.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/GitTfsTest/Core/TfsWorkspaceTests.cs b/GitTfsTest/Core/TfsWorkspaceTests.cs index ec085698..2f7051b9 100644 --- a/GitTfsTest/Core/TfsWorkspaceTests.cs +++ b/GitTfsTest/Core/TfsWorkspaceTests.cs @@ -61,6 +61,7 @@ namespace Sep.Git.Tfs.Test.Core Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything)) .Return(checkinEvaluationResult); @@ -68,6 +69,7 @@ namespace Sep.Git.Tfs.Test.Core workspace.Expect(w => w.Checkin( Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything, Arg.Is.Anything, @@ -109,6 +111,7 @@ namespace Sep.Git.Tfs.Test.Core Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything)) .Return(checkinEvaluationResult); @@ -116,6 +119,7 @@ namespace Sep.Git.Tfs.Test.Core workspace.Expect(w => w.Checkin( Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything, Arg.Is.Anything, @@ -160,6 +164,7 @@ namespace Sep.Git.Tfs.Test.Core Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything)) .Return(checkinEvaluationResult); @@ -167,6 +172,7 @@ namespace Sep.Git.Tfs.Test.Core workspace.Expect(w => w.Checkin( Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything, Arg.Is.Anything, @@ -212,6 +218,7 @@ namespace Sep.Git.Tfs.Test.Core Arg.Is.Anything, Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything)) .Return(checkinEvaluationResult); @@ -219,6 +226,7 @@ namespace Sep.Git.Tfs.Test.Core workspace.Expect(w => w.Checkin( Arg.Is.Anything, Arg.Is.Anything, + Arg.Is.Anything, Arg.Is.Anything, Arg>.Is.Anything, Arg.Is.Anything, diff --git a/GitTfsTest/Util/AuthorsFileUnitTest.cs b/GitTfsTest/Util/AuthorsFileUnitTest.cs index b512caee..5227f612 100644 --- a/GitTfsTest/Util/AuthorsFileUnitTest.cs +++ b/GitTfsTest/Util/AuthorsFileUnitTest.cs @@ -53,19 +53,19 @@ namespace Sep.Git.Tfs.Test.Util [Fact] public void AuthorsFileMultiLineRecord() { - string author = + string author = @"Domain\Test.User = Test User Domain\Different.User = Three Name User < TestUser@example.com >"; AuthorsFile authFile = new AuthorsFile(); authFile.Parse(new StreamReader(new MemoryStream(Encoding.ASCII.GetBytes(author)))); Assert.NotNull(authFile.Authors); Assert.Equal(2, authFile.Authors.Count); - + Assert.True(authFile.Authors.ContainsKey(@"Domain\Test.User")); Author auth = authFile.Authors[@"Domain\Test.User"]; Assert.Equal("Test User", auth.Name); Assert.Equal("TestUser@example.com", auth.Email); - + Assert.True(authFile.Authors.ContainsKey(@"Domain\Different.User")); auth = authFile.Authors[@"Domain\Different.User"]; Assert.Equal("Three Name User", auth.Name); @@ -167,5 +167,114 @@ differentDomain\Blåbærsyltetøy = ÆØÅ User <ÆØÅ@example.com>"; Assert.Equal(1, authFile.Authors.Count); Assert.True(authFile.Authors.ContainsKey(@"domain\Blåbærsyltetøy")); } + + + private string _MergeAuthorsIntoString(string[] authors) + { + string result = @""; + + for (int i = 0; i < authors.Length; ++i) + { + if (i != 0) + result += Environment.NewLine; + + result += authors[i]; + } + return result; + } + + + private AuthorsFile _SetupAuthorsFile(string[] authors) + { + string author = _MergeAuthorsIntoString(authors); + + AuthorsFile authFile = new AuthorsFile(); + authFile.Parse(new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(author)))); + return authFile; + } + + [Fact] + public void AuthorsFileMultipleUsers() + { + string[] authors = { + @"Domain\Test.User = Test User ", + @"Domain\Different.User = Three Name User < DiffUser@example.com >", + @"Domain\Yet.Another.User = Mr. 3 " + }; + + AuthorsFile authFile = _SetupAuthorsFile(authors); + + Assert.Equal(authors.Length, authFile.Authors.Count); + Assert.Equal(authors.Length, authFile.AuthorsByGitUserId.Count); + + // contains all tfs users + Assert.True(authFile.Authors.ContainsKey(@"Domain\Test.User")); + Assert.True(authFile.Authors.ContainsKey(@"Domain\Different.User")); + Assert.True(authFile.Authors.ContainsKey(@"Domain\Yet.Another.User")); + + // contains all git users + Assert.True(authFile.AuthorsByGitUserId.ContainsKey(Author.BuildGitUserId("TestUser@example.com"))); + Assert.True(authFile.AuthorsByGitUserId.ContainsKey(Author.BuildGitUserId("DiffUser@example.com"))); + Assert.True(authFile.AuthorsByGitUserId.ContainsKey(Author.BuildGitUserId("YAU@example.com"))); + } + + + [Fact] + public void AuthorsFileDifferentIdsForUsersWithSameEmail() + { + string[] authors = { + @"Domain\Test.User = Test User ", + @"Domain\Different.User = Three Name User < TestUser@example.com >", + @"Domain\Yet.Another.User = Mr. 3 " + }; + + AuthorsFile authFile = _SetupAuthorsFile(authors); + + Assert.NotNull(authFile.AuthorsByGitUserId); + + // multiple users with the same email -> 3 tfs users, 1 git user + Assert.Equal(authors.Length, authFile.Authors.Count); + Assert.Equal(1, authFile.AuthorsByGitUserId.Count); + + // contains all tfs users + Assert.True(authFile.Authors.ContainsKey(@"Domain\Test.User")); + Assert.True(authFile.Authors.ContainsKey(@"Domain\Different.User")); + Assert.True(authFile.Authors.ContainsKey(@"Domain\Yet.Another.User")); + + // contains all git users + Assert.True(authFile.AuthorsByGitUserId.ContainsKey(Author.BuildGitUserId("TestUser@example.com"))); + + // return the first tfs user id in the authors file when multiple + // match to the same git id. + Tuple git_author = new Tuple("Test User", "TestUser@example.com"); + Author a = authFile.FindAuthor(git_author); + Assert.NotNull(a); + Assert.Equal(a.TfsUserId, @"Domain\Test.User"); + + } + + + + [Fact] + public void AuthorsFileFindAuthors() + { + string[] authors = { + @"Domain\Test.User = Test User ", + @"Domain\Different.User = Three Name User < TestUser@example.com >", + @"Domain\Yet.Another.User = Mr. 3 " + }; + + AuthorsFile authFile = _SetupAuthorsFile(authors); + + // find existing author + Tuple gitAuthor = new Tuple("Test User", "TestUser@example.com"); + Author existingAuthor = authFile.FindAuthor(gitAuthor); + Assert.NotNull(existingAuthor); + + // try to find unknown author + Tuple gitUnknownAuthor = new Tuple("Test User", "TestFailUser@example.com"); + Author unknownAuthor = authFile.FindAuthor(gitUnknownAuthor); + Assert.Null(unknownAuthor); + } } }