From 455b5f2536168942fd471c6f8cc3591b6665419a Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Mon, 10 Sep 2012 23:18:36 -0400 Subject: [PATCH 01/67] New config reader for tfs remotes. --- GitTfs/Core/Ext.cs | 7 ++ GitTfs/Core/GitRepository.cs | 81 ++---------------- GitTfs/Core/GitTfsRemote.cs | 11 ++- GitTfs/Core/RemoteConfigReader.cs | 42 ++++++++++ GitTfs/Core/RemoteInfo.cs | 20 +++++ GitTfs/GitTfs.csproj | 4 +- GitTfsTest/Core/RemoteConfigReaderTests.cs | 97 ++++++++++++++++++++++ GitTfsTest/GitTfsTest.csproj | 3 +- lib/libgit2sharp | 2 +- 9 files changed, 187 insertions(+), 80 deletions(-) create mode 100644 GitTfs/Core/RemoteConfigReader.cs create mode 100644 GitTfs/Core/RemoteInfo.cs create mode 100644 GitTfsTest/Core/RemoteConfigReaderTests.cs diff --git a/GitTfs/Core/Ext.cs b/GitTfs/Core/Ext.cs index 2458a274..07c6c0db 100644 --- a/GitTfs/Core/Ext.cs +++ b/GitTfs/Core/Ext.cs @@ -52,6 +52,13 @@ namespace Sep.Git.Tfs.Core } } + public static T GetOrAdd(this Dictionary dictionary, K key) where T : new() + { + if (!dictionary.ContainsKey(key)) + dictionary.Add(key, new T()); + return dictionary[key]; + } + public static T FirstOr(this IEnumerable e, T defaultValue) { foreach (var x in e) return x; diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index 3117afd2..bfcc4c1b 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -18,14 +18,16 @@ namespace Sep.Git.Tfs.Core private static readonly Regex configLineRegex = new Regex("^tfs-remote\\.(?[^.]+)\\.(?[^.=]+)=(?.*)$"); private IDictionary _cachedRemotes; private Repository _repository; + private RemoteConfigReader _remoteConfigReader; - public GitRepository(TextWriter stdout, string gitDir, IContainer container, Globals globals) + public GitRepository(TextWriter stdout, string gitDir, IContainer container, Globals globals, RemoteConfigReader remoteConfigReader) : base(stdout, container) { _container = container; _globals = globals; GitDir = gitDir; _repository = new LibGit2Sharp.Repository(GitDir); + _remoteConfigReader = remoteConfigReader; } ~GitRepository() @@ -94,9 +96,8 @@ namespace Sep.Git.Tfs.Core private IDictionary ReadTfsRemotes() { - var remotes = new Dictionary(); - CommandOutputPipe(stdout => ParseRemoteConfig(stdout, remotes), "config", "--list"); - return remotes; + _repository.Config.Set("tfs.touch", "1"); // reload configuration, because `git tfs init` and `git tfs clone` use Process.Start to update the config, so _repository's copy is out of date. + return _remoteConfigReader.Load(_repository.Config).Select(x => _container.With(x).With(this).GetInstance()).ToDictionary(x => x.Id); } public bool HasRemote(string remoteId) @@ -150,78 +151,6 @@ namespace Sep.Git.Tfs.Core this.SetConfig(_globals.RemoteConfigKey(remoteId, subkey), value); } - private void ParseRemoteConfig(TextReader stdout, IDictionary remotes) - { - string line; - while ((line = stdout.ReadLine()) != null) - { - TryParseRemoteConfigLine(line, remotes); - } - foreach (var gitTfsRemotePair in remotes) - { - var remote = gitTfsRemotePair.Value; - remote.EnsureTfsAuthenticated(); - } - } - - private void TryParseRemoteConfigLine(string line, IDictionary remotes) - { - var match = configLineRegex.Match(line); - if (match.Success) - { - var key = match.Groups["key"].Value; - var value = match.Groups["value"].Value; - var remoteId = match.Groups["id"].Value; - var remote = remotes.ContainsKey(remoteId) - ? remotes[remoteId] - : (remotes[remoteId] = BuildRemote(remoteId)); - try - { - SetRemoteConfigValue(remote, key, value); - } - catch(Exception e) - { - throw new GitTfsException("Malformed value for " + key + ": " + value, e); - } - } - } - - private IGitTfsRemote BuildRemote(string id) - { - var remote = _container.GetInstance(); - remote.Repository = this; - remote.Id = id; - return remote; - } - - private void SetRemoteConfigValue(IGitTfsRemote remote, string key, string value) - { - switch (key) - { - case "url": - remote.TfsUrl = value; - break; - case "legacy-urls": - remote.Tfs.LegacyUrls = value.Split(','); - break; - case "repository": - remote.TfsRepositoryPath = value; - break; - case "ignore-paths": - remote.IgnoreRegexExpression = value; - break; - case "username": - remote.TfsUsername = value; - break; - case "password": - remote.TfsPassword = value; - break; - case "autotag": - remote.Autotag = bool.Parse(value); - break; - } - } - public GitCommit GetCommit(string commitish) { return new GitCommit(_repository.Lookup(commitish)); diff --git a/GitTfs/Core/GitTfsRemote.cs b/GitTfs/Core/GitTfsRemote.cs index a9de6d68..d6465da6 100644 --- a/GitTfs/Core/GitTfsRemote.cs +++ b/GitTfs/Core/GitTfsRemote.cs @@ -20,12 +20,21 @@ namespace Sep.Git.Tfs.Core private long? maxChangesetId; private string maxCommitHash; - public GitTfsRemote(RemoteOptions remoteOptions, Globals globals, ITfsHelper tfsHelper, TextWriter stdout) + public GitTfsRemote(RemoteInfo info, IGitRepository repository, RemoteOptions remoteOptions, Globals globals, ITfsHelper tfsHelper, TextWriter stdout) { this.remoteOptions = remoteOptions; this.globals = globals; this.stdout = stdout; Tfs = tfsHelper; + Repository = repository; + + Id = info.Id; + TfsUrl = info.Url; + TfsRepositoryPath = info.Repository; + TfsUsername = info.Username; + TfsPassword = info.Password; + IgnoreRegexExpression = info.IgnoreRegex; + Autotag = info.Autotag; } public void EnsureTfsAuthenticated() diff --git a/GitTfs/Core/RemoteConfigReader.cs b/GitTfs/Core/RemoteConfigReader.cs new file mode 100644 index 00000000..972ef4ce --- /dev/null +++ b/GitTfs/Core/RemoteConfigReader.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using LibGit2Sharp; + +namespace Sep.Git.Tfs.Core +{ + public class RemoteConfigReader + { + public IEnumerable Load(IEnumerable config) + { + var remotes = new Dictionary(); + foreach (var entry in config) + { + var keyParts = entry.Key.Split('.'); + if (keyParts.Length == 3 && keyParts[0] == "tfs-remote") + { + var id = keyParts[1]; + var key = keyParts[2]; + var remote = remotes.GetOrAdd(id); + remote.Id = id; + if (key == "url") + remote.Url = entry.Value; + else if (key == "repository") + remote.Repository = entry.Value; + else if (key == "username") + remote.Username = entry.Value; + else if (key == "password") + remote.Password = entry.Value; + else if (key == "ignore-paths") + remote.IgnoreRegex = entry.Value; + else if (key == "no-meta-data") + remote.NoMetaData = entry.Value.ToLower() != "false"; + else if (key == "legacy-urls") + remote.Aliases = entry.Value.Split(','); + else if (key == "autotag") + remote.Autotag = bool.Parse(entry.Value); + } + } + return remotes.Values; + } + } +} diff --git a/GitTfs/Core/RemoteInfo.cs b/GitTfs/Core/RemoteInfo.cs new file mode 100644 index 00000000..022be7db --- /dev/null +++ b/GitTfs/Core/RemoteInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sep.Git.Tfs.Core +{ + public class RemoteInfo + { + public string Id { get; set; } + public string Url { get; set; } + public string Repository { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string IgnoreRegex { get; set; } + public bool NoMetaData { get; set; } + public IEnumerable Aliases { get; set; } + public bool Autotag { get; set; } + } +} diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index e3df7337..ba75d70a 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -136,6 +136,8 @@ + + @@ -252,4 +254,4 @@ - + \ No newline at end of file diff --git a/GitTfsTest/Core/RemoteConfigReaderTests.cs b/GitTfsTest/Core/RemoteConfigReaderTests.cs new file mode 100644 index 00000000..c126520c --- /dev/null +++ b/GitTfsTest/Core/RemoteConfigReaderTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LibGit2Sharp; +using Sep.Git.Tfs.Core; +using Xunit; + +namespace Sep.Git.Tfs.Test.Core +{ + public class RemoteConfigReaderTests + { + RemoteConfigReader _reader = new RemoteConfigReader(); + Dictionary _config = new Dictionary(); + + IEnumerable _gitConfig { get { return _config.Select(x => new ConfigurationEntry(x.Key, x.Value)); } } + IEnumerable _remotes { get { return _reader.Load(_gitConfig); } } + RemoteInfo _firstRemote { get { return _remotes.FirstOrDefault(); } } + + public RemoteConfigReaderTests() + { + // Set some normal-ish config params. This makes sure that there is no barfing on extra config entries. + _config["core.autocrlf"] = "true"; + _config["ui.color"] = "auto"; + } + + [Fact] + public void NoConfig() + { + Assert.Empty(_remotes); + } + + void SetUpMinimalRemote() + { + _config["tfs-remote.default.url"] = "http://server/path"; + _config["tfs-remote.default.repository"] = "$/project"; + } + + [Fact] + public void MinimalRemote() + { + SetUpMinimalRemote(); + Assert.Equal(1, _remotes.Count()); + Assert.Equal("default", _firstRemote.Id); + Assert.Equal("http://server/path", _firstRemote.Url); + Assert.Equal("$/project", _firstRemote.Repository); + Assert.Null(_firstRemote.Username); + Assert.Null(_firstRemote.Password); + Assert.Null(_firstRemote.IgnoreRegex); + Assert.False(_firstRemote.NoMetaData); + } + + void SetUpCompleteRemote() + { + SetUpMinimalRemote(); + _config["tfs-remote.default.username"] = "theuser"; + _config["tfs-remote.default.password"] = "thepassword"; + _config["tfs-remote.default.no-meta-data"] = "true"; + _config["tfs-remote.default.ignore-paths"] = "ignorethis.zip"; + _config["tfs-remote.default.legacy-urls"] = "http://old:8080/,http://other/"; + _config["tfs-remote.default.autotag"] = "true"; + } + + [Fact] + public void RemoteWithEverything() + { + SetUpCompleteRemote(); + Assert.Equal("default", _firstRemote.Id); + Assert.Equal("http://server/path", _firstRemote.Url); + Assert.Equal("$/project", _firstRemote.Repository); + Assert.Equal("theuser", _firstRemote.Username); + Assert.Equal("thepassword", _firstRemote.Password); + Assert.Equal("ignorethis.zip", _firstRemote.IgnoreRegex); + Assert.True(_firstRemote.NoMetaData); + Assert.Equal(new string[] { "http://old:8080/", "http://other/" }, _firstRemote.Aliases); + Assert.True(_firstRemote.Autotag); + } + + [Fact] + public void NoMetaDataCanBe_1() + { + SetUpMinimalRemote(); + _config["tfs-remote.default.no-meta-data"] = "1"; + Assert.True(_firstRemote.NoMetaData); + } + + [Fact] + public void NoMetaDataCanBe_false() + { + SetUpMinimalRemote(); + _config["tfs-remote.default.no-meta-data"] = "false"; + Assert.False(_firstRemote.NoMetaData); + } + } + + // also a GitTfsHistoryLoaderTest + // also a GitTfsConfig +} diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index 5f852539..f8142d74 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -91,6 +91,7 @@ + @@ -149,4 +150,4 @@ --> - + \ No newline at end of file diff --git a/lib/libgit2sharp b/lib/libgit2sharp index c1743ba7..1e868421 160000 --- a/lib/libgit2sharp +++ b/lib/libgit2sharp @@ -1 +1 @@ -Subproject commit c1743ba7f9547f53d565222f628fbc7138a65973 +Subproject commit 1e868421e9004f6fa48eea6301f4c86a5aeaedd4 From 6dd896eecded0540d873a87b3b2c8ddd98246e24 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 16 Oct 2012 09:53:56 -0400 Subject: [PATCH 02/67] Unit tests for the configuration for a new tfs remote. --- GitTfs/Commands/Bootstrap.cs | 11 ++- GitTfs/Commands/Init.cs | 10 ++- GitTfs/Core/GitRepository.cs | 63 +++++++------- GitTfs/Core/IGitRepository.cs | 3 +- ...nfigReader.cs => RemoteConfigConverter.cs} | 18 +++- GitTfs/Core/RemoteInfo.cs | 7 ++ GitTfs/GitTfs.csproj | 2 +- .../Core/RemoteConfigConverterDumpTests.cs | 87 +++++++++++++++++++ ...s.cs => RemoteConfigConverterLoadTests.cs} | 9 +- GitTfsTest/GitTfsTest.csproj | 6 +- GitTfsTest/Integration/IntegrationHelper.cs | 5 ++ 11 files changed, 173 insertions(+), 48 deletions(-) rename GitTfs/Core/{RemoteConfigReader.cs => RemoteConfigConverter.cs} (59%) create mode 100644 GitTfsTest/Core/RemoteConfigConverterDumpTests.cs rename GitTfsTest/Core/{RemoteConfigReaderTests.cs => RemoteConfigConverterLoadTests.cs} (93%) diff --git a/GitTfs/Commands/Bootstrap.cs b/GitTfs/Commands/Bootstrap.cs index 55c5ac90..db835428 100644 --- a/GitTfs/Commands/Bootstrap.cs +++ b/GitTfs/Commands/Bootstrap.cs @@ -44,8 +44,15 @@ namespace Sep.Git.Tfs.Commands if (parent.Remote.IsDerived) { var remoteId = GetRemoteId(parent); - _globals.Repository.CreateTfsRemote(remoteId, parent, _remoteOptions); - _stdout.WriteLine("-> new remote " + remoteId); + var remote = _globals.Repository.CreateTfsRemote(new RemoteInfo + { + Id = remoteId, + Url = parent.Remote.TfsUrl, + Repository = parent.Remote.TfsRepositoryPath, + RemoteOptions = _remoteOptions, + }); + remote.UpdateRef(parent.GitCommit, parent.ChangesetId); + _stdout.WriteLine("-> new remote " + remote.Id); } else { diff --git a/GitTfs/Commands/Init.cs b/GitTfs/Commands/Init.cs index be7f7a50..01829aeb 100644 --- a/GitTfs/Commands/Init.cs +++ b/GitTfs/Commands/Init.cs @@ -81,9 +81,13 @@ namespace Sep.Git.Tfs.Commands private void GitTfsInit(string tfsUrl, string tfsRepositoryPath) { - gitHelper.SetConfig("core.autocrlf", "false"); - gitHelper.SetConfig("core.ignorecase", "false"); - globals.Repository.CreateTfsRemote(globals.RemoteId, tfsUrl, tfsRepositoryPath, remoteOptions); + globals.Repository.CreateTfsRemote(new RemoteInfo + { + Id = globals.RemoteId, + Url = tfsUrl, + Repository = tfsRepositoryPath, + RemoteOptions = remoteOptions, + }); } } diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index bfcc4c1b..0934ac0d 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -18,9 +18,9 @@ namespace Sep.Git.Tfs.Core private static readonly Regex configLineRegex = new Regex("^tfs-remote\\.(?[^.]+)\\.(?[^.=]+)=(?.*)$"); private IDictionary _cachedRemotes; private Repository _repository; - private RemoteConfigReader _remoteConfigReader; + private RemoteConfigConverter _remoteConfigReader; - public GitRepository(TextWriter stdout, string gitDir, IContainer container, Globals globals, RemoteConfigReader remoteConfigReader) + public GitRepository(TextWriter stdout, string gitDir, IContainer container, Globals globals, RemoteConfigConverter remoteConfigReader) : base(stdout, container) { _container = container; @@ -94,10 +94,40 @@ namespace Sep.Git.Tfs.Core return _cachedRemotes ?? (_cachedRemotes = ReadTfsRemotes()); } + public IGitTfsRemote CreateTfsRemote(RemoteInfo remote) + { + if (HasRemote(remote.Id)) + throw new GitTfsException("A remote with id \"" + remote.Id + "\" already exists."); + + // These help the new (if it's new) git repository to behave more sanely. + _repository.Config.Set("core.autocrlf", "false"); + _repository.Config.Set("core.ignorecase", "false"); + + foreach (var entry in _remoteConfigReader.Dump(remote)) + { + if (entry.Value != null) + { + _repository.Config.Set(entry.Key, entry.Value); + } + else + { + _repository.Config.Unset(entry.Key); + } + } + + return _cachedRemotes[remote.Id] = BuildRemote(remote); + } + private IDictionary ReadTfsRemotes() { + // does this need to ensuretfsauthenticated? _repository.Config.Set("tfs.touch", "1"); // reload configuration, because `git tfs init` and `git tfs clone` use Process.Start to update the config, so _repository's copy is out of date. - return _remoteConfigReader.Load(_repository.Config).Select(x => _container.With(x).With(this).GetInstance()).ToDictionary(x => x.Id); + return _remoteConfigReader.Load(_repository.Config).Select(x => BuildRemote(x)).ToDictionary(x => x.Id); + } + + private IGitTfsRemote BuildRemote(RemoteInfo remoteInfo) + { + return _container.With(remoteInfo).With(this).GetInstance(); } public bool HasRemote(string remoteId) @@ -119,33 +149,6 @@ namespace Sep.Git.Tfs.Core } } - public void CreateTfsRemote(string remoteId, TfsChangesetInfo tfsHead, RemoteOptions remoteOptions) - { - CreateTfsRemote(remoteId, tfsHead.Remote.TfsUrl, tfsHead.Remote.TfsRepositoryPath, remoteOptions); - ReadTfsRemote(remoteId).UpdateRef(tfsHead.GitCommit, tfsHead.ChangesetId); - } - - public void CreateTfsRemote(string remoteId, string tfsUrl, string tfsRepositoryPath, RemoteOptions remoteOptions) - { - if (HasRemote(remoteId)) - throw new GitTfsException("A remote with id \"" + remoteId + "\" already exists."); - - if (remoteOptions != null) - { - if (remoteOptions.NoMetaData) SetTfsConfig(remoteId, "no-meta-data", 1); - if (remoteOptions.IgnoreRegex != null) SetTfsConfig(remoteId, "ignore-paths", remoteOptions.IgnoreRegex); - if (!string.IsNullOrEmpty(remoteOptions.Username)) SetTfsConfig(remoteId, "username", remoteOptions.Username); - if (!string.IsNullOrEmpty(remoteOptions.Password)) SetTfsConfig(remoteId, "password", remoteOptions.Password); - } - - SetTfsConfig(remoteId, "url", tfsUrl); - SetTfsConfig(remoteId, "repository", tfsRepositoryPath); - SetTfsConfig(remoteId, "fetch", "refs/remotes/" + remoteId + "/master"); - - Directory.CreateDirectory(Path.Combine(GitDir, "tfs")); - _cachedRemotes = null; - } - private void SetTfsConfig(string remoteId, string subkey, object value) { this.SetConfig(_globals.RemoteConfigKey(remoteId, subkey), value); diff --git a/GitTfs/Core/IGitRepository.cs b/GitTfs/Core/IGitRepository.cs index cbe69d4f..d7eebf49 100644 --- a/GitTfs/Core/IGitRepository.cs +++ b/GitTfs/Core/IGitRepository.cs @@ -9,8 +9,7 @@ namespace Sep.Git.Tfs.Core string GitDir { get; set; } IEnumerable ReadAllTfsRemotes(); IGitTfsRemote ReadTfsRemote(string remoteId); - void /*or IGitTfsRemote*/ CreateTfsRemote(string remoteId, string tfsUrl, string tfsRepositoryPath, RemoteOptions remoteOptions); - void /*or IGitTfsRemote*/ CreateTfsRemote(string remoteId, TfsChangesetInfo tfsHead, RemoteOptions remoteOptions); + IGitTfsRemote CreateTfsRemote(RemoteInfo remoteInfo); bool HasRemote(string remoteId); void MoveTfsRefForwardIfNeeded(IGitTfsRemote remote); IEnumerable GetLastParentTfsCommits(string head); diff --git a/GitTfs/Core/RemoteConfigReader.cs b/GitTfs/Core/RemoteConfigConverter.cs similarity index 59% rename from GitTfs/Core/RemoteConfigReader.cs rename to GitTfs/Core/RemoteConfigConverter.cs index 972ef4ce..c7956fc2 100644 --- a/GitTfs/Core/RemoteConfigReader.cs +++ b/GitTfs/Core/RemoteConfigConverter.cs @@ -4,7 +4,7 @@ using LibGit2Sharp; namespace Sep.Git.Tfs.Core { - public class RemoteConfigReader + public class RemoteConfigConverter { public IEnumerable Load(IEnumerable config) { @@ -38,5 +38,21 @@ namespace Sep.Git.Tfs.Core } return remotes.Values; } + + public IEnumerable Dump(RemoteInfo remote) + { + if (!string.IsNullOrWhiteSpace(remote.Id)) + { + var prefix = "tfs-remote." + remote.Id + "."; + yield return new ConfigurationEntry(prefix + "url", remote.Url); + yield return new ConfigurationEntry(prefix + "repository", remote.Repository); + yield return new ConfigurationEntry(prefix + "username", remote.Username); + yield return new ConfigurationEntry(prefix + "password", remote.Password); + yield return new ConfigurationEntry(prefix + "ignore-paths", remote.IgnoreRegex); + yield return new ConfigurationEntry(prefix + "no-meta-data", remote.NoMetaData ? "true" : null); + yield return new ConfigurationEntry(prefix + "legacy-urls", remote.Aliases == null ? null : string.Join(",", remote.Aliases)); + yield return new ConfigurationEntry(prefix + "autotag", remote.Autotag ? "true" : null); + } + } } } diff --git a/GitTfs/Core/RemoteInfo.cs b/GitTfs/Core/RemoteInfo.cs index 022be7db..70c64a22 100644 --- a/GitTfs/Core/RemoteInfo.cs +++ b/GitTfs/Core/RemoteInfo.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Sep.Git.Tfs.Commands; namespace Sep.Git.Tfs.Core { @@ -16,5 +17,11 @@ namespace Sep.Git.Tfs.Core public bool NoMetaData { get; set; } public IEnumerable Aliases { get; set; } public bool Autotag { get; set; } + + public RemoteOptions RemoteOptions + { + get { return new RemoteOptions { IgnoreRegex = IgnoreRegex, NoMetaData = NoMetaData, Username = Username, Password = Password }; } + set { IgnoreRegex = value.IgnoreRegex; NoMetaData = value.NoMetaData; Username = value.Username; Password = value.Password; } + } } } diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index ba75d70a..5ea2972e 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -136,7 +136,7 @@ - + diff --git a/GitTfsTest/Core/RemoteConfigConverterDumpTests.cs b/GitTfsTest/Core/RemoteConfigConverterDumpTests.cs new file mode 100644 index 00000000..60716710 --- /dev/null +++ b/GitTfsTest/Core/RemoteConfigConverterDumpTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using LibGit2Sharp; +using Xunit; +using Sep.Git.Tfs.Core; + +namespace Sep.Git.Tfs.Test.Core +{ + public class RemoteConfigConverterDumpTests + { + RemoteConfigConverter _dumper = new RemoteConfigConverter(); + + [Fact] + public void DumpsNothingWithNoId() + { + var remote = new RemoteInfo { Url = "http://server/path", Repository = "$/Project" }; + Assert.Empty(_dumper.Dump(remote)); + } + + [Fact] + public void DumpsNothingWithBlankId() + { + var remote = new RemoteInfo { Id = " ", Url = "http://server/path", Repository = "$/Project" }; + Assert.Empty(_dumper.Dump(remote)); + } + + [Fact] + public void DumpsMinimalRemote() + { + var remote = new RemoteInfo { Id = "default", Url = "http://server/path", Repository = "$/Project" }; + var config = _dumper.Dump(remote); + AssertContainsConfig("tfs-remote.default.url", "http://server/path", config); + AssertContainsConfig("tfs-remote.default.repository", "$/Project", config); + AssertContainsConfig("tfs-remote.default.username", null, config); + AssertContainsConfig("tfs-remote.default.password", null, config); + AssertContainsConfig("tfs-remote.default.no-meta-data", null, config); + AssertContainsConfig("tfs-remote.default.ignore-paths", null, config); + AssertContainsConfig("tfs-remote.default.legacy-urls", null, config); + AssertContainsConfig("tfs-remote.default.autotag", null, config); + } + + [Fact] + public void DumpsCompleteRemote() + { + var remote = new RemoteInfo { + Id = "default", + Url = "http://server/path", + Repository = "$/Project", + Username = "user", + Password = "pass", + IgnoreRegex = "abc", + NoMetaData = true, + Autotag = true, + Aliases = new string[] { "http://abc", "http://def" }, + }; + var config = _dumper.Dump(remote); + AssertContainsConfig("tfs-remote.default.url", "http://server/path", config); + AssertContainsConfig("tfs-remote.default.repository", "$/Project", config); + AssertContainsConfig("tfs-remote.default.username", "user", config); + AssertContainsConfig("tfs-remote.default.password", "pass", config); + AssertContainsConfig("tfs-remote.default.no-meta-data", "true", config); + AssertContainsConfig("tfs-remote.default.ignore-paths", "abc", config); + AssertContainsConfig("tfs-remote.default.legacy-urls", "http://abc,http://def", config); + AssertContainsConfig("tfs-remote.default.autotag", "true", config); + } + + private void AssertContainsConfig(string key, string value, IEnumerable configs) + { + Assert.Contains(new ConfigurationEntry(key, value), configs, comparer); + } + + static IEqualityComparer comparer = new ConfigurationEntryComparer(); + + class ConfigurationEntryComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(ConfigurationEntry x, ConfigurationEntry y) + { + return x.Key == y.Key && x.Value == y.Value; + } + + int IEqualityComparer.GetHashCode(ConfigurationEntry obj) + { + return obj.Key.GetHashCode(); + } + } + } +} diff --git a/GitTfsTest/Core/RemoteConfigReaderTests.cs b/GitTfsTest/Core/RemoteConfigConverterLoadTests.cs similarity index 93% rename from GitTfsTest/Core/RemoteConfigReaderTests.cs rename to GitTfsTest/Core/RemoteConfigConverterLoadTests.cs index c126520c..4a127c3e 100644 --- a/GitTfsTest/Core/RemoteConfigReaderTests.cs +++ b/GitTfsTest/Core/RemoteConfigConverterLoadTests.cs @@ -7,16 +7,16 @@ using Xunit; namespace Sep.Git.Tfs.Test.Core { - public class RemoteConfigReaderTests + public class RemoteConfigConverterLoadTests { - RemoteConfigReader _reader = new RemoteConfigReader(); + RemoteConfigConverter _reader = new RemoteConfigConverter(); Dictionary _config = new Dictionary(); IEnumerable _gitConfig { get { return _config.Select(x => new ConfigurationEntry(x.Key, x.Value)); } } IEnumerable _remotes { get { return _reader.Load(_gitConfig); } } RemoteInfo _firstRemote { get { return _remotes.FirstOrDefault(); } } - public RemoteConfigReaderTests() + public RemoteConfigConverterLoadTests() { // Set some normal-ish config params. This makes sure that there is no barfing on extra config entries. _config["core.autocrlf"] = "true"; @@ -91,7 +91,4 @@ namespace Sep.Git.Tfs.Test.Core Assert.False(_firstRemote.NoMetaData); } } - - // also a GitTfsHistoryLoaderTest - // also a GitTfsConfig } diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index f8142d74..557bf87e 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -12,7 +12,6 @@ GitTfsTest v4.0 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 3.5 @@ -91,7 +90,8 @@ - + + @@ -150,4 +150,4 @@ --> - \ No newline at end of file + diff --git a/GitTfsTest/Integration/IntegrationHelper.cs b/GitTfsTest/Integration/IntegrationHelper.cs index 72424916..d2bf8dbe 100644 --- a/GitTfsTest/Integration/IntegrationHelper.cs +++ b/GitTfsTest/Integration/IntegrationHelper.cs @@ -121,11 +121,16 @@ namespace Sep.Git.Tfs.Test.Integration startInfo.Arguments = "/c git tfs --debug " + String.Join(" ", args); startInfo.UseShellExecute = false; startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; Console.WriteLine("PATH: " + startInfo.EnvironmentVariables["Path"]); Console.WriteLine(">> " + startInfo.FileName + " " + startInfo.Arguments); var process = Process.Start(startInfo); + var stderr = ""; + process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => stderr += e.Data); + process.BeginErrorReadLine(); Console.Out.Write(process.StandardOutput.ReadToEnd()); process.WaitForExit(); + if (!string.IsNullOrWhiteSpace(stderr)) Console.Out.WriteLine("stderr:\n" + stderr); } private string CurrentBuildPath From ea752c62099160c4e2ac82794aa89521a9d36b3c Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 16 Oct 2012 16:43:01 -0400 Subject: [PATCH 03/67] Add tests for bootstrap. --- GitTfs/Core/TfsInterop/TfsPlugin.cs | 2 +- GitTfsTest/GitTfsTest.csproj | 3 +- GitTfsTest/Integration/BootstrapTests.cs | 51 +++++++++++++++++++++ GitTfsTest/Integration/CloneTests.cs | 7 +-- GitTfsTest/Integration/IntegrationHelper.cs | 46 +++++++++++++++++-- 5 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 GitTfsTest/Integration/BootstrapTests.cs diff --git a/GitTfs/Core/TfsInterop/TfsPlugin.cs b/GitTfs/Core/TfsInterop/TfsPlugin.cs index 91e5b254..1e1f1b8d 100644 --- a/GitTfs/Core/TfsInterop/TfsPlugin.cs +++ b/GitTfs/Core/TfsInterop/TfsPlugin.cs @@ -59,7 +59,7 @@ namespace Sep.Git.Tfs.Core.TfsInterop { public IEnumerable InnerExceptions { get; private set; } - public PluginLoaderException(string message, IEnumerable failures) : base(message, failures.Last()) + public PluginLoaderException(string message, IEnumerable failures) : base(message, failures.LastOrDefault()) { InnerExceptions = failures; } diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index 557bf87e..857465ea 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -95,6 +95,7 @@ + @@ -150,4 +151,4 @@ --> - + \ No newline at end of file diff --git a/GitTfsTest/Integration/BootstrapTests.cs b/GitTfsTest/Integration/BootstrapTests.cs new file mode 100644 index 00000000..145db8b7 --- /dev/null +++ b/GitTfsTest/Integration/BootstrapTests.cs @@ -0,0 +1,51 @@ +using System; +using Xunit; + +namespace Sep.Git.Tfs.Test.Integration +{ + public class BootstrapTests : IDisposable + { + IntegrationHelper h = new IntegrationHelper(); + + public void Dispose() + { + h.Dispose(); + } + + [Fact] + public void BootstrapWithNoRemotes() + { + h.SetupGitRepo("repo", g => + { + g.Commit("A sample commit."); + }); + h.RunIn("repo", "bootstrap"); + h.AssertNoRef("repo", "tfs/default"); + } + + [Fact] + public void BootstrapWithARemoteAtHead() + { + string c1 = null; + h.SetupGitRepo("repo", g => + { + c1 = g.Commit("A sample commit from TFS.\n\ngit-tfs-id: [http://server/tfs]$/MyProject;C1"); + }); + h.RunIn("repo", "bootstrap"); + h.AssertRef("repo", "tfs/default", c1); + } + + [Fact] + public void BootstrapWithARemoteAsAParentOfHead() + { + string c1 = null; + h.SetupGitRepo("repo", g => + { + c1 = g.Commit("A sample commit from TFS.\n\ngit-tfs-id: [http://server/tfs]$/MyProject;C1"); + g.Commit("Another sample commit."); + }); + h.RunIn("repo", "bootstrap"); + h.AssertRef("repo", "tfs/default", c1); + } + } +} diff --git a/GitTfsTest/Integration/CloneTests.cs b/GitTfsTest/Integration/CloneTests.cs index b40bde38..1d92a40b 100644 --- a/GitTfsTest/Integration/CloneTests.cs +++ b/GitTfsTest/Integration/CloneTests.cs @@ -9,12 +9,7 @@ namespace Sep.Git.Tfs.Test.Integration // This will cause the hashes to differ on computers in different time zones. public class CloneTests : IDisposable { - IntegrationHelper h; - - public CloneTests() - { - h = new IntegrationHelper(); - } + IntegrationHelper h = new IntegrationHelper(); public void Dispose() { diff --git a/GitTfsTest/Integration/IntegrationHelper.cs b/GitTfsTest/Integration/IntegrationHelper.cs index d2bf8dbe..e4258678 100644 --- a/GitTfsTest/Integration/IntegrationHelper.cs +++ b/GitTfsTest/Integration/IntegrationHelper.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; +using LibGit2Sharp; using Sep.Git.Tfs.Core; using Sep.Git.Tfs.Core.TfsInterop; using Sep.Git.Tfs.VsFake; @@ -49,6 +50,33 @@ namespace Sep.Git.Tfs.Test.Integration #endregion + #region set up a git repository + + public void SetupGitRepo(string path, Action buildIt) + { + using (var repo = Repository.Init(Path.Combine(Workdir, path))) + buildIt(new RepoBuilder(repo)); + } + + public class RepoBuilder + { + private Repository _repo; + + public RepoBuilder(Repository repo) + { + _repo = repo; + } + + public string Commit(string message) + { + File.WriteAllText(Path.Combine(_repo.Info.WorkingDirectory, "README.txt"), message); + _repo.Index.Stage("README.txt"); + return _repo.Commit(message).Id.Sha; + } + } + + #endregion + #region set up vsfake script public string FakeScript @@ -71,7 +99,7 @@ namespace Sep.Git.Tfs.Test.Integration public FakeChangesetBuilder Changeset(int changesetId, string message, DateTime checkinDate) { - var changeset =new ScriptedChangeset + var changeset = new ScriptedChangeset { Id = changesetId, Comment = message, @@ -110,11 +138,12 @@ namespace Sep.Git.Tfs.Test.Integration public string TfsUrl { get { return "http://does/not/matter"; } } - public void Run(params string[] args) + public void RunIn(string pathInWorkdir, params string[] args) { var startInfo = new ProcessStartInfo(); - startInfo.WorkingDirectory = Workdir; + startInfo.WorkingDirectory = Path.Combine(Workdir, pathInWorkdir); startInfo.EnvironmentVariables["GIT_TFS_CLIENT"] = "Fake"; + if (!File.Exists(FakeScript)) File.WriteAllText(FakeScript, ""); startInfo.EnvironmentVariables[Script.EnvVar] = FakeScript; startInfo.EnvironmentVariables["Path"] = CurrentBuildPath + ";" + Environment.GetEnvironmentVariable("Path"); startInfo.FileName = "cmd"; @@ -133,6 +162,11 @@ namespace Sep.Git.Tfs.Test.Integration if (!string.IsNullOrWhiteSpace(stderr)) Console.Out.WriteLine("stderr:\n" + stderr); } + public void Run(params string[] args) + { + RunIn(".", args); + } + private string CurrentBuildPath { get @@ -153,8 +187,14 @@ namespace Sep.Git.Tfs.Test.Integration Assert.True(Directory.Exists(Path.Combine(path, ".git")), path + " should have a .git dir inside of it"); } + public void AssertNoRef(string repodir, string gitref) + { + AssertEqual(null, RevParse(repodir, gitref), "Expected no ref " + gitref); + } + public void AssertRef(string repodir, string gitref, string expectedSha) { + Assert.NotNull(expectedSha); AssertEqual(expectedSha, RevParse(repodir, gitref), "Expected " + gitref + " to be " + expectedSha); } From 4d97b715424723f44d08bcba83ca4975b70cee22 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 20 Dec 2012 16:24:59 -0500 Subject: [PATCH 04/67] Use a custom workspace, if desired. --- GitTfs.VsCommon/TfsHelper.Common.cs | 1 + GitTfs.VsFake/TfsHelper.VsFake.cs | 2 ++ GitTfs/Core/GitRepository.cs | 5 +++++ GitTfs/Core/GitTfsRemote.cs | 2 +- GitTfs/Core/IGitRepository.cs | 1 + GitTfs/GitTfs.cs | 1 + 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/GitTfs.VsCommon/TfsHelper.Common.cs b/GitTfs.VsCommon/TfsHelper.Common.cs index 8b78f893..fe9a485f 100644 --- a/GitTfs.VsCommon/TfsHelper.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.Common.cs @@ -139,6 +139,7 @@ namespace Sep.Git.Tfs.VsCommon public void WithWorkspace(string localDirectory, IGitTfsRemote remote, TfsChangesetInfo versionToFetch, Action action) { + Trace.WriteLine("Setting up a TFS workspace at " + localDirectory); var workspace = GetWorkspace(localDirectory, remote.TfsRepositoryPath); try { diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 7be32532..f774e987 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using Sep.Git.Tfs.Commands; @@ -185,6 +186,7 @@ namespace Sep.Git.Tfs.VsFake public void WithWorkspace(string directory, IGitTfsRemote remote, TfsChangesetInfo versionToFetch, Action action) { + Trace.WriteLine("Setting up a TFS workspace at " + directory); var fakeWorkspace = new FakeWorkspace(directory, remote.TfsRepositoryPath); var workspace = new TfsWorkspace(fakeWorkspace, directory, _stdout, versionToFetch, remote, null, this, null); action(workspace); diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index bebbaeed..43ae9fb7 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -52,6 +52,11 @@ namespace Sep.Git.Tfs.Core gitCommand.WorkingDirectory = Path.Combine(gitCommand.WorkingDirectory, WorkingCopySubdir); } + public string GetConfig(string key) + { + return _repository.Config.Get(key, null); + } + public IEnumerable ReadAllTfsRemotes() { return GetTfsRemotes().Values; diff --git a/GitTfs/Core/GitTfsRemote.cs b/GitTfs/Core/GitTfsRemote.cs index 92eca97a..de86d15f 100644 --- a/GitTfs/Core/GitTfsRemote.cs +++ b/GitTfs/Core/GitTfsRemote.cs @@ -114,7 +114,7 @@ namespace Sep.Git.Tfs.Core { get { - return Path.Combine(Dir, "workspace"); + return Repository.GetConfig("git-tfs.workspace-dir") ?? Path.Combine(Dir, "workspace"); } } diff --git a/GitTfs/Core/IGitRepository.cs b/GitTfs/Core/IGitRepository.cs index f06686bb..a27e5453 100644 --- a/GitTfs/Core/IGitRepository.cs +++ b/GitTfs/Core/IGitRepository.cs @@ -7,6 +7,7 @@ namespace Sep.Git.Tfs.Core public interface IGitRepository : IGitHelpers { string GitDir { get; set; } + string GetConfig(string key); IEnumerable ReadAllTfsRemotes(); IGitTfsRemote ReadTfsRemote(string remoteId); void /*or IGitTfsRemote*/ CreateTfsRemote(string remoteId, string tfsUrl, string tfsRepositoryPath, RemoteOptions remoteOptions); diff --git a/GitTfs/GitTfs.cs b/GitTfs/GitTfs.cs index 7f1d43cc..d8c3e3de 100644 --- a/GitTfs/GitTfs.cs +++ b/GitTfs/GitTfs.cs @@ -44,6 +44,7 @@ namespace Sep.Git.Tfs public void Main(GitTfsCommand command, IList unparsedArgs) { + Trace.WriteLine(_gitTfsVersionProvider.GetVersionString()); if(_globals.ShowHelp) { Environment.ExitCode = _help.ShowHelp(command); From 6577c3ca8d47ba02edf125d69b42d2a10f34645f Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Sun, 23 Dec 2012 00:25:51 +0100 Subject: [PATCH 05/67] Look for remotes only in local config list! --- GitTfs/Core/GitRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index 43ae9fb7..31bf414a 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -100,7 +100,7 @@ namespace Sep.Git.Tfs.Core private IDictionary ReadTfsRemotes() { var remotes = new Dictionary(); - CommandOutputPipe(stdout => ParseRemoteConfig(stdout, remotes), "config", "--list"); + CommandOutputPipe(stdout => ParseRemoteConfig(stdout, remotes), "config", "--list", "--local"); return remotes; } From 06242dcbec6e20bd4631d61a6c9361c2a5bae254 Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Tue, 18 Dec 2012 12:53:56 +0100 Subject: [PATCH 06/67] Add -r option for rebase in 'pull' command --- GitTfs/Commands/Pull.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GitTfs/Commands/Pull.cs b/GitTfs/Commands/Pull.cs index 7f0a7fde..b28ce40c 100644 --- a/GitTfs/Commands/Pull.cs +++ b/GitTfs/Commands/Pull.cs @@ -21,7 +21,7 @@ namespace Sep.Git.Tfs.Commands get { return fetch.OptionSet - .Add("rebase", "rebase your modifications on tfs changes", v => _shouldRebase = v != null ); + .Add("r|rebase", "rebase your modifications on tfs changes", v => _shouldRebase = v != null); } } From 4375934e17df0fc3553f5625e0b752efa11fd906 Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Fri, 21 Dec 2012 11:20:36 +0100 Subject: [PATCH 07/67] Add a command 'clean-workspace-local' to clean workspace directory +add this command when launch 'cleanup' command --- GitTfs/Commands/Cleanup.cs | 9 +++- GitTfs/Commands/CleanupWorkspaceLocal.cs | 59 ++++++++++++++++++++++++ GitTfs/Core/DerivedGitTfsRemote.cs | 5 ++ GitTfs/Core/GitTfsRemote.cs | 16 +++++++ GitTfs/Core/IGitTfsRemote.cs | 1 + GitTfs/GitTfs.csproj | 1 + 6 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 GitTfs/Commands/CleanupWorkspaceLocal.cs diff --git a/GitTfs/Commands/Cleanup.cs b/GitTfs/Commands/Cleanup.cs index 267a48ac..1d40fc6e 100644 --- a/GitTfs/Commands/Cleanup.cs +++ b/GitTfs/Commands/Cleanup.cs @@ -13,10 +13,12 @@ namespace Sep.Git.Tfs.Commands public class Cleanup : GitTfsCommand { private readonly CleanupWorkspaces _cleanupWorkspaces; + private readonly CleanupWorkspaceLocal _cleanupWorkspaceLocal; - public Cleanup(CleanupWorkspaces cleanupWorkspaces) + public Cleanup(CleanupWorkspaces cleanupWorkspaces, CleanupWorkspaceLocal cleanupWorkspaceLocal) { _cleanupWorkspaces = cleanupWorkspaces; + _cleanupWorkspaceLocal = cleanupWorkspaceLocal; } public OptionSet OptionSet @@ -27,7 +29,10 @@ namespace Sep.Git.Tfs.Commands public int Run() { - return Choose(_cleanupWorkspaces.Run()); + var result = Choose(_cleanupWorkspaces.Run()); + if (result != GitTfsExitCodes.OK) + return result; + return Choose(_cleanupWorkspaceLocal.Run()); } private int Choose(params int[] results) diff --git a/GitTfs/Commands/CleanupWorkspaceLocal.cs b/GitTfs/Commands/CleanupWorkspaceLocal.cs new file mode 100644 index 00000000..be5341b3 --- /dev/null +++ b/GitTfs/Commands/CleanupWorkspaceLocal.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using NDesk.Options; +using Sep.Git.Tfs.Core; +using StructureMap; + +namespace Sep.Git.Tfs.Commands +{ + [Pluggable("cleanup-workspace-local")] + [Description("cleanup-workspace-local [tfs-remote-id]...")] + [RequiresValidGitRepository] + public class CleanupWorkspaceLocal : GitTfsCommand + { + private readonly TextWriter _stdout; + private readonly Globals _globals; + private readonly CleanupOptions _cleanupOptions; + + public CleanupWorkspaceLocal(TextWriter stdout, Globals globals, CleanupOptions cleanupOptions) + { + _stdout = stdout; + _globals = globals; + _cleanupOptions = cleanupOptions; + } + + public OptionSet OptionSet + { + get { return _cleanupOptions.OptionSet; } + } + + public int Run() + { + _cleanupOptions.Init(); + foreach(var remote in _globals.Repository.ReadAllTfsRemotes()) + { + Cleanup(remote); + } + return GitTfsExitCodes.OK; + } + + public int Run(IList remoteIds) + { + _cleanupOptions.Init(); + foreach (var remoteId in remoteIds) + { + var remote = _globals.Repository.ReadTfsRemote(remoteId); + Cleanup(remote); + } + return GitTfsExitCodes.OK; + } + + private void Cleanup(IGitTfsRemote remote) + { + _stdout.WriteLine("Cleaning up workspaces directory for TFS remote " + remote.Id); + remote.CleanupWorkspaceDirectory(); + } + } +} diff --git a/GitTfs/Core/DerivedGitTfsRemote.cs b/GitTfs/Core/DerivedGitTfsRemote.cs index 1a329c82..00c2f265 100644 --- a/GitTfs/Core/DerivedGitTfsRemote.cs +++ b/GitTfs/Core/DerivedGitTfsRemote.cs @@ -207,6 +207,11 @@ namespace Sep.Git.Tfs.Core throw new NotImplementedException(); } + public void CleanupWorkspaceDirectory() + { + throw new NotImplementedException(); + } + public ITfsChangeset GetChangeset(long changesetId) { throw new NotImplementedException(); diff --git a/GitTfs/Core/GitTfsRemote.cs b/GitTfs/Core/GitTfsRemote.cs index de86d15f..978ce821 100644 --- a/GitTfs/Core/GitTfsRemote.cs +++ b/GitTfs/Core/GitTfsRemote.cs @@ -123,6 +123,22 @@ namespace Sep.Git.Tfs.Core Tfs.CleanupWorkspaces(WorkingDirectory); } + public void CleanupWorkspaceDirectory() + { + try + { + var allFiles = Directory.EnumerateFiles(WorkingDirectory, "*", SearchOption.AllDirectories); + foreach (var file in allFiles) + File.SetAttributes(file, File.GetAttributes(file) & ~FileAttributes.ReadOnly); + + Directory.Delete(WorkingDirectory, true); + } + catch (Exception ex) + { + Trace.WriteLine(ex.Message); + } + } + public bool ShouldSkip(string path) { return IsInDotGit(path) || diff --git a/GitTfs/Core/IGitTfsRemote.cs b/GitTfs/Core/IGitTfsRemote.cs index 3573d12f..50cf33b3 100644 --- a/GitTfs/Core/IGitTfsRemote.cs +++ b/GitTfs/Core/IGitTfsRemote.cs @@ -38,6 +38,7 @@ namespace Sep.Git.Tfs.Core /// long Checkin(string head, string parent, TfsChangesetInfo parentChangeset, CheckinOptions options); void CleanupWorkspace(); + void CleanupWorkspaceDirectory(); ITfsChangeset GetChangeset(long changesetId); void UpdateRef(string commitHash, long changesetId); void EnsureTfsAuthenticated(); diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index 24ddf38b..a235f4f8 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -135,6 +135,7 @@ + From 3fc13957dddbc38161d6050285e85ce68498a19a Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Sun, 30 Dec 2012 08:11:06 -0500 Subject: [PATCH 08/67] Fix an off-by-one. (Thanks, @daniellee! https://groups.google.com/forum/#!msg/git-tfs-dev/kiRE5FB5RKI/xh_sxkH-2H8J) --- GitTfs.VsFake/TfsHelper.VsFake.cs | 2 +- GitTfsTest/Integration/CloneTests.cs | 14 +++++++------- GitTfsTest/Integration/IntegrationHelper.cs | 4 +--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index f774e987..670a4ddd 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -56,7 +56,7 @@ namespace Sep.Git.Tfs.VsFake public IEnumerable GetChangesets(string path, long startVersion, GitTfsRemote remote) { - return TfsPlugin.Script.Changesets.Where(x => x.Id > startVersion).Select(x => BuildTfsChangeset(x, remote)); + return TfsPlugin.Script.Changesets.Where(x => x.Id >= startVersion).Select(x => BuildTfsChangeset(x, remote)); } private ITfsChangeset BuildTfsChangeset(ScriptedChangeset changeset, GitTfsRemote remote) diff --git a/GitTfsTest/Integration/CloneTests.cs b/GitTfsTest/Integration/CloneTests.cs index 5dcf3845..28c27b4b 100644 --- a/GitTfsTest/Integration/CloneTests.cs +++ b/GitTfsTest/Integration/CloneTests.cs @@ -26,7 +26,7 @@ namespace Sep.Git.Tfs.Test.Integration { } - [FactExceptOnUnix(Skip="eventually")] + [FactExceptOnUnix] public void ClonesEmptyProject() { h.SetupFake(r => @@ -36,7 +36,7 @@ namespace Sep.Git.Tfs.Test.Integration }); h.Run("clone", h.TfsUrl, "$/MyProject"); h.AssertGitRepo("MyProject"); - const string expectedSha = "tbd"; + const string expectedSha = "4053764b2868a2be71ae7f5f113ad84dff8a052a"; h.AssertRef("MyProject", "HEAD", expectedSha); h.AssertRef("MyProject", "master", expectedSha); h.AssertRef("MyProject", "tfs/default", expectedSha); @@ -57,7 +57,7 @@ namespace Sep.Git.Tfs.Test.Integration }); h.Run("clone", h.TfsUrl, "$/MyProject"); h.AssertGitRepo("MyProject"); - const string expectedSha = "72a03802ac5f864a40a9bee13608f85e0e2ad05b"; + const string expectedSha = "d64d883266eca65bede947c79529318718a0d8eb"; h.AssertRef("MyProject", "HEAD", expectedSha); h.AssertRef("MyProject", "master", expectedSha); h.AssertRef("MyProject", "tfs/default", expectedSha); @@ -78,7 +78,7 @@ namespace Sep.Git.Tfs.Test.Integration }); h.Run("clone", h.TfsUrl, "$/MyProject"); h.AssertGitRepo("MyProject"); - AssertRefs("ea7ed178fb4cce7f46d2c84b907a88fa9d194014"); + AssertRefs("4faa9a5f32e6af118b84071a537228d3f7da7d9d"); h.AssertFileInWorkspace("MyProject", "ÆØÅ/äöü.txt", "File contents"); } @@ -95,7 +95,7 @@ namespace Sep.Git.Tfs.Test.Integration }); h.Run("clone", h.TfsUrl, "$/MyProject"); h.AssertGitRepo("MyProject"); - AssertRefs("78f0490e22ae245a63238744de2d96f0675880a0"); + AssertRefs("5bd7660fa145ce0c38b5c279502478ce205a0cfb"); h.AssertFileInWorkspace("MyProject", "Folder/File.txt", "Blåbærsyltetøy er godt!"); } @@ -112,7 +112,7 @@ namespace Sep.Git.Tfs.Test.Integration }); h.Run("clone", h.TfsUrl, "$/MyProject"); h.AssertGitRepo("MyProject"); - AssertRefs("9a73fe007130ca91517283aafe1d442f406df973"); + AssertRefs("cd14e6e28abffd625412dae36d9d9659bf6cb68c"); h.AssertFileInWorkspace("MyProject", "Folder/File.txt", "File contents"); var expectedCommitMessage = new System.Text.StringBuilder(); @@ -148,7 +148,7 @@ namespace Sep.Git.Tfs.Test.Integration h.Run("clone", h.TfsUrl, "$/MyProject"); h.AssertGitRepo("MyProject"); h.AssertCleanWorkspace("MyProject"); - AssertRefs("70cdbdca83c3808e60bc1f8cde7e155055447df7"); + AssertRefs("175420603e41cd0175e3c25581754726bd21cb96"); } } } diff --git a/GitTfsTest/Integration/IntegrationHelper.cs b/GitTfsTest/Integration/IntegrationHelper.cs index d26fe08f..660b3349 100644 --- a/GitTfsTest/Integration/IntegrationHelper.cs +++ b/GitTfsTest/Integration/IntegrationHelper.cs @@ -173,9 +173,7 @@ namespace Sep.Git.Tfs.Test.Integration public void AssertEmptyWorkspace(string repodir) { var entries = new List(Directory.GetFileSystemEntries(Path.Combine(Workdir, repodir))); - entries.Remove("."); - entries.Remove(".."); - entries.Remove(".git"); + entries = entries.Where(f => Path.GetFileName(f) != ".git").ToList(); AssertEqual(new List(), entries, "entries in " + repodir); } From ae16316d654b463295956e244740559e3dd23c27 Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Sun, 30 Dec 2012 21:12:27 +0100 Subject: [PATCH 09/67] Run Cleanup only when previous succeed --- GitTfs/Commands/Cleanup.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/GitTfs/Commands/Cleanup.cs b/GitTfs/Commands/Cleanup.cs index 1d40fc6e..89dbfe85 100644 --- a/GitTfs/Commands/Cleanup.cs +++ b/GitTfs/Commands/Cleanup.cs @@ -26,18 +26,21 @@ namespace Sep.Git.Tfs.Commands get { return _cleanupWorkspaces.OptionSet; } } - public int Run() { - var result = Choose(_cleanupWorkspaces.Run()); - if (result != GitTfsExitCodes.OK) - return result; - return Choose(_cleanupWorkspaceLocal.Run()); + return RunAll(_cleanupWorkspaces.Run, _cleanupWorkspaceLocal.Run); } - private int Choose(params int[] results) + private int RunAll(params Func[] cleaners) { - return results.Where(x => x != GitTfsExitCodes.OK).FirstOr(GitTfsExitCodes.OK); + var result = GitTfsExitCodes.OK; + foreach (var cleaner in cleaners) + { + result = cleaner(); + if (result != GitTfsExitCodes.OK) + return result; + } + return GitTfsExitCodes.OK; } } } From 38fdab79c54a16663124436a92cb6f412db2b3b8 Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Sat, 29 Dec 2012 22:05:36 +0100 Subject: [PATCH 10/67] 'git tfs branch' now lists all branches ancestor or descendent to the remote.TfsRepositoryPath --- GitTfs.VsCommon/TfsHelper.Common.cs | 7 +- .../TfsHelper.PostVs2010.Common.cs | 40 ++++++++++ GitTfs.VsCommon/Wrappers.cs | 39 ++++++++++ GitTfs.VsFake/TfsHelper.VsFake.cs | 5 ++ GitTfs/Commands/Branch.cs | 77 +++++++++++++++++++ GitTfs/Core/IBranchVisitor.cs | 11 +++ GitTfs/Core/TfsInterop/IBranch.cs | 25 ++++++ GitTfs/Core/TfsInterop/IItem.cs | 5 ++ GitTfs/Core/TfsInterop/ITfsHelper.cs | 1 + GitTfs/GitTfs.csproj | 3 + 10 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 GitTfs/Commands/Branch.cs create mode 100644 GitTfs/Core/IBranchVisitor.cs create mode 100644 GitTfs/Core/TfsInterop/IBranch.cs diff --git a/GitTfs.VsCommon/TfsHelper.Common.cs b/GitTfs.VsCommon/TfsHelper.Common.cs index fe9a485f..7ef40a56 100644 --- a/GitTfs.VsCommon/TfsHelper.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.Common.cs @@ -20,7 +20,7 @@ namespace Sep.Git.Tfs.VsCommon public abstract class TfsHelperBase : ITfsHelper { private readonly TextWriter _stdout; - private readonly TfsApiBridge _bridge; + protected readonly TfsApiBridge _bridge; private readonly IContainer _container; public TfsHelperBase(TextWriter stdout, TfsApiBridge bridge, IContainer container) @@ -125,6 +125,11 @@ namespace Sep.Git.Tfs.VsCommon throw new NotImplementedException(); } + public virtual IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath) + { + throw new NotImplementedException(); + } + public virtual int GetRootChangesetForBranch(string tfsPathBranchToCreate, string tfsPathParentBranch = null) { throw new NotImplementedException(); diff --git a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs index 63b6c9c0..f6062819 100644 --- a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs @@ -4,10 +4,30 @@ using System.IO; using System.Linq; using Microsoft.TeamFoundation.VersionControl.Client; using Sep.Git.Tfs.Core; +using Sep.Git.Tfs.Core.TfsInterop; using StructureMap; namespace Sep.Git.Tfs.VsCommon { + public class BranchContainsPathVisitor : IBranchVisitor + { + private string searchPath; + + public BranchContainsPathVisitor(string searchPath) + { + this.searchPath = searchPath; + } + + public bool Found { get; private set; } + + public void Visit(IBranch childBranch, int level) + { + if (Found == false && childBranch.Path == searchPath) + { + Found = true; + } + } + } public abstract class TfsHelperVs2010Base : TfsHelperBase { @@ -23,6 +43,26 @@ namespace Sep.Git.Tfs.VsCommon return VersionControl.QueryRootBranchObjects(RecursionType.Full).Select(b => b.Properties.RootItem.Item); } + public override IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath) + { + var recursionType = RecursionType.Full; + var branches = VersionControl.QueryRootBranchObjects(recursionType) + .Where(b => b.Properties.RootItem.IsDeleted == false) + .ToList(); + + var roots = branches.Where(b => b.Properties.ParentBranch == null).ToList(); + var children = branches.Except(roots).ToList(); + + var wrapped = roots.Select(b => WrapperForBranchFactory.Wrap(b, children)).ToList(); + + return wrapped.FirstOrDefault(b => + { + var visitor = new BranchContainsPathVisitor(remoteTfsPath); + b.AcceptVisitor(visitor); + return visitor.Found; + }); + } + public override int GetRootChangesetForBranch(string tfsPathBranchToCreate, string tfsPathParentBranch = null) { if (!string.IsNullOrWhiteSpace(tfsPathParentBranch)) diff --git a/GitTfs.VsCommon/Wrappers.cs b/GitTfs.VsCommon/Wrappers.cs index a3301249..f2db43f5 100644 --- a/GitTfs.VsCommon/Wrappers.cs +++ b/GitTfs.VsCommon/Wrappers.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using Microsoft.TeamFoundation.Server; using Microsoft.TeamFoundation.VersionControl.Client; +using Sep.Git.Tfs.Core; using Sep.Git.Tfs.Core.TfsInterop; using Sep.Git.Tfs.Util; @@ -55,6 +56,44 @@ namespace Sep.Git.Tfs.VsCommon } } + public class WrapperForBranch : IBranch + { + private readonly BranchObject branch; + + public WrapperForBranch(BranchObject branch, IEnumerable children) + { + this.branch = branch; + this.ChildBranches = children; + } + + public BranchObject WrappedBranch { get { return this.branch; } } + + public IEnumerable ChildBranches { get; private set; } + + public DateTime DateCreated { get { return branch.DateCreated; } } + + public string Path { get { return branch.Properties.RootItem.Item; } } + + public override string ToString() + { + return string.Format("{0} [{1} children]", this.Path, this.ChildBranches.Count()); + } + } + + public class WrapperForBranchFactory + { + public static WrapperForBranch Wrap(BranchObject branch, IList related) + { + var children = + related.Where(c => c.Properties.ParentBranch.Item == branch.Properties.RootItem.Item) + .Select(c => WrapperForBranchFactory.Wrap(c, related)); + + var wrapper = new WrapperForBranch(branch, children); + + return wrapper; + } + } + public class WrapperForChangeset : WrapperFor, IChangeset { private readonly TfsApiBridge _bridge; diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 670a4ddd..78215883 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -356,6 +356,11 @@ namespace Sep.Git.Tfs.VsFake throw new NotImplementedException(); } + public IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath) + { + throw new NotImplementedException(); + } + public IEnumerable GetLabels(string tfsPathBranch) { throw new NotImplementedException(); diff --git a/GitTfs/Commands/Branch.cs b/GitTfs/Commands/Branch.cs new file mode 100644 index 00000000..2a5352ca --- /dev/null +++ b/GitTfs/Commands/Branch.cs @@ -0,0 +1,77 @@ +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using NDesk.Options; +using Sep.Git.Tfs.Core; +using Sep.Git.Tfs.Core.TfsInterop; +using StructureMap; + +namespace Sep.Git.Tfs.Commands +{ + [Pluggable("branch")] + [Description("branch")] + [RequiresValidGitRepository] + public class Branch : GitTfsCommand + { + private Globals globals; + private TextWriter stdout; + + public OptionSet OptionSet { get; private set; } + + public Branch(Globals globals, TextWriter stdout) + { + this.globals = globals; + this.stdout = stdout; + + this.OptionSet = globals.OptionSet; + } + + private class Visitor : IBranchVisitor + { + private readonly TextWriter _stdout; + private readonly string _targetPath; + + public Visitor(string targetPath, TextWriter writer) + { + _targetPath = targetPath; + _stdout = writer; + } + + public void Visit(IBranch branch, int level) + { + for (var i = 0; i < level-1; i++ ) + _stdout.Write(" | "); + + if (level > 0) + _stdout.Write(" +- "); + + _stdout.Write(branch.Path); + + if (branch.Path.Equals(_targetPath)) + _stdout.Write(" * "); + + _stdout.WriteLine(); + } + } + + public int Run() + { + stdout.WriteLine("TFS branches:"); + stdout.WriteLine(""); + + var repo = globals.Repository; + var remote = repo.ReadTfsRemote(GitTfsConstants.DefaultRepositoryId); + + var root = remote.Tfs.GetRootTfsBranchForRemotePath(remote.TfsRepositoryPath); + + var visitor = new Visitor(remote.TfsRepositoryPath, stdout); + + root.AcceptVisitor(visitor); + + stdout.WriteLine(""); + + return GitTfsExitCodes.OK; + } + } +} \ No newline at end of file diff --git a/GitTfs/Core/IBranchVisitor.cs b/GitTfs/Core/IBranchVisitor.cs new file mode 100644 index 00000000..8c72266b --- /dev/null +++ b/GitTfs/Core/IBranchVisitor.cs @@ -0,0 +1,11 @@ +using System.Linq; +using System.Collections.Generic; +using Sep.Git.Tfs.Core.TfsInterop; + +namespace Sep.Git.Tfs.Core +{ + public interface IBranchVisitor + { + void Visit(IBranch childBranch, int level); + } +} \ No newline at end of file diff --git a/GitTfs/Core/TfsInterop/IBranch.cs b/GitTfs/Core/TfsInterop/IBranch.cs new file mode 100644 index 00000000..845efbcc --- /dev/null +++ b/GitTfs/Core/TfsInterop/IBranch.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +namespace Sep.Git.Tfs.Core.TfsInterop +{ + public interface IBranch + { + IEnumerable ChildBranches { get; } + DateTime DateCreated { get; } + string Path { get; } + } + + public static class BranchExtensions + { + public static void AcceptVisitor(this IBranch branch, IBranchVisitor visitor, int level = 0) + { + visitor.Visit(branch, level); + foreach (var childBranch in branch.ChildBranches) + { + childBranch.AcceptVisitor(visitor, level + 1); + } + } + } +} \ No newline at end of file diff --git a/GitTfs/Core/TfsInterop/IItem.cs b/GitTfs/Core/TfsInterop/IItem.cs index 9aa90201..a544f2e8 100644 --- a/GitTfs/Core/TfsInterop/IItem.cs +++ b/GitTfs/Core/TfsInterop/IItem.cs @@ -15,6 +15,11 @@ namespace Sep.Git.Tfs.Core.TfsInterop TemporaryFile DownloadFile(); } + public interface IItemIdentifier + { + + } + public interface IItemDownloadStrategy { TemporaryFile DownloadFile(IItem item); diff --git a/GitTfs/Core/TfsInterop/ITfsHelper.cs b/GitTfs/Core/TfsInterop/ITfsHelper.cs index 1fc8fda1..394b4274 100644 --- a/GitTfs/Core/TfsInterop/ITfsHelper.cs +++ b/GitTfs/Core/TfsInterop/ITfsHelper.cs @@ -32,6 +32,7 @@ namespace Sep.Git.Tfs.Core.TfsInterop IEnumerable GetLabels(string tfsPathBranch); bool CanGetBranchInformation { get; } IEnumerable GetAllTfsBranchesOrderedByCreation(); + IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath); void EnsureAuthenticated(); } } \ No newline at end of file diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index a235f4f8..969f6699 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -130,6 +130,9 @@ Properties\Version.cs + + + From dca8c22fabb78827b3d91e3f5f0d6573719c280e Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Sun, 30 Dec 2012 19:37:19 +0100 Subject: [PATCH 11/67] Like `git branch` command, display local and remote branches --- GitTfs/Commands/Branch.cs | 47 ++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/GitTfs/Commands/Branch.cs b/GitTfs/Commands/Branch.cs index 2a5352ca..2665025e 100644 --- a/GitTfs/Commands/Branch.cs +++ b/GitTfs/Commands/Branch.cs @@ -16,15 +16,29 @@ namespace Sep.Git.Tfs.Commands { private Globals globals; private TextWriter stdout; + public string TfsUsername { get; set; } + public string TfsPassword { get; set; } + public bool DisplayRemotes { get; set; } - public OptionSet OptionSet { get; private set; } + public OptionSet OptionSet + { + get + { + return new OptionSet + { + { "r|remotes", "Display all the TFS branch of the current TFS server", v => DisplayRemotes = (v != null) }, + //{ "u|username=", "TFS username", v => TfsUsername = v }, + //{ "p|password=", "TFS password", v => TfsPassword = v }, + }; + } + } public Branch(Globals globals, TextWriter stdout) { this.globals = globals; this.stdout = stdout; - this.OptionSet = globals.OptionSet; + //this.OptionSet = globals.OptionSet; } private class Visitor : IBranchVisitor @@ -57,20 +71,33 @@ namespace Sep.Git.Tfs.Commands public int Run() { - stdout.WriteLine("TFS branches:"); - stdout.WriteLine(""); + if (DisplayRemotes) + { + stdout.WriteLine("TFS branches:"); + stdout.WriteLine(""); - var repo = globals.Repository; - var remote = repo.ReadTfsRemote(GitTfsConstants.DefaultRepositoryId); + var repo = globals.Repository; + var remote = repo.ReadTfsRemote(GitTfsConstants.DefaultRepositoryId); - var root = remote.Tfs.GetRootTfsBranchForRemotePath(remote.TfsRepositoryPath); + var root = remote.Tfs.GetRootTfsBranchForRemotePath(remote.TfsRepositoryPath); - var visitor = new Visitor(remote.TfsRepositoryPath, stdout); + var visitor = new Visitor(remote.TfsRepositoryPath, stdout); - root.AcceptVisitor(visitor); + root.AcceptVisitor(visitor); - stdout.WriteLine(""); + stdout.WriteLine(""); + return GitTfsExitCodes.OK; + } + + var tfsRemotes = globals.Repository.ReadAllTfsRemotes(); + stdout.WriteLine("Git-tfs remotes:"); + foreach (var remote in tfsRemotes) + { + stdout.WriteLine(); + stdout.WriteLine(" {0} -> {1} {2}", remote.Id, remote.TfsUrl, remote.TfsRepositoryPath); + stdout.WriteLine(" {0} - {1} @ {2}", remote.RemoteRef, remote.MaxCommitHash, remote.MaxChangesetId); + } return GitTfsExitCodes.OK; } } From a2a1916f705213dbd9d38eff7c9f7a0d629043da Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Sun, 30 Dec 2012 19:40:26 +0100 Subject: [PATCH 12/67] Correct clone message and init-branch using the good branches informations --- GitTfs.VsCommon/TfsHelper.Common.cs | 4 +-- .../TfsHelper.PostVs2010.Common.cs | 18 ++++++---- GitTfs.VsFake/TfsHelper.VsFake.cs | 4 +-- GitTfs/Commands/Clone.cs | 36 +++++++++---------- GitTfs/Commands/InitBranch.cs | 3 +- GitTfs/Core/TfsInterop/IBranch.cs | 10 ++++++ GitTfs/Core/TfsInterop/ITfsHelper.cs | 4 +-- GitTfsTest/Commands/InitBranchTest.cs | 31 ++++++++++++++-- 8 files changed, 76 insertions(+), 34 deletions(-) diff --git a/GitTfs.VsCommon/TfsHelper.Common.cs b/GitTfs.VsCommon/TfsHelper.Common.cs index 7ef40a56..fd6099e8 100644 --- a/GitTfs.VsCommon/TfsHelper.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.Common.cs @@ -120,12 +120,12 @@ namespace Sep.Git.Tfs.VsCommon public virtual bool CanGetBranchInformation { get { return false; } } - public virtual IEnumerable GetAllTfsBranchesOrderedByCreation() + public virtual IEnumerable GetAllTfsRootBranchesOrderedByCreation() { throw new NotImplementedException(); } - public virtual IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath) + public virtual IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath, bool searchExactPath = true) { throw new NotImplementedException(); } diff --git a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs index f6062819..c88f4ad1 100644 --- a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs @@ -12,17 +12,21 @@ namespace Sep.Git.Tfs.VsCommon public class BranchContainsPathVisitor : IBranchVisitor { private string searchPath; + private bool searchExactPath; - public BranchContainsPathVisitor(string searchPath) + public BranchContainsPathVisitor(string searchPath, bool searchExactPath) { this.searchPath = searchPath; + this.searchExactPath = searchExactPath; } public bool Found { get; private set; } public void Visit(IBranch childBranch, int level) { - if (Found == false && childBranch.Path == searchPath) + if (Found == false + && ((searchExactPath && searchPath.ToLower() == childBranch.Path.ToLower()) + || (!searchExactPath && searchPath.ToLower().IndexOf(childBranch.Path.ToLower()) == 0))) { Found = true; } @@ -38,12 +42,14 @@ namespace Sep.Git.Tfs.VsCommon public override bool CanGetBranchInformation { get { return true; } } - public override IEnumerable GetAllTfsBranchesOrderedByCreation() + public override IEnumerable GetAllTfsRootBranchesOrderedByCreation() { - return VersionControl.QueryRootBranchObjects(RecursionType.Full).Select(b => b.Properties.RootItem.Item); + return VersionControl.QueryRootBranchObjects(RecursionType.Full) + .Where(b => b.Properties.ParentBranch == null) + .Select(b => b.Properties.RootItem.Item); } - public override IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath) + public override IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath, bool searchExactPath = true) { var recursionType = RecursionType.Full; var branches = VersionControl.QueryRootBranchObjects(recursionType) @@ -57,7 +63,7 @@ namespace Sep.Git.Tfs.VsCommon return wrapped.FirstOrDefault(b => { - var visitor = new BranchContainsPathVisitor(remoteTfsPath); + var visitor = new BranchContainsPathVisitor(remoteTfsPath, searchExactPath); b.AcceptVisitor(visitor); return visitor.Found; }); diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 78215883..aed1efe4 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -351,12 +351,12 @@ namespace Sep.Git.Tfs.VsFake throw new NotImplementedException(); } - public IEnumerable GetAllTfsBranchesOrderedByCreation() + public IEnumerable GetAllTfsRootBranchesOrderedByCreation() { throw new NotImplementedException(); } - public IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath) + public IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath, bool searchExactPath = true) { throw new NotImplementedException(); } diff --git a/GitTfs/Commands/Clone.cs b/GitTfs/Commands/Clone.cs index 9c6757f1..90bd54c5 100644 --- a/GitTfs/Commands/Clone.cs +++ b/GitTfs/Commands/Clone.cs @@ -8,6 +8,7 @@ using NDesk.Options; using Sep.Git.Tfs.Core; using StructureMap; using Sep.Git.Tfs.Util; +using Sep.Git.Tfs.Core.TfsInterop; namespace Sep.Git.Tfs.Commands { @@ -101,30 +102,29 @@ namespace Sep.Git.Tfs.Commands var remote = globals.Repository.ReadTfsRemote(GitTfsConstants.DefaultRepositoryId); if (!remote.Tfs.CanGetBranchInformation) return; - var tfsBranchesPath = remote.Tfs.GetAllTfsBranchesOrderedByCreation().ToList(); + var tfsTrunkRepository = remote.Tfs.GetRootTfsBranchForRemotePath(tfsRepositoryPath, false); + if (tfsTrunkRepository == null) + { + var tfsRootBranches = remote.Tfs.GetAllTfsRootBranchesOrderedByCreation(); + var cloneMsg = " => If you want to manage branches with git-tfs, clone one of this branch instead :\n" + + " - " + tfsRootBranches.Aggregate((s1, s2) => s1 + @"\n - " + s2); + + if (withBranches) + throw new GitTfsException("error: cloning the whole repository or too high in the repository path doesn't permit to manage branches!\n" + cloneMsg); + stdout.WriteLine("warning: you are going to clone the whole repository or too high in the repository path !\n" + cloneMsg); + } + + var tfsBranchesPath = tfsTrunkRepository.GetAllChildren(); var tfsPathToClone = tfsRepositoryPath.TrimEnd('/').ToLower(); - var tfsTrunkRepositoryPath = tfsBranchesPath.First(); + var tfsTrunkRepositoryPath = tfsTrunkRepository.Path; if (tfsPathToClone != tfsTrunkRepositoryPath.ToLower()) { - if (tfsBranchesPath.Select(e=>e.ToLower()).Contains(tfsPathToClone)) + if (tfsBranchesPath.Select(e=>e.Path.ToLower()).Contains(tfsPathToClone)) stdout.WriteLine("info: you are going to clone a branch instead of the trunk ( {0} )\n" + " => If you want to manage branches with git-tfs, clone {0} with '--with-branches' option instead...)", tfsTrunkRepositoryPath); else - { - if (tfsTrunkRepositoryPath.ToLower().IndexOf(tfsPathToClone) == 0) - { - if (withBranches) - throw new GitTfsException("error: cloning the whole repository doesn't permit to manage branches!\n" - + " =>If you want to manage branches with git-tfs, clone " + tfsTrunkRepositoryPath + " instead..."); - stdout.WriteLine("warning: you are going to clone the whole repository!\n" - + " =>If you want to manage branches with git-tfs, clone " + tfsTrunkRepositoryPath + " instead..."); - } - else - { - stdout.WriteLine("warning: you are going to clone a subdirectory of a branch and won't be able to manage branches :(\n" - + " => If you want to manage branches with git-tfs, clone " + tfsTrunkRepositoryPath + " with '--with-branches' option instead...)"); - } - } + stdout.WriteLine("warning: you are going to clone a subdirectory of a branch and won't be able to manage branches :(\n" + + " => If you want to manage branches with git-tfs, clone " + tfsTrunkRepositoryPath + " with '--with-branches' option instead...)"); } } } diff --git a/GitTfs/Commands/InitBranch.cs b/GitTfs/Commands/InitBranch.cs index 7c27fe9a..28ec6fac 100644 --- a/GitTfs/Commands/InitBranch.cs +++ b/GitTfs/Commands/InitBranch.cs @@ -7,6 +7,7 @@ using NDesk.Options; using Sep.Git.Tfs.Core; using StructureMap; using Sep.Git.Tfs.Util; +using Sep.Git.Tfs.Core.TfsInterop; namespace Sep.Git.Tfs.Commands { @@ -79,7 +80,7 @@ namespace Sep.Git.Tfs.Commands var allRemotes = _globals.Repository.ReadAllTfsRemotes(); bool first = true; - var allTfsBranches = defaultRemote.Tfs.GetAllTfsBranchesOrderedByCreation(); + var allTfsBranches = defaultRemote.Tfs.GetRootTfsBranchForRemotePath(defaultRemote.TfsRepositoryPath).GetAllChildren().Select(b=>b.Path); _stdout.WriteLine("Tfs branches found:"); foreach (var tfsBranch in allTfsBranches) diff --git a/GitTfs/Core/TfsInterop/IBranch.cs b/GitTfs/Core/TfsInterop/IBranch.cs index 845efbcc..78d19091 100644 --- a/GitTfs/Core/TfsInterop/IBranch.cs +++ b/GitTfs/Core/TfsInterop/IBranch.cs @@ -21,5 +21,15 @@ namespace Sep.Git.Tfs.Core.TfsInterop childBranch.AcceptVisitor(visitor, level + 1); } } + + public static IEnumerable GetAllChildren(this IBranch branch) + { + var childrenBranches = new List(branch.ChildBranches); + foreach (var childBranch in branch.ChildBranches) + { + childrenBranches.AddRange(childBranch.GetAllChildren()); + } + return childrenBranches; + } } } \ No newline at end of file diff --git a/GitTfs/Core/TfsInterop/ITfsHelper.cs b/GitTfs/Core/TfsInterop/ITfsHelper.cs index 394b4274..3d95fc5b 100644 --- a/GitTfs/Core/TfsInterop/ITfsHelper.cs +++ b/GitTfs/Core/TfsInterop/ITfsHelper.cs @@ -31,8 +31,8 @@ namespace Sep.Git.Tfs.Core.TfsInterop int GetRootChangesetForBranch(string tfsPathBranchToCreate, string tfsPathParentBranch = null); IEnumerable GetLabels(string tfsPathBranch); bool CanGetBranchInformation { get; } - IEnumerable GetAllTfsBranchesOrderedByCreation(); - IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath); + IEnumerable GetAllTfsRootBranchesOrderedByCreation(); + IBranch GetRootTfsBranchForRemotePath(string remoteTfsPath, bool searchExactPath = true); void EnsureAuthenticated(); } } \ No newline at end of file diff --git a/GitTfsTest/Commands/InitBranchTest.cs b/GitTfsTest/Commands/InitBranchTest.cs index 7abafe7f..e5a46611 100644 --- a/GitTfsTest/Commands/InitBranchTest.cs +++ b/GitTfsTest/Commands/InitBranchTest.cs @@ -245,6 +245,15 @@ namespace Sep.Git.Tfs.Test.Commands #endregion #region Init All branches + public class MockBranch : IBranch + { + public IEnumerable ChildBranches { get; set; } + + public DateTime DateCreated { get; set; } + + public string Path { get; set; } + } + [Fact] public void ShouldInitAllBranches() { @@ -259,7 +268,15 @@ namespace Sep.Git.Tfs.Test.Commands remote.Tfs = mocks.Get(); var tfsPathBranch1 = "$/MyProject/MyBranch1"; var tfsPathBranch2 = "$/MyProject/MyBranch2"; - remote.Tfs.Stub(t => t.GetAllTfsBranchesOrderedByCreation()).Return(new List { remote.TfsRepositoryPath, tfsPathBranch1, tfsPathBranch2 }); + remote.Tfs.Stub(t => t.GetRootTfsBranchForRemotePath("")).IgnoreArguments().Return(new MockBranch() + { + ChildBranches = new List{ + new MockBranch(){ Path = remote.TfsRepositoryPath, ChildBranches = new List()}, + new MockBranch(){ Path = tfsPathBranch1, ChildBranches = new List()}, + new MockBranch(){ Path = tfsPathBranch2, ChildBranches = new List() } + } + }); + remote.Tfs.Stub(t => t.GetAllTfsRootBranchesOrderedByCreation()).Return(new List { remote.TfsRepositoryPath }); gitRepository.Expect(x => x.ReadTfsRemote("default")).Return(remote).Repeat.Once(); gitRepository.Expect(x => x.ReadAllTfsRemotes()).Return(new List { remote }).Repeat.Once(); @@ -306,8 +323,8 @@ namespace Sep.Git.Tfs.Test.Commands gitRepository.VerifyAllExpectations(); newBranch1Remote.VerifyAllExpectations(); newBranch2Remote.VerifyAllExpectations(); - } + [Fact] public void ShouldFailInitAllBranchesBecauseNeedCloneWasMadeFromTrunk() { @@ -322,7 +339,15 @@ namespace Sep.Git.Tfs.Test.Commands remote.Tfs = mocks.Get(); var tfsPathBranch1 = "$/MyProject/MyBranch1"; var tfsPathBranch2 = "$/MyProject/MyBranch2"; - remote.Tfs.Stub(t => t.GetAllTfsBranchesOrderedByCreation()).Return(new List { "$/MyProject/TheCloneWasNotMadeFromTheTrunk!", tfsPathBranch1, tfsPathBranch2 }); + remote.Tfs.Stub(t => t.GetRootTfsBranchForRemotePath("")).IgnoreArguments().Return(new MockBranch() + { + ChildBranches = new List{ + new MockBranch(){ Path = "$/MyProject/TheCloneWasNotMadeFromTheTrunk!", ChildBranches = new List()}, + new MockBranch(){ Path = tfsPathBranch1, ChildBranches = new List()}, + new MockBranch(){ Path = tfsPathBranch2, ChildBranches = new List() } + } + }); + remote.Tfs.Stub(t => t.GetAllTfsRootBranchesOrderedByCreation()).Return(new List { "$/MyProject/TheCloneWasNotMadeFromTheTrunk!" }); gitRepository.Expect(x => x.ReadTfsRemote("default")).Return(remote).Repeat.Once(); gitRepository.Expect(x => x.ReadAllTfsRemotes()).Return(new List { remote }).Repeat.Once(); From da394477c173016120e37a8293b43b57a6d41696 Mon Sep 17 00:00:00 2001 From: David Alpert Date: Tue, 1 Jan 2013 23:09:54 -0600 Subject: [PATCH 13/67] fixing a possible NullReferenceException --- GitTfs/Core/TfsInterop/IBranch.cs | 2 ++ .../Core/TfsInterop/BranchExtensionsTest.cs | 19 +++++++++++++++++++ GitTfsTest/GitTfsTest.csproj | 1 + 3 files changed, 22 insertions(+) create mode 100644 GitTfsTest/Core/TfsInterop/BranchExtensionsTest.cs diff --git a/GitTfs/Core/TfsInterop/IBranch.cs b/GitTfs/Core/TfsInterop/IBranch.cs index 78d19091..6ae3357e 100644 --- a/GitTfs/Core/TfsInterop/IBranch.cs +++ b/GitTfs/Core/TfsInterop/IBranch.cs @@ -24,6 +24,8 @@ namespace Sep.Git.Tfs.Core.TfsInterop public static IEnumerable GetAllChildren(this IBranch branch) { + if (branch == null) return Enumerable.Empty(); + var childrenBranches = new List(branch.ChildBranches); foreach (var childBranch in branch.ChildBranches) { diff --git a/GitTfsTest/Core/TfsInterop/BranchExtensionsTest.cs b/GitTfsTest/Core/TfsInterop/BranchExtensionsTest.cs new file mode 100644 index 00000000..1ddf078e --- /dev/null +++ b/GitTfsTest/Core/TfsInterop/BranchExtensionsTest.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Sep.Git.Tfs.Core.TfsInterop; +using Xunit; + +namespace Sep.Git.Tfs.Test.Core.TfsInterop +{ + public class BranchExtensionsTest + { + [Fact] + public void FactMethodName() + { + IEnumerable result = ((IBranch) null).GetAllChildren(); + + Assert.NotNull(result); + Assert.Empty(result); + } + } +} \ No newline at end of file diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index 14b66239..d9c7547d 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -110,6 +110,7 @@ + From 637569c42496fbd917d1d78bcbe00c22d1735010 Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 00:05:43 -0600 Subject: [PATCH 14/67] trailing slashes are stripped from incoming TFS repository paths so that exact comparisons match properly. --- .../TfsHelper.PostVs2010.Common.cs | 25 +------- GitTfs/Commands/Clone.cs | 3 + GitTfs/Commands/InitBranch.cs | 7 +- .../BranchContainsPathVisitor.cs | 30 +++++++++ GitTfs/Core/GitRepository.cs | 2 +- GitTfs/GitTfs.csproj | 1 + .../BranchContainsPathVisitorTest.cs | 64 +++++++++++++++++++ GitTfsTest/GitTfsTest.csproj | 1 + 8 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 GitTfs/Core/BranchVisitors/BranchContainsPathVisitor.cs create mode 100644 GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs diff --git a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs index c88f4ad1..bc81d73a 100644 --- a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs @@ -4,35 +4,12 @@ using System.IO; using System.Linq; using Microsoft.TeamFoundation.VersionControl.Client; using Sep.Git.Tfs.Core; +using Sep.Git.Tfs.Core.BranchVisitors; using Sep.Git.Tfs.Core.TfsInterop; using StructureMap; namespace Sep.Git.Tfs.VsCommon { - public class BranchContainsPathVisitor : IBranchVisitor - { - private string searchPath; - private bool searchExactPath; - - public BranchContainsPathVisitor(string searchPath, bool searchExactPath) - { - this.searchPath = searchPath; - this.searchExactPath = searchExactPath; - } - - public bool Found { get; private set; } - - public void Visit(IBranch childBranch, int level) - { - if (Found == false - && ((searchExactPath && searchPath.ToLower() == childBranch.Path.ToLower()) - || (!searchExactPath && searchPath.ToLower().IndexOf(childBranch.Path.ToLower()) == 0))) - { - Found = true; - } - } - } - public abstract class TfsHelperVs2010Base : TfsHelperBase { public TfsHelperVs2010Base(TextWriter stdout, TfsApiBridge bridge, IContainer container) diff --git a/GitTfs/Commands/Clone.cs b/GitTfs/Commands/Clone.cs index 90bd54c5..cb42a2e4 100644 --- a/GitTfs/Commands/Clone.cs +++ b/GitTfs/Commands/Clone.cs @@ -51,6 +51,9 @@ namespace Sep.Git.Tfs.Commands var currentDir = Environment.CurrentDirectory; var repositoryDirCreated = InitGitDir(gitRepositoryPath); + // TFS string representations of repository paths do not end in trailing slashes + tfsRepositoryPath = (tfsRepositoryPath ?? string.Empty).TrimEnd('/'); + try { var retVal = 0; diff --git a/GitTfs/Commands/InitBranch.cs b/GitTfs/Commands/InitBranch.cs index 28ec6fac..e4cd5570 100644 --- a/GitTfs/Commands/InitBranch.cs +++ b/GitTfs/Commands/InitBranch.cs @@ -58,9 +58,11 @@ namespace Sep.Git.Tfs.Commands public int Run(string tfsBranchPath, string gitBranchNameExpected) { - var defaultRemote = InitFromDefaultRemote(); + // TFS representations of repository paths do not have trailing slashes + tfsBranchPath = (tfsBranchPath ?? string.Empty).TrimEnd('/'); + var allRemotes = _globals.Repository.ReadAllTfsRemotes(); tfsBranchPath.AssertValidTfsPath(); @@ -132,6 +134,9 @@ namespace Sep.Git.Tfs.Commands { Trace.WriteLine("=> Working on TFS branch : " + tfsRepositoryPath); + // TFS string representations of repository paths do not end in trailing slashes + tfsRepositoryPath = (tfsRepositoryPath ?? string.Empty).TrimEnd('/'); + if (allRemotes.Count(r => r.TfsRepositoryPath.ToLower() == tfsRepositoryPath.ToLower()) != 0) { Trace.WriteLine("There is already a remote for this tfs branch. Branch ignored!"); diff --git a/GitTfs/Core/BranchVisitors/BranchContainsPathVisitor.cs b/GitTfs/Core/BranchVisitors/BranchContainsPathVisitor.cs new file mode 100644 index 00000000..0e3ecc72 --- /dev/null +++ b/GitTfs/Core/BranchVisitors/BranchContainsPathVisitor.cs @@ -0,0 +1,30 @@ +using System.Linq; +using System.Collections.Generic; +using Sep.Git.Tfs.Core.TfsInterop; + +namespace Sep.Git.Tfs.Core.BranchVisitors +{ + public class BranchContainsPathVisitor : IBranchVisitor + { + private string searchPath; + private bool searchExactPath; + + public BranchContainsPathVisitor(string searchPath, bool searchExactPath) + { + this.searchPath = searchPath; + this.searchExactPath = searchExactPath; + } + + public bool Found { get; private set; } + + public void Visit(IBranch childBranch, int level) + { + if (Found == false + && ((searchExactPath && searchPath.ToLower() == childBranch.Path.ToLower()) + || (!searchExactPath && searchPath.ToLower().IndexOf(childBranch.Path.ToLower()) == 0))) + { + Found = true; + } + } + } +} \ No newline at end of file diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index 31bf414a..670ecd51 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -215,7 +215,7 @@ namespace Sep.Git.Tfs.Core remote.Tfs.LegacyUrls = value.Split(','); break; case "repository": - remote.TfsRepositoryPath = value; + remote.TfsRepositoryPath = (value ?? string.Empty).TrimEnd('/'); /* tfs paths don't have trailing slashes */ break; case "ignore-paths": remote.IgnoreRegexExpression = value; diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index 969f6699..25e99e5c 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -131,6 +131,7 @@ Properties\Version.cs + diff --git a/GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs b/GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs new file mode 100644 index 00000000..5a508d17 --- /dev/null +++ b/GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using Sep.Git.Tfs.Core.BranchVisitors; +using Sep.Git.Tfs.Core.TfsInterop; +using Sep.Git.Tfs.Test.Commands; +using Xunit; + +namespace Sep.Git.Tfs.Test.Core.BranchVisitors +{ + public class BranchContainsPathVisitorTest + { + private IBranch branch; + + public BranchContainsPathVisitorTest() + { + branch = new InitBranchTest.MockBranch + { + ChildBranches = Enumerable.Empty(), + DateCreated = DateTime.Now, + Path = @"$/Scratch/Source/Main" + }; + } + + [Fact] + public void InexactMatch_WithoutTrailingSlash_IsFound() + { + var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main", false); + + branch.AcceptVisitor(visitor); + + Assert.True(visitor.Found); + } + + [Fact] + public void InexactMatch_WithTrailingSlash_IsFound() + { + var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main/", false); + + branch.AcceptVisitor(visitor); + + Assert.True(visitor.Found); + } + + [Fact] + public void ExactMatch_WithoutTrailingSlash_IsFound() + { + var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main", true); + + branch.AcceptVisitor(visitor); + + Assert.True(visitor.Found); + } + + [Fact] + public void ExactMatch_WithTrailingSlash_IsNotFound() + { + var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main/", true); + + branch.AcceptVisitor(visitor); + + Assert.False(visitor.Found); + } + } +} \ No newline at end of file diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index d9c7547d..775582ca 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -110,6 +110,7 @@ + From 20bc4b02212c1ea4c4ce217b22afe06468720954 Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 00:32:01 -0600 Subject: [PATCH 15/67] adding the expected error message to an InitBranchTest to verify that it is throwing the expected exception --- GitTfsTest/Commands/InitBranchTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GitTfsTest/Commands/InitBranchTest.cs b/GitTfsTest/Commands/InitBranchTest.cs index e5a46611..205b6f9f 100644 --- a/GitTfsTest/Commands/InitBranchTest.cs +++ b/GitTfsTest/Commands/InitBranchTest.cs @@ -375,7 +375,9 @@ namespace Sep.Git.Tfs.Test.Commands gitRepository.Expect(x => x.ReadTfsRemote(GIT_BRANCH_TO_INIT2)).Return(newBranch2Remote).Repeat.Never(); #endregion - Assert.Throws(typeof(GitTfsException), ()=>mocks.ClassUnderTest.Run()); + var ex = Assert.Throws(typeof(GitTfsException), ()=>mocks.ClassUnderTest.Run()); + + Assert.Equal("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from the trunk...", ex.Message); gitRepository.VerifyAllExpectations(); From 457770bc1f7365a2a7f386d8389881ba04338852 Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 00:37:03 -0600 Subject: [PATCH 16/67] fixed the mocked setup of some IBranches to surface an issue with the InitBranch command: the IBranch structure is a node in a tree containing a path and referencing it's children, thus getting only its children never includes the root path, causing the current implementation of InitBranch to always throw an exception. --- GitTfsTest/Commands/InitBranchTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GitTfsTest/Commands/InitBranchTest.cs b/GitTfsTest/Commands/InitBranchTest.cs index 205b6f9f..4f24cec6 100644 --- a/GitTfsTest/Commands/InitBranchTest.cs +++ b/GitTfsTest/Commands/InitBranchTest.cs @@ -270,8 +270,8 @@ namespace Sep.Git.Tfs.Test.Commands var tfsPathBranch2 = "$/MyProject/MyBranch2"; remote.Tfs.Stub(t => t.GetRootTfsBranchForRemotePath("")).IgnoreArguments().Return(new MockBranch() { + Path = remote.TfsRepositoryPath, ChildBranches = new List{ - new MockBranch(){ Path = remote.TfsRepositoryPath, ChildBranches = new List()}, new MockBranch(){ Path = tfsPathBranch1, ChildBranches = new List()}, new MockBranch(){ Path = tfsPathBranch2, ChildBranches = new List() } } @@ -341,8 +341,8 @@ namespace Sep.Git.Tfs.Test.Commands var tfsPathBranch2 = "$/MyProject/MyBranch2"; remote.Tfs.Stub(t => t.GetRootTfsBranchForRemotePath("")).IgnoreArguments().Return(new MockBranch() { + Path = "$/MyProject/TheCloneWasNotMadeFromTheTrunk!", ChildBranches = new List{ - new MockBranch(){ Path = "$/MyProject/TheCloneWasNotMadeFromTheTrunk!", ChildBranches = new List()}, new MockBranch(){ Path = tfsPathBranch1, ChildBranches = new List()}, new MockBranch(){ Path = tfsPathBranch2, ChildBranches = new List() } } From b70c581c826679504584880c0adb3cb855f9b01c Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 00:42:46 -0600 Subject: [PATCH 17/67] fixing a problem with InitBranch where it always failed. --- GitTfs/Commands/InitBranch.cs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/GitTfs/Commands/InitBranch.cs b/GitTfs/Commands/InitBranch.cs index e4cd5570..a268fdc3 100644 --- a/GitTfs/Commands/InitBranch.cs +++ b/GitTfs/Commands/InitBranch.cs @@ -81,26 +81,21 @@ namespace Sep.Git.Tfs.Commands var allRemotes = _globals.Repository.ReadAllTfsRemotes(); - bool first = true; - var allTfsBranches = defaultRemote.Tfs.GetRootTfsBranchForRemotePath(defaultRemote.TfsRepositoryPath).GetAllChildren().Select(b=>b.Path); + var rootBranch = defaultRemote.Tfs.GetRootTfsBranchForRemotePath(defaultRemote.TfsRepositoryPath); + if (defaultRemote.TfsRepositoryPath.ToLower() != rootBranch.Path.ToLower()) + throw new GitTfsException("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from the trunk..."); + + var childBranchPaths = rootBranch.GetAllChildren().Select(b=>b.Path); _stdout.WriteLine("Tfs branches found:"); - foreach (var tfsBranch in allTfsBranches) + foreach (var tfsBranchPath in childBranchPaths) { - _stdout.WriteLine("- " + tfsBranch); + _stdout.WriteLine("- " + tfsBranchPath); } - foreach (var tfsBranch in allTfsBranches) + foreach (var tfsBranchPath in childBranchPaths) { - if (first) - { - if (defaultRemote.TfsRepositoryPath.ToLower() != tfsBranch.ToLower()) - throw new GitTfsException("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from the trunk..."); - - first = false; - continue; - } - var result = CreateBranch(defaultRemote, tfsBranch, allRemotes); + var result = CreateBranch(defaultRemote, tfsBranchPath, allRemotes); if (result < 0) return result; } From 1bcd9b0ea31034d6820dd2a26708d75b2e9833fe Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 00:47:12 -0600 Subject: [PATCH 18/67] improving the error message when InitBranch fails because it needs a clone of the trunk --- GitTfs/Commands/InitBranch.cs | 2 +- GitTfsTest/Commands/InitBranchTest.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/GitTfs/Commands/InitBranch.cs b/GitTfs/Commands/InitBranch.cs index a268fdc3..0ab1cadb 100644 --- a/GitTfs/Commands/InitBranch.cs +++ b/GitTfs/Commands/InitBranch.cs @@ -83,7 +83,7 @@ namespace Sep.Git.Tfs.Commands var rootBranch = defaultRemote.Tfs.GetRootTfsBranchForRemotePath(defaultRemote.TfsRepositoryPath); if (defaultRemote.TfsRepositoryPath.ToLower() != rootBranch.Path.ToLower()) - throw new GitTfsException("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from the trunk..."); + throw new GitTfsException(string.Format("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from '{0}'...", rootBranch.Path)); var childBranchPaths = rootBranch.GetAllChildren().Select(b=>b.Path); diff --git a/GitTfsTest/Commands/InitBranchTest.cs b/GitTfsTest/Commands/InitBranchTest.cs index 4f24cec6..819a2bba 100644 --- a/GitTfsTest/Commands/InitBranchTest.cs +++ b/GitTfsTest/Commands/InitBranchTest.cs @@ -328,7 +328,6 @@ namespace Sep.Git.Tfs.Test.Commands [Fact] public void ShouldFailInitAllBranchesBecauseNeedCloneWasMadeFromTrunk() { - const string GIT_BRANCH_TO_INIT1 = "MyBranch1"; const string GIT_BRANCH_TO_INIT2 = "MyBranch2"; @@ -347,7 +346,6 @@ namespace Sep.Git.Tfs.Test.Commands new MockBranch(){ Path = tfsPathBranch2, ChildBranches = new List() } } }); - remote.Tfs.Stub(t => t.GetAllTfsRootBranchesOrderedByCreation()).Return(new List { "$/MyProject/TheCloneWasNotMadeFromTheTrunk!" }); gitRepository.Expect(x => x.ReadTfsRemote("default")).Return(remote).Repeat.Once(); gitRepository.Expect(x => x.ReadAllTfsRemotes()).Return(new List { remote }).Repeat.Once(); @@ -377,7 +375,7 @@ namespace Sep.Git.Tfs.Test.Commands var ex = Assert.Throws(typeof(GitTfsException), ()=>mocks.ClassUnderTest.Run()); - Assert.Equal("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from the trunk...", ex.Message); + Assert.Equal("error: Init all the branches is only possible when 'git tfs clone' was done from the trunk!!! Please clone again from '$/MyProject/TheCloneWasNotMadeFromTheTrunk!'...", ex.Message); gitRepository.VerifyAllExpectations(); From 89a2cfb5cded259c7d88399edd2b68cdbbfc5411 Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Wed, 2 Jan 2013 19:10:45 +0100 Subject: [PATCH 19/67] When displaying TFS branches, display remotes if there is on matching --- GitTfs/Commands/Branch.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/GitTfs/Commands/Branch.cs b/GitTfs/Commands/Branch.cs index 2665025e..b19a08f2 100644 --- a/GitTfs/Commands/Branch.cs +++ b/GitTfs/Commands/Branch.cs @@ -45,11 +45,13 @@ namespace Sep.Git.Tfs.Commands { private readonly TextWriter _stdout; private readonly string _targetPath; + private readonly IEnumerable _tfsRemotes; - public Visitor(string targetPath, TextWriter writer) + public Visitor(string targetPath, TextWriter writer, IEnumerable tfsRemotes = null) { _targetPath = targetPath; _stdout = writer; + _tfsRemotes = tfsRemotes; } public void Visit(IBranch branch, int level) @@ -65,12 +67,20 @@ namespace Sep.Git.Tfs.Commands if (branch.Path.Equals(_targetPath)) _stdout.Write(" * "); + if (_tfsRemotes != null) + { + var remote = _tfsRemotes.FirstOrDefault(r => r.TfsRepositoryPath == branch.Path); + if (remote != null) + _stdout.Write(" -> " + remote.Id); + } + _stdout.WriteLine(); } } public int Run() { + var tfsRemotes = globals.Repository.ReadAllTfsRemotes(); if (DisplayRemotes) { stdout.WriteLine("TFS branches:"); @@ -81,7 +91,7 @@ namespace Sep.Git.Tfs.Commands var root = remote.Tfs.GetRootTfsBranchForRemotePath(remote.TfsRepositoryPath); - var visitor = new Visitor(remote.TfsRepositoryPath, stdout); + var visitor = new Visitor(remote.TfsRepositoryPath, stdout, tfsRemotes); root.AcceptVisitor(visitor); @@ -90,7 +100,6 @@ namespace Sep.Git.Tfs.Commands return GitTfsExitCodes.OK; } - var tfsRemotes = globals.Repository.ReadAllTfsRemotes(); stdout.WriteLine("Git-tfs remotes:"); foreach (var remote in tfsRemotes) { From 54f4350c71f46257cf12d52497f4d89dc156d668 Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Wed, 2 Jan 2013 21:25:39 +0100 Subject: [PATCH 20/67] Filter label by name --- GitTfs.VsCommon/TfsHelper.Common.cs | 7 ++++--- GitTfs.VsFake/TfsHelper.VsFake.cs | 2 +- GitTfs/Commands/Labels.cs | 9 ++++++++- GitTfs/Core/TfsInterop/ITfsHelper.cs | 4 ++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/GitTfs.VsCommon/TfsHelper.Common.cs b/GitTfs.VsCommon/TfsHelper.Common.cs index fe9a485f..ea522718 100644 --- a/GitTfs.VsCommon/TfsHelper.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.Common.cs @@ -559,9 +559,10 @@ namespace Sep.Git.Tfs.VsCommon return new WorkItemCheckedInfo(Convert.ToInt32(workitem), true, checkinAction); } - public IEnumerable GetLabels(string tfsPathBranch) + public IEnumerable GetLabels(string tfsPathBranch, string nameFilter = null) { - var labels = VersionControl.QueryLabels(null, tfsPathBranch, null, true, tfsPathBranch, VersionSpec.Latest); + var labels = VersionControl.QueryLabels(nameFilter, tfsPathBranch, null, true, tfsPathBranch, VersionSpec.Latest); + return labels.Select(e => new TfsLabel { Id = e.LabelId, Name = e.Name, @@ -574,4 +575,4 @@ namespace Sep.Git.Tfs.VsCommon } } -} \ No newline at end of file +} diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 670a4ddd..e98f7d84 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -356,7 +356,7 @@ namespace Sep.Git.Tfs.VsFake throw new NotImplementedException(); } - public IEnumerable GetLabels(string tfsPathBranch) + public IEnumerable GetLabels(string tfsPathBranch, string nameFilter = null) { throw new NotImplementedException(); } diff --git a/GitTfs/Commands/Labels.cs b/GitTfs/Commands/Labels.cs index 2e39543d..2210bfc7 100644 --- a/GitTfs/Commands/Labels.cs +++ b/GitTfs/Commands/Labels.cs @@ -24,6 +24,7 @@ namespace Sep.Git.Tfs.Commands public string TfsPassword { get; set; } public string ParentBranch { get; set; } public bool LabelAllBranches { get; set; } + public string NameFilter { get; set; } string AuthorsFilePath { get; set; } public Labels(TextWriter stdout, Globals globals, AuthorsFile authors) @@ -40,6 +41,7 @@ namespace Sep.Git.Tfs.Commands return new OptionSet { { "all|fetch-all", "Fetch all the labels on all the TFS remotes (For TFS 2010 and later)", v => LabelAllBranches = v != null }, + { "n|label-name=", "Fetch all the labels respecting this name filter", v => NameFilter = v }, { "u|username=", "TFS username", v => TfsUsername = v }, { "p|password=", "TFS password", v => TfsPassword = v }, { "a|authors=", "Path to an Authors file to map TFS users to Git users", v => AuthorsFilePath = v }, @@ -83,9 +85,14 @@ namespace Sep.Git.Tfs.Commands private int CreateLabelsForTfsBranch(IGitTfsRemote tfsRemote) { + if (string.IsNullOrWhiteSpace(NameFilter)) + NameFilter = null; + else + NameFilter = NameFilter.Trim(); + UpdateRemote(tfsRemote); _stdout.WriteLine("Looking for label on " + tfsRemote.TfsRepositoryPath + "..."); - var labels = tfsRemote.Tfs.GetLabels(tfsRemote.TfsRepositoryPath); + var labels = tfsRemote.Tfs.GetLabels(tfsRemote.TfsRepositoryPath, NameFilter); _stdout.WriteLine(labels.Count() +" labels found!"); foreach (var label in labels) { diff --git a/GitTfs/Core/TfsInterop/ITfsHelper.cs b/GitTfs/Core/TfsInterop/ITfsHelper.cs index 1fc8fda1..dc09c322 100644 --- a/GitTfs/Core/TfsInterop/ITfsHelper.cs +++ b/GitTfs/Core/TfsInterop/ITfsHelper.cs @@ -29,9 +29,9 @@ namespace Sep.Git.Tfs.Core.TfsInterop long ShowCheckinDialog(IWorkspace workspace, IPendingChange[] pendingChanges, IEnumerable checkedInfos, string checkinComment); void CleanupWorkspaces(string workingDirectory); int GetRootChangesetForBranch(string tfsPathBranchToCreate, string tfsPathParentBranch = null); - IEnumerable GetLabels(string tfsPathBranch); + IEnumerable GetLabels(string tfsPathBranch, string nameFilter = null); bool CanGetBranchInformation { get; } IEnumerable GetAllTfsBranchesOrderedByCreation(); void EnsureAuthenticated(); } -} \ No newline at end of file +} From edc2def0e2d6ec20be492382d68423256597a4c0 Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Wed, 2 Jan 2013 21:35:17 +0100 Subject: [PATCH 21/67] Filter label by name by excluding names using regular expression --- GitTfs/Commands/Labels.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/GitTfs/Commands/Labels.cs b/GitTfs/Commands/Labels.cs index 2210bfc7..b02f5025 100644 --- a/GitTfs/Commands/Labels.cs +++ b/GitTfs/Commands/Labels.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using NDesk.Options; using Sep.Git.Tfs.Core; using StructureMap; @@ -25,6 +26,7 @@ namespace Sep.Git.Tfs.Commands public string ParentBranch { get; set; } public bool LabelAllBranches { get; set; } public string NameFilter { get; set; } + public string ExcludeNameFilter { get; set; } string AuthorsFilePath { get; set; } public Labels(TextWriter stdout, Globals globals, AuthorsFile authors) @@ -42,6 +44,7 @@ namespace Sep.Git.Tfs.Commands { { "all|fetch-all", "Fetch all the labels on all the TFS remotes (For TFS 2010 and later)", v => LabelAllBranches = v != null }, { "n|label-name=", "Fetch all the labels respecting this name filter", v => NameFilter = v }, + { "e|exclude-label-name=", "Exclude all the labels respecting this regex name filter", v => ExcludeNameFilter = v }, { "u|username=", "TFS username", v => TfsUsername = v }, { "p|password=", "TFS password", v => TfsPassword = v }, { "a|authors=", "Path to an Authors file to map TFS users to Git users", v => AuthorsFilePath = v }, @@ -94,8 +97,16 @@ namespace Sep.Git.Tfs.Commands _stdout.WriteLine("Looking for label on " + tfsRemote.TfsRepositoryPath + "..."); var labels = tfsRemote.Tfs.GetLabels(tfsRemote.TfsRepositoryPath, NameFilter); _stdout.WriteLine(labels.Count() +" labels found!"); + + Regex exludeRegex = null; + if (ExcludeNameFilter != null) + exludeRegex = new Regex(ExcludeNameFilter); + foreach (var label in labels) { + if (ExcludeNameFilter != null && exludeRegex.IsMatch(label.Name)) + continue; + Trace.WriteLine("LabelId:" + label.Id + "/ChangesetId:" + label.ChangesetId + "/LabelName:" + label.Name + "/Owner:" + label.Owner); Trace.WriteLine("Try to find changeset in git repository..."); string sha1TagCommit = _globals.Repository.FindCommitHashByCommitMessage("git-tfs-id: .*;C" + label.ChangesetId + "[^0-9]"); From 6e42acb54be3c771551ec884909bca14eb33ae1e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 25 Dec 2012 22:07:25 +0100 Subject: [PATCH 22/67] Add method to compare Tfs url and Repo path to GitTfsRemote Moving logic from TfsHelper.Common and GitRepository into GitTfsRemote. Easier to test as TfsHelper is tightly connected to Tfs and GitRepository is difficult to test directly. Added tests for GitTfsRemote. --- GitTfs.VsCommon/TfsHelper.Common.cs | 5 -- GitTfs.VsFake/TfsHelper.VsFake.cs | 5 -- GitTfs/Core/DerivedGitTfsRemote.cs | 5 ++ GitTfs/Core/Ext.cs | 5 ++ GitTfs/Core/GitRepository.cs | 3 +- GitTfs/Core/GitTfsRemote.cs | 10 +++ GitTfs/Core/IGitTfsRemote.cs | 1 + GitTfs/Core/TfsInterop/ITfsHelper.cs | 1 - GitTfsTest/Core/GitTfsRemoteTests.cs | 93 ++++++++++++++++++++++++++++ GitTfsTest/GitTfsTest.csproj | 1 + 10 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 GitTfsTest/Core/GitTfsRemoteTests.cs diff --git a/GitTfs.VsCommon/TfsHelper.Common.cs b/GitTfs.VsCommon/TfsHelper.Common.cs index fe9a485f..74ca1829 100644 --- a/GitTfs.VsCommon/TfsHelper.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.Common.cs @@ -500,11 +500,6 @@ namespace Sep.Git.Tfs.VsCommon return BuildTfsChangeset(VersionControl.GetChangeset(changesetId), remote); } - public bool MatchesUrl(string tfsUrl) - { - return Url == tfsUrl || LegacyUrls.Contains(tfsUrl); - } - public IEnumerable GetWorkItemInfos(IEnumerable workItems, TfsWorkItemCheckinAction checkinAction) { return diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 670a4ddd..fdad681f 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -319,11 +319,6 @@ namespace Sep.Git.Tfs.VsFake throw new NotImplementedException(); } - public bool MatchesUrl(string tfsUrl) - { - throw new NotImplementedException(); - } - public bool HasShelveset(string shelvesetName) { throw new NotImplementedException(); diff --git a/GitTfs/Core/DerivedGitTfsRemote.cs b/GitTfs/Core/DerivedGitTfsRemote.cs index 00c2f265..b208a482 100644 --- a/GitTfs/Core/DerivedGitTfsRemote.cs +++ b/GitTfs/Core/DerivedGitTfsRemote.cs @@ -227,6 +227,11 @@ namespace Sep.Git.Tfs.Core throw new NotImplementedException(); } + public bool MatchesUrlAndRepositoryPath(string tfsUrl, string tfsRepositoryPath) + { + throw new NotImplementedException(); + } + #endregion } } diff --git a/GitTfs/Core/Ext.cs b/GitTfs/Core/Ext.cs index 8a9132f4..77fcd0d1 100644 --- a/GitTfs/Core/Ext.cs +++ b/GitTfs/Core/Ext.cs @@ -130,5 +130,10 @@ namespace Sep.Git.Tfs.Core public static StreamWriter WithEncoding(this StreamWriter stream, Encoding encoding) { return new StreamWriter(stream.BaseStream, encoding); } + + public static bool Contains(this IEnumerable list, string toCheck, StringComparison comp) + { + return list.Any(listMember => listMember.IndexOf(toCheck, comp) >= 0); + } } } diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index 31bf414a..f0b14b4d 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Sep.Git.Tfs.Commands; +using Sep.Git.Tfs.Core.TfsInterop; using StructureMap; using LibGit2Sharp; @@ -75,7 +76,7 @@ namespace Sep.Git.Tfs.Core var allRemotes = GetTfsRemotes(); var matchingRemotes = allRemotes.Values.Where( - remote => remote.Tfs.MatchesUrl(tfsUrl) && remote.TfsRepositoryPath == tfsRepositoryPath); + remote => remote.MatchesUrlAndRepositoryPath(tfsUrl, tfsRepositoryPath)); switch (matchingRemotes.Count()) { case 0: diff --git a/GitTfs/Core/GitTfsRemote.cs b/GitTfs/Core/GitTfsRemote.cs index 978ce821..57cf50cf 100644 --- a/GitTfs/Core/GitTfsRemote.cs +++ b/GitTfs/Core/GitTfsRemote.cs @@ -508,5 +508,15 @@ namespace Sep.Git.Tfs.Core PendChangesToWorkspace(head, parent, workspace); return workspace.Checkin(options); } + + public bool MatchesUrlAndRepositoryPath(string tfsUrl, string tfsRepositoryPath) + { + return MatchesTfsUrl(tfsUrl) && TfsRepositoryPath.Equals(tfsRepositoryPath, StringComparison.OrdinalIgnoreCase); + } + + private bool MatchesTfsUrl(string tfsUrl) + { + return TfsUrl.Equals(tfsUrl, StringComparison.OrdinalIgnoreCase) || Tfs.LegacyUrls.Contains(tfsUrl, StringComparison.OrdinalIgnoreCase); + } } } diff --git a/GitTfs/Core/IGitTfsRemote.cs b/GitTfs/Core/IGitTfsRemote.cs index 50cf33b3..09e8091f 100644 --- a/GitTfs/Core/IGitTfsRemote.cs +++ b/GitTfs/Core/IGitTfsRemote.cs @@ -42,5 +42,6 @@ namespace Sep.Git.Tfs.Core ITfsChangeset GetChangeset(long changesetId); void UpdateRef(string commitHash, long changesetId); void EnsureTfsAuthenticated(); + bool MatchesUrlAndRepositoryPath(string tfsUrl, string tfsRepositoryPath); } } diff --git a/GitTfs/Core/TfsInterop/ITfsHelper.cs b/GitTfs/Core/TfsInterop/ITfsHelper.cs index 1fc8fda1..bd0eb076 100644 --- a/GitTfs/Core/TfsInterop/ITfsHelper.cs +++ b/GitTfs/Core/TfsInterop/ITfsHelper.cs @@ -21,7 +21,6 @@ namespace Sep.Git.Tfs.Core.TfsInterop ITfsChangeset GetLatestChangeset(GitTfsRemote remote); ITfsChangeset GetChangeset(int changesetId, GitTfsRemote remote); IChangeset GetChangeset(int changesetId); - bool MatchesUrl(string tfsUrl); bool HasShelveset(string shelvesetName); ITfsChangeset GetShelvesetData(IGitTfsRemote remote, string shelvesetOwner, string shelvesetName); int ListShelvesets(ShelveList shelveList, IGitTfsRemote remote); diff --git a/GitTfsTest/Core/GitTfsRemoteTests.cs b/GitTfsTest/Core/GitTfsRemoteTests.cs new file mode 100644 index 00000000..7150f676 --- /dev/null +++ b/GitTfsTest/Core/GitTfsRemoteTests.cs @@ -0,0 +1,93 @@ +using System.IO; +using Rhino.Mocks; +using Sep.Git.Tfs.Core; +using Sep.Git.Tfs.Core.TfsInterop; +using Sep.Git.Tfs.Util; +using StructureMap.AutoMocking; +using Xunit; + +namespace Sep.Git.Tfs.Test.Core +{ + public class GitTfsRemoteTests + { + [Fact] + public void MatchesUrlAndRepositoryPath_should_be_case_insensitive_for_tfs_url() + { + var mocker = new RhinoAutoMocker(); + mocker.Inject(new StringWriter()); + var helper = MockRepository.GenerateStub(); + helper.Url = "http://testvcs:8080/tfs/test"; + helper.LegacyUrls = new string[0]; + mocker.Inject(helper); + mocker.ClassUnderTest.TfsRepositoryPath = "test"; + + bool matches = mocker.ClassUnderTest.MatchesUrlAndRepositoryPath("http://testvcs:8080/tfs/Test", "test"); + + Assert.Equal(true, matches); + } + + [Fact] + public void MatchesUrlAndRepositoryPath_should_be_false_if_no_match_for_tfs_url() + { + var mocker = new RhinoAutoMocker(); + mocker.Inject(new StringWriter()); + var helper = MockRepository.GenerateStub(); + helper.Url = "http://testvcs:8080/tfs/test"; + helper.LegacyUrls = new string[0]; + mocker.Inject(helper); + mocker.ClassUnderTest.TfsRepositoryPath = "test"; + + bool matches = mocker.ClassUnderTest.MatchesUrlAndRepositoryPath("http://adifferenturl:8080/tfs/Test", "test"); + + Assert.Equal(false, matches); + } + + [Fact] + public void MatchesUrlAndRepositoryPath_should_be_case_insensitive_for_legacy_urls() + { + var mocker = new RhinoAutoMocker(); + mocker.Inject(new StringWriter()); + var helper = MockRepository.GenerateStub(); + helper.Url = ""; + helper.LegacyUrls = new[] { "http://testvcs:8080/tfs/test", "AnotherUrlThatDoesntMatch" }; + mocker.Inject(helper); + mocker.ClassUnderTest.TfsRepositoryPath = "test"; + + bool matches = mocker.ClassUnderTest.MatchesUrlAndRepositoryPath("http://testvcs:8080/tfs/Test", "test"); + + Assert.Equal(true, matches); + } + + [Fact] + public void MatchesUrlAndRepositoryPath_should_be_case_insensitive_for_tfs_repository_path() + { + var mocker = new RhinoAutoMocker(); + mocker.Inject(new StringWriter()); + var helper = MockRepository.GenerateStub(); + helper.Url = "test"; + helper.LegacyUrls = new string[0]; + mocker.Inject(helper); + mocker.ClassUnderTest.TfsRepositoryPath = "$/Test"; + + bool matches = mocker.ClassUnderTest.MatchesUrlAndRepositoryPath("test", "$/test"); + + Assert.Equal(true, matches); + } + + [Fact] + public void MatchesUrlAndRepositoryPath_should_be_false_if_no_match_for_tfs_repository_path() + { + var mocker = new RhinoAutoMocker(); + mocker.Inject(new StringWriter()); + var helper = MockRepository.GenerateStub(); + helper.Url = "test"; + helper.LegacyUrls = new string[0]; + mocker.Inject(helper); + mocker.ClassUnderTest.TfsRepositoryPath = "$/Test"; + + bool matches = mocker.ClassUnderTest.MatchesUrlAndRepositoryPath("test", "$/shouldnotmatch"); + + Assert.Equal(false, matches); + } + } +} \ No newline at end of file diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index 14b66239..7ebeda6d 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -110,6 +110,7 @@ + From 4784e99fde7e9ebd9a40fd488ec7cf544aa148d1 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 3 Jan 2013 00:49:58 +0100 Subject: [PATCH 23/67] Add Fetch/Pull tests Created tests for comparing the tfs url, legacy urls and the repository path with mixed case when calling git tfs fetch or git tfs pull. Used libgit2sharp to count the commits before and after. Added support for running commands from the project directory (git tfs clone is called from outside the project directory). --- GitTfs/Core/GitRepository.cs | 1 + GitTfsTest/GitTfsTest.csproj | 1 + GitTfsTest/Integration/FetchTests.cs | 89 +++++++++++++++++++++ GitTfsTest/Integration/IntegrationHelper.cs | 21 ++++- 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 GitTfsTest/Integration/FetchTests.cs diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index f0b14b4d..38b2ab20 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -86,6 +86,7 @@ namespace Sep.Git.Tfs.Core .WithRecommendation("Try setting a legacy-url for an existing remote."); return new DerivedGitTfsRemote(tfsUrl, tfsRepositoryPath); case 1: + Trace.WriteLine("One remote matched"); return matchingRemotes.First(); default: Trace.WriteLine("More than one remote matched!"); diff --git a/GitTfsTest/GitTfsTest.csproj b/GitTfsTest/GitTfsTest.csproj index 7ebeda6d..5ef26fc0 100644 --- a/GitTfsTest/GitTfsTest.csproj +++ b/GitTfsTest/GitTfsTest.csproj @@ -123,6 +123,7 @@ + diff --git a/GitTfsTest/Integration/FetchTests.cs b/GitTfsTest/Integration/FetchTests.cs new file mode 100644 index 00000000..172046b3 --- /dev/null +++ b/GitTfsTest/Integration/FetchTests.cs @@ -0,0 +1,89 @@ +using System; +using Sep.Git.Tfs.Core.TfsInterop; +using Xunit; + +namespace Sep.Git.Tfs.Test.Integration +{ + public class FetchTests : IDisposable + { + private readonly IntegrationHelper integrationHelper; + + public FetchTests() + { + integrationHelper = new IntegrationHelper(); + } + + public void Dispose() + { + integrationHelper.Dispose(); + } + + [FactExceptOnUnix] + public void CanFetchWithMixedUpCasingForTfsServerUrl() + { + CloneRepoWithTwoCommits(); + AddNewCommitToFakeTfsServer(); + string tfsUrlInUpperCase = integrationHelper.TfsUrl.ToUpper(); + integrationHelper.ChangeConfigSetting("MyProject", "tfs-remote.default.url", tfsUrlInUpperCase); + + integrationHelper.RunInProjectDirectory("MyProject", "pull"); + + Assert.Equal(3, integrationHelper.GetCommitCount("MyProject")); + } + + [FactExceptOnUnix] + public void CanFetchWithMixedUpCasingForLegacyTfsServerUrl() + { + CloneRepoWithTwoCommits(); + AddNewCommitToFakeTfsServer(); + string tfsUrlInUpperCase = integrationHelper.TfsUrl.ToUpper(); + integrationHelper.ChangeConfigSetting("MyProject", "tfs-remote.default.url", "nomatch"); + integrationHelper.ChangeConfigSetting("MyProject", "tfs-remote.default.legacy-urls", tfsUrlInUpperCase + ",aDifferentUrl"); + + integrationHelper.RunInProjectDirectory("MyProject", "pull"); + + Assert.Equal(3, integrationHelper.GetCommitCount("MyProject")); + } + + [FactExceptOnUnix] + public void CanFetchWithMixedUpCasingForTfsRepositoryPath() + { + CloneRepoWithTwoCommits(); + AddNewCommitToFakeTfsServer(); + const string repoUrlInUpperCase = "$/MYPROJECT"; + integrationHelper.ChangeConfigSetting("MyProject", "tfs-remote.default.repository", repoUrlInUpperCase); + + integrationHelper.RunInProjectDirectory("MyProject", "pull"); + + Assert.Equal(3, integrationHelper.GetCommitCount("MyProject")); + } + + private void CloneRepoWithTwoCommits() + { + integrationHelper.SetupFake(r => + { + r.Changeset(1, "Project created from template", DateTime.Parse("2012-01-01 12:12:12 -05:00")) + .Change(TfsChangeType.Add, TfsItemType.Folder, "$/MyProject"); + r.Changeset(2, "Add Readme", DateTime.Parse("2012-01-02 12:12:12 -05:00")) + .Change(TfsChangeType.Add, TfsItemType.Folder, "$/MyProject/Folder") + .Change(TfsChangeType.Add, TfsItemType.File, "$/MyProject/Folder/File.txt", "File contents") + .Change(TfsChangeType.Add, TfsItemType.File, "$/MyProject/README", "tldr"); + }); + integrationHelper.Run("clone", integrationHelper.TfsUrl, "$/MyProject"); + integrationHelper.AssertGitRepo("MyProject"); + } + + private void AddNewCommitToFakeTfsServer() + { + integrationHelper.SetupFake(r => CreateAChangeset(r)); + } + + private static IntegrationHelper.FakeChangesetBuilder CreateAChangeset(IntegrationHelper.FakeHistoryBuilder r) + { + return r.Changeset(3, "Add a file", DateTime.Parse("2012-01-03 12:12:12 -05:00")) + .Change(TfsChangeType.Add, TfsItemType.Folder, "$/MyProject/Foo") + .Change(TfsChangeType.Add, TfsItemType.Folder, "$/MyProject/Foo/Bar") + .Change(TfsChangeType.Add, TfsItemType.File, "$/MyProject/Foo/Bar/File.txt", "File contents"); + } + } +} \ No newline at end of file diff --git a/GitTfsTest/Integration/IntegrationHelper.cs b/GitTfsTest/Integration/IntegrationHelper.cs index 660b3349..6b642f5f 100644 --- a/GitTfsTest/Integration/IntegrationHelper.cs +++ b/GitTfsTest/Integration/IntegrationHelper.cs @@ -111,9 +111,14 @@ namespace Sep.Git.Tfs.Test.Integration public string TfsUrl { get { return "http://does/not/matter"; } } public void Run(params string[] args) + { + RunInProjectDirectory("", args); + } + + public void RunInProjectDirectory(string projectDirectory, params string[] args) { var startInfo = new ProcessStartInfo(); - startInfo.WorkingDirectory = Workdir; + startInfo.WorkingDirectory = Path.Combine(Workdir, projectDirectory); startInfo.EnvironmentVariables["GIT_TFS_CLIENT"] = "Fake"; startInfo.EnvironmentVariables[Script.EnvVar] = FakeScript; startInfo.EnvironmentVariables["Path"] = CurrentBuildPath + ";" + Environment.GetEnvironmentVariable("Path"); @@ -137,10 +142,22 @@ namespace Sep.Git.Tfs.Test.Integration } } + public void ChangeConfigSetting(string repodir, string key, string value) + { + var repo = new LibGit2Sharp.Repository(Path.Combine(Workdir, repodir)); + repo.Config.Set(key, value); + } + #endregion #region assertions + public int GetCommitCount(string repodir) + { + var repo = new LibGit2Sharp.Repository(Path.Combine(Workdir, repodir)); + return repo.Commits.Count(); + } + public void AssertGitRepo(string repodir) { var path = Path.Combine(Workdir, repodir); @@ -197,7 +214,7 @@ namespace Sep.Git.Tfs.Test.Integration var commit = LibGit2Sharp.RepositoryExtensions.Lookup(repo, commitish); AssertEqual(message, commit.Message, "Commit message of " + commitish); } - + private void AssertEqual(T expected, T actual, string message) { try From 2f770d749fb03ce5ce96f5167804e4c4f045d80f Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 19:57:30 -0600 Subject: [PATCH 24/67] incorporating @sc68cal's suggestions --- GitTfs/Commands/Branch.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/GitTfs/Commands/Branch.cs b/GitTfs/Commands/Branch.cs index b19a08f2..6a1ed4b5 100644 --- a/GitTfs/Commands/Branch.cs +++ b/GitTfs/Commands/Branch.cs @@ -16,20 +16,16 @@ namespace Sep.Git.Tfs.Commands { private Globals globals; private TextWriter stdout; - public string TfsUsername { get; set; } - public string TfsPassword { get; set; } public bool DisplayRemotes { get; set; } public OptionSet OptionSet { - get - { + get { return new OptionSet { - { "r|remotes", "Display all the TFS branch of the current TFS server", v => DisplayRemotes = (v != null) }, - //{ "u|username=", "TFS username", v => TfsUsername = v }, - //{ "p|password=", "TFS password", v => TfsPassword = v }, - }; + { "r|remotes", "Display all the TFS branch of the current TFS server", v => DisplayRemotes = (v != null) } + } + .Merge(globals.OptionSet); } } @@ -37,8 +33,6 @@ namespace Sep.Git.Tfs.Commands { this.globals = globals; this.stdout = stdout; - - //this.OptionSet = globals.OptionSet; } private class Visitor : IBranchVisitor @@ -103,8 +97,7 @@ namespace Sep.Git.Tfs.Commands stdout.WriteLine("Git-tfs remotes:"); foreach (var remote in tfsRemotes) { - stdout.WriteLine(); - stdout.WriteLine(" {0} -> {1} {2}", remote.Id, remote.TfsUrl, remote.TfsRepositoryPath); + stdout.WriteLine("\n {0} -> {1} {2}", remote.Id, remote.TfsUrl, remote.TfsRepositoryPath); stdout.WriteLine(" {0} - {1} @ {2}", remote.RemoteRef, remote.MaxCommitHash, remote.MaxChangesetId); } return GitTfsExitCodes.OK; From a2af52536bbdce9d10d58d25d27099fb007e14cf Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 21:20:46 -0600 Subject: [PATCH 25/67] renaming some branch-related helper classes and cleaning up whitespace in the output --- .../TfsHelper.PostVs2010.Common.cs | 2 +- GitTfs/Commands/Branch.cs | 79 ++++++++++--------- ...or.cs => BranchTreeContainsPathVisitor.cs} | 4 +- ...BranchVisitor.cs => IBranchTreeVisitor.cs} | 2 +- GitTfs/Core/TfsInterop/IBranch.cs | 6 +- GitTfs/GitTfs.csproj | 4 +- .../BranchContainsPathVisitorTest.cs | 8 +- 7 files changed, 56 insertions(+), 49 deletions(-) rename GitTfs/Core/BranchVisitors/{BranchContainsPathVisitor.cs => BranchTreeContainsPathVisitor.cs} (82%) rename GitTfs/Core/{IBranchVisitor.cs => IBranchTreeVisitor.cs} (81%) diff --git a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs index bc81d73a..54f9eb89 100644 --- a/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.PostVs2010.Common.cs @@ -40,7 +40,7 @@ namespace Sep.Git.Tfs.VsCommon return wrapped.FirstOrDefault(b => { - var visitor = new BranchContainsPathVisitor(remoteTfsPath, searchExactPath); + var visitor = new BranchTreeContainsPathVisitor(remoteTfsPath, searchExactPath); b.AcceptVisitor(visitor); return visitor.Found; }); diff --git a/GitTfs/Commands/Branch.cs b/GitTfs/Commands/Branch.cs index 6a1ed4b5..4043a85b 100644 --- a/GitTfs/Commands/Branch.cs +++ b/GitTfs/Commands/Branch.cs @@ -35,13 +35,51 @@ namespace Sep.Git.Tfs.Commands this.stdout = stdout; } - private class Visitor : IBranchVisitor + public int Run() + { + // should probably pull this from options so that it is settable from the command-line + const string remoteId = GitTfsConstants.DefaultRepositoryId; + + var tfsRemotes = globals.Repository.ReadAllTfsRemotes(); + if (DisplayRemotes) + { + WriteRemoteTfsBranchStructure(stdout, remoteId, tfsRemotes); + return GitTfsExitCodes.OK; + } + + WriteTfsRemoteDetails(stdout, tfsRemotes); + return GitTfsExitCodes.OK; + } + + private void WriteRemoteTfsBranchStructure(TextWriter writer, string remoteId, IEnumerable tfsRemotes) + { + writer.WriteLine("TFS branch structure:"); + + var repo = globals.Repository; + var remote = repo.ReadTfsRemote(remoteId); + var root = remote.Tfs.GetRootTfsBranchForRemotePath(remote.TfsRepositoryPath); + + var visitor = new WriteBranchStructureTreeVisitor(remote.TfsRepositoryPath, writer, tfsRemotes); + root.AcceptVisitor(visitor); + } + + private void WriteTfsRemoteDetails(TextWriter writer, IEnumerable tfsRemotes) + { + writer.WriteLine("Git-tfs remote details:"); + foreach (var remote in tfsRemotes) + { + writer.WriteLine("\n {0} -> {1} {2}", remote.Id, remote.TfsUrl, remote.TfsRepositoryPath); + writer.WriteLine(" {0} - {1} @ {2}", remote.RemoteRef, remote.MaxCommitHash, remote.MaxChangesetId); + } + } + + private class WriteBranchStructureTreeVisitor : IBranchTreeVisitor { private readonly TextWriter _stdout; private readonly string _targetPath; private readonly IEnumerable _tfsRemotes; - public Visitor(string targetPath, TextWriter writer, IEnumerable tfsRemotes = null) + public WriteBranchStructureTreeVisitor(string targetPath, TextWriter writer, IEnumerable tfsRemotes = null) { _targetPath = targetPath; _stdout = writer; @@ -51,10 +89,10 @@ namespace Sep.Git.Tfs.Commands public void Visit(IBranch branch, int level) { for (var i = 0; i < level-1; i++ ) - _stdout.Write(" | "); + _stdout.Write("| "); if (level > 0) - _stdout.Write(" +- "); + _stdout.Write("+- "); _stdout.Write(branch.Path); @@ -65,42 +103,11 @@ namespace Sep.Git.Tfs.Commands { var remote = _tfsRemotes.FirstOrDefault(r => r.TfsRepositoryPath == branch.Path); if (remote != null) - _stdout.Write(" -> " + remote.Id); + _stdout.Write("-> " + remote.Id); } _stdout.WriteLine(); } } - - public int Run() - { - var tfsRemotes = globals.Repository.ReadAllTfsRemotes(); - if (DisplayRemotes) - { - stdout.WriteLine("TFS branches:"); - stdout.WriteLine(""); - - var repo = globals.Repository; - var remote = repo.ReadTfsRemote(GitTfsConstants.DefaultRepositoryId); - - var root = remote.Tfs.GetRootTfsBranchForRemotePath(remote.TfsRepositoryPath); - - var visitor = new Visitor(remote.TfsRepositoryPath, stdout, tfsRemotes); - - root.AcceptVisitor(visitor); - - stdout.WriteLine(""); - - return GitTfsExitCodes.OK; - } - - stdout.WriteLine("Git-tfs remotes:"); - foreach (var remote in tfsRemotes) - { - stdout.WriteLine("\n {0} -> {1} {2}", remote.Id, remote.TfsUrl, remote.TfsRepositoryPath); - stdout.WriteLine(" {0} - {1} @ {2}", remote.RemoteRef, remote.MaxCommitHash, remote.MaxChangesetId); - } - return GitTfsExitCodes.OK; - } } } \ No newline at end of file diff --git a/GitTfs/Core/BranchVisitors/BranchContainsPathVisitor.cs b/GitTfs/Core/BranchVisitors/BranchTreeContainsPathVisitor.cs similarity index 82% rename from GitTfs/Core/BranchVisitors/BranchContainsPathVisitor.cs rename to GitTfs/Core/BranchVisitors/BranchTreeContainsPathVisitor.cs index 0e3ecc72..cfdcca36 100644 --- a/GitTfs/Core/BranchVisitors/BranchContainsPathVisitor.cs +++ b/GitTfs/Core/BranchVisitors/BranchTreeContainsPathVisitor.cs @@ -4,12 +4,12 @@ using Sep.Git.Tfs.Core.TfsInterop; namespace Sep.Git.Tfs.Core.BranchVisitors { - public class BranchContainsPathVisitor : IBranchVisitor + public class BranchTreeContainsPathVisitor : IBranchTreeVisitor { private string searchPath; private bool searchExactPath; - public BranchContainsPathVisitor(string searchPath, bool searchExactPath) + public BranchTreeContainsPathVisitor(string searchPath, bool searchExactPath) { this.searchPath = searchPath; this.searchExactPath = searchExactPath; diff --git a/GitTfs/Core/IBranchVisitor.cs b/GitTfs/Core/IBranchTreeVisitor.cs similarity index 81% rename from GitTfs/Core/IBranchVisitor.cs rename to GitTfs/Core/IBranchTreeVisitor.cs index 8c72266b..dd5fe5a5 100644 --- a/GitTfs/Core/IBranchVisitor.cs +++ b/GitTfs/Core/IBranchTreeVisitor.cs @@ -4,7 +4,7 @@ using Sep.Git.Tfs.Core.TfsInterop; namespace Sep.Git.Tfs.Core { - public interface IBranchVisitor + public interface IBranchTreeVisitor { void Visit(IBranch childBranch, int level); } diff --git a/GitTfs/Core/TfsInterop/IBranch.cs b/GitTfs/Core/TfsInterop/IBranch.cs index 6ae3357e..5b1b384c 100644 --- a/GitTfs/Core/TfsInterop/IBranch.cs +++ b/GitTfs/Core/TfsInterop/IBranch.cs @@ -13,12 +13,12 @@ namespace Sep.Git.Tfs.Core.TfsInterop public static class BranchExtensions { - public static void AcceptVisitor(this IBranch branch, IBranchVisitor visitor, int level = 0) + public static void AcceptVisitor(this IBranch branch, IBranchTreeVisitor treeVisitor, int level = 0) { - visitor.Visit(branch, level); + treeVisitor.Visit(branch, level); foreach (var childBranch in branch.ChildBranches) { - childBranch.AcceptVisitor(visitor, level + 1); + childBranch.AcceptVisitor(treeVisitor, level + 1); } } diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index 25e99e5c..de0aa5df 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -131,8 +131,8 @@ Properties\Version.cs - - + + diff --git a/GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs b/GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs index 5a508d17..6af16e1d 100644 --- a/GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs +++ b/GitTfsTest/Core/BranchVisitors/BranchContainsPathVisitorTest.cs @@ -24,7 +24,7 @@ namespace Sep.Git.Tfs.Test.Core.BranchVisitors [Fact] public void InexactMatch_WithoutTrailingSlash_IsFound() { - var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main", false); + var visitor = new BranchTreeContainsPathVisitor(@"$/Scratch/Source/Main", false); branch.AcceptVisitor(visitor); @@ -34,7 +34,7 @@ namespace Sep.Git.Tfs.Test.Core.BranchVisitors [Fact] public void InexactMatch_WithTrailingSlash_IsFound() { - var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main/", false); + var visitor = new BranchTreeContainsPathVisitor(@"$/Scratch/Source/Main/", false); branch.AcceptVisitor(visitor); @@ -44,7 +44,7 @@ namespace Sep.Git.Tfs.Test.Core.BranchVisitors [Fact] public void ExactMatch_WithoutTrailingSlash_IsFound() { - var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main", true); + var visitor = new BranchTreeContainsPathVisitor(@"$/Scratch/Source/Main", true); branch.AcceptVisitor(visitor); @@ -54,7 +54,7 @@ namespace Sep.Git.Tfs.Test.Core.BranchVisitors [Fact] public void ExactMatch_WithTrailingSlash_IsNotFound() { - var visitor = new BranchContainsPathVisitor(@"$/Scratch/Source/Main/", true); + var visitor = new BranchTreeContainsPathVisitor(@"$/Scratch/Source/Main/", true); branch.AcceptVisitor(visitor); From 823177570a59412ec64fd9ec9ee5dd4a713a76ab Mon Sep 17 00:00:00 2001 From: David Alpert Date: Wed, 2 Jan 2013 21:35:09 -0600 Subject: [PATCH 26/67] fixing whitespace in the output of git branch -r --- GitTfs/Commands/Branch.cs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/GitTfs/Commands/Branch.cs b/GitTfs/Commands/Branch.cs index 4043a85b..4b167937 100644 --- a/GitTfs/Commands/Branch.cs +++ b/GitTfs/Commands/Branch.cs @@ -53,7 +53,7 @@ namespace Sep.Git.Tfs.Commands private void WriteRemoteTfsBranchStructure(TextWriter writer, string remoteId, IEnumerable tfsRemotes) { - writer.WriteLine("TFS branch structure:"); + writer.WriteLine("\nTFS branch structure:"); var repo = globals.Repository; var remote = repo.ReadTfsRemote(remoteId); @@ -65,7 +65,7 @@ namespace Sep.Git.Tfs.Commands private void WriteTfsRemoteDetails(TextWriter writer, IEnumerable tfsRemotes) { - writer.WriteLine("Git-tfs remote details:"); + writer.WriteLine("\nGit-tfs remote details:"); foreach (var remote in tfsRemotes) { writer.WriteLine("\n {0} -> {1} {2}", remote.Id, remote.TfsUrl, remote.TfsRepositoryPath); @@ -88,24 +88,29 @@ namespace Sep.Git.Tfs.Commands public void Visit(IBranch branch, int level) { - for (var i = 0; i < level-1; i++ ) - _stdout.Write("| "); + for (var i = 0; i < level; i++ ) + _stdout.Write(" | "); + + _stdout.WriteLine(); + + for (var i = 0; i < level - 1; i++) + _stdout.Write(" | "); if (level > 0) - _stdout.Write("+- "); + _stdout.Write(" +-"); - _stdout.Write(branch.Path); - - if (branch.Path.Equals(_targetPath)) - _stdout.Write(" * "); + _stdout.Write(" {0}", branch.Path); if (_tfsRemotes != null) { var remote = _tfsRemotes.FirstOrDefault(r => r.TfsRepositoryPath == branch.Path); if (remote != null) - _stdout.Write("-> " + remote.Id); + _stdout.Write(" -> " + remote.Id); } + if (branch.Path.Equals(_targetPath)) + _stdout.Write(" [*]"); + _stdout.WriteLine(); } } From b5fb9b5b5ba470418749979c1071da2a8b08df8c Mon Sep 17 00:00:00 2001 From: Philippe Miossec Date: Mon, 24 Dec 2012 23:51:35 +0100 Subject: [PATCH 27/67] Add a note on the commit to keep trace of the workitems Manage workitems when fetching changesets with adding a note with the workitems displaying Id, title and a link to the workitem --- GitTfs.VsCommon/TfsHelper.Common.cs | 24 ++++++++++++++++++++++-- GitTfs/Core/GitRepository.cs | 6 ++++++ GitTfs/Core/GitTfsRemote.cs | 12 +++++++++++- GitTfs/Core/IGitRepository.cs | 4 +++- GitTfs/Core/ITfsWorkitem.cs | 10 ++++++++++ GitTfs/Core/TfsChangesetInfo.cs | 4 +++- GitTfs/Core/TfsWorkitem.cs | 10 ++++++++++ GitTfs/GitTfs.csproj | 4 +++- 8 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 GitTfs/Core/ITfsWorkitem.cs create mode 100644 GitTfs/Core/TfsWorkitem.cs diff --git a/GitTfs.VsCommon/TfsHelper.Common.cs b/GitTfs.VsCommon/TfsHelper.Common.cs index fe9a485f..8af5f26a 100644 --- a/GitTfs.VsCommon/TfsHelper.Common.cs +++ b/GitTfs.VsCommon/TfsHelper.Common.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net; +using Microsoft.TeamFoundation; using Microsoft.TeamFoundation.Server; using Microsoft.TeamFoundation.VersionControl.Client; using Microsoft.TeamFoundation.WorkItemTracking.Client; @@ -108,11 +109,18 @@ namespace Sep.Git.Tfs.VsCommon get { return GetService(); } } + private ILinking _linking; + private ILinking Linking + { + get { return _linking ?? (_linking = GetService()); } + } + public IEnumerable GetChangesets(string path, long startVersion, GitTfsRemote remote) { var changesets = VersionControl.QueryHistory(path, VersionSpec.Latest, 0, RecursionType.Full, null, new ChangesetVersionSpec((int) startVersion), VersionSpec.Latest, int.MaxValue, true, true, true); + return changesets.Cast() .OrderBy(changeset => changeset.ChangesetId) .Select(changeset => BuildTfsChangeset(changeset, remote)); @@ -133,7 +141,19 @@ namespace Sep.Git.Tfs.VsCommon private ITfsChangeset BuildTfsChangeset(Changeset changeset, GitTfsRemote remote) { var tfsChangeset = _container.With(this).With(_bridge.Wrap(changeset)).GetInstance(); - tfsChangeset.Summary = new TfsChangesetInfo {ChangesetId = changeset.ChangesetId, Remote = remote}; + tfsChangeset.Summary = new TfsChangesetInfo { ChangesetId = changeset.ChangesetId, Remote = remote }; + + if (changeset.WorkItems != null) + { + tfsChangeset.Summary.Workitems = changeset.WorkItems.Select(wi => new TfsWorkitem + { + Id = wi.Id, + Title = wi.Title, + Description = wi.Description, + Url = Linking.GetArtifactUrl(wi.Uri.AbsoluteUri) + }); + } + return tfsChangeset; } @@ -574,4 +594,4 @@ namespace Sep.Git.Tfs.VsCommon } } -} \ No newline at end of file +} diff --git a/GitTfs/Core/GitRepository.cs b/GitTfs/Core/GitRepository.cs index 31bf414a..1cecbe78 100644 --- a/GitTfs/Core/GitRepository.cs +++ b/GitTfs/Core/GitRepository.cs @@ -448,5 +448,11 @@ namespace Sep.Git.Tfs.Core if (_repository.Tags[name] == null) _repository.ApplyTag(name, sha, new Signature(Owner, emailOwner, new DateTimeOffset(creationDate)), comment); } + + public void CreateNote(string sha, string content, string owner, string emailOwner, DateTime creationDate) + { + Signature author = new Signature(owner, emailOwner, creationDate); + _repository.Notes.Add(new ObjectId(sha), content, author, author, "commits"); + } } } diff --git a/GitTfs/Core/GitTfsRemote.cs b/GitTfs/Core/GitTfsRemote.cs index 978ce821..99872601 100644 --- a/GitTfs/Core/GitTfsRemote.cs +++ b/GitTfs/Core/GitTfsRemote.cs @@ -184,7 +184,17 @@ namespace Sep.Git.Tfs.Core log.CommitParents.Add(parent); } } - UpdateRef(Commit(log), changeset.Summary.ChangesetId); + var commitSha = Commit(log); + UpdateRef(commitSha, changeset.Summary.ChangesetId); + if(changeset.Summary.Workitems.Count() != 0) + { + string workitemNote = "Workitems:\n"; + foreach(var workitem in changeset.Summary.Workitems) + { + workitemNote += String.Format("[{0}] {1}\n {2}\n", workitem.Id, workitem.Title, workitem.Url); + } + Repository.CreateNote(commitSha, workitemNote, log.AuthorName, log.AuthorEmail, log.Date); + } DoGcIfNeeded(); } } diff --git a/GitTfs/Core/IGitRepository.cs b/GitTfs/Core/IGitRepository.cs index a27e5453..e16235c6 100644 --- a/GitTfs/Core/IGitRepository.cs +++ b/GitTfs/Core/IGitRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using Sep.Git.Tfs.Commands; @@ -29,5 +30,6 @@ namespace Sep.Git.Tfs.Core bool CreateBranch(string gitBranchName, string target); string FindCommitHashByCommitMessage(string patternToFind); void CreateTag(string name, string sha, string comment, string Owner, string emailOwner, System.DateTime creationDate); + void CreateNote(string sha, string content, string owner, string emailOwner, DateTime creationDate); } } diff --git a/GitTfs/Core/ITfsWorkitem.cs b/GitTfs/Core/ITfsWorkitem.cs new file mode 100644 index 00000000..87325e26 --- /dev/null +++ b/GitTfs/Core/ITfsWorkitem.cs @@ -0,0 +1,10 @@ +namespace Sep.Git.Tfs.Core +{ + public interface ITfsWorkitem + { + int Id { get; set; } + string Title { get; set; } + string Description { get; set; } + string Url { get; set; } + } +} \ No newline at end of file diff --git a/GitTfs/Core/TfsChangesetInfo.cs b/GitTfs/Core/TfsChangesetInfo.cs index 8ff4db32..2e6c3b32 100644 --- a/GitTfs/Core/TfsChangesetInfo.cs +++ b/GitTfs/Core/TfsChangesetInfo.cs @@ -1,9 +1,11 @@ -namespace Sep.Git.Tfs.Core +using System.Collections.Generic; +namespace Sep.Git.Tfs.Core { public class TfsChangesetInfo { public IGitTfsRemote Remote { get; set; } public long ChangesetId { get; set; } public string GitCommit { get; set; } + public IEnumerable Workitems { get; set; } } } diff --git a/GitTfs/Core/TfsWorkitem.cs b/GitTfs/Core/TfsWorkitem.cs new file mode 100644 index 00000000..fd7fe919 --- /dev/null +++ b/GitTfs/Core/TfsWorkitem.cs @@ -0,0 +1,10 @@ +namespace Sep.Git.Tfs.Core +{ + public class TfsWorkitem : ITfsWorkitem + { + public int Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public string Url { get; set; } + } +} \ No newline at end of file diff --git a/GitTfs/GitTfs.csproj b/GitTfs/GitTfs.csproj index a235f4f8..4dd5011f 100644 --- a/GitTfs/GitTfs.csproj +++ b/GitTfs/GitTfs.csproj @@ -130,6 +130,7 @@ Properties\Version.cs + @@ -200,6 +201,7 @@ + @@ -291,4 +293,4 @@ - \ No newline at end of file + From 9fede73533ecf4604c920e9aa37c6967bca55ba8 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 4 Jan 2013 16:41:50 -0500 Subject: [PATCH 28/67] Build fake workspace like the normal workspace. --- GitTfs.VsFake/TfsHelper.VsFake.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 670a4ddd..4812f7a5 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -188,7 +188,12 @@ namespace Sep.Git.Tfs.VsFake { Trace.WriteLine("Setting up a TFS workspace at " + directory); var fakeWorkspace = new FakeWorkspace(directory, remote.TfsRepositoryPath); - var workspace = new TfsWorkspace(fakeWorkspace, directory, _stdout, versionToFetch, remote, null, this, null); + var workspace = _container.With("localDirectory").EqualTo(directory) + .With("remote").EqualTo(remote) + .With("contextVersion").EqualTo(versionToFetch) + .With("workspace").EqualTo(fakeWorkspace) + .With("tfsHelper").EqualTo(this) + .GetInstance(); action(workspace); } From d704a3050742a1b51d06fcf455b08831e8323645 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 4 Jan 2013 16:43:41 -0500 Subject: [PATCH 29/67] Use libgit2sharp for rev-parse. --- GitTfsTest/Integration/CloneTests.cs | 6 ++-- GitTfsTest/Integration/IntegrationHelper.cs | 31 +++++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/GitTfsTest/Integration/CloneTests.cs b/GitTfsTest/Integration/CloneTests.cs index 28c27b4b..8be73c12 100644 --- a/GitTfsTest/Integration/CloneTests.cs +++ b/GitTfsTest/Integration/CloneTests.cs @@ -39,7 +39,7 @@ namespace Sep.Git.Tfs.Test.Integration const string expectedSha = "4053764b2868a2be71ae7f5f113ad84dff8a052a"; h.AssertRef("MyProject", "HEAD", expectedSha); h.AssertRef("MyProject", "master", expectedSha); - h.AssertRef("MyProject", "tfs/default", expectedSha); + h.AssertRef("MyProject", "refs/remotes/tfs/default", expectedSha); h.AssertEmptyWorkspace("MyProject"); } @@ -60,7 +60,7 @@ namespace Sep.Git.Tfs.Test.Integration const string expectedSha = "d64d883266eca65bede947c79529318718a0d8eb"; h.AssertRef("MyProject", "HEAD", expectedSha); h.AssertRef("MyProject", "master", expectedSha); - h.AssertRef("MyProject", "tfs/default", expectedSha); + h.AssertRef("MyProject", "refs/remotes/tfs/default", expectedSha); h.AssertFileInWorkspace("MyProject", "Folder/File.txt", "File contents"); h.AssertFileInWorkspace("MyProject", "README", "tldr"); } @@ -127,7 +127,7 @@ namespace Sep.Git.Tfs.Test.Integration { h.AssertRef("MyProject", "HEAD", expectedSha); h.AssertRef("MyProject", "master", expectedSha); - h.AssertRef("MyProject", "tfs/default", expectedSha); + h.AssertRef("MyProject", "refs/remotes/tfs/default", expectedSha); } [FactExceptOnUnix] diff --git a/GitTfsTest/Integration/IntegrationHelper.cs b/GitTfsTest/Integration/IntegrationHelper.cs index 660b3349..2f79f548 100644 --- a/GitTfsTest/Integration/IntegrationHelper.cs +++ b/GitTfsTest/Integration/IntegrationHelper.cs @@ -10,6 +10,7 @@ using Sep.Git.Tfs.Core.TfsInterop; using Sep.Git.Tfs.VsFake; using Xunit; using Xunit.Sdk; +using LibGit2Sharp; namespace Sep.Git.Tfs.Test.Integration { @@ -34,6 +35,12 @@ namespace Sep.Git.Tfs.Test.Integration public void Dispose() { + while (!_repositories.Empty()) + { + var repo = _repositories.First(); + repo.Value.Dispose(); + _repositories.Remove(repo.Key); + } if (_workdir != null) { try @@ -47,6 +54,15 @@ namespace Sep.Git.Tfs.Test.Integration } } + private Dictionary _repositories = new Dictionary(); + public Repository Repository(string path) + { + path = Path.Combine(Workdir, path); + if (!_repositories.ContainsKey(path)) + _repositories.Add(path, new Repository(path)); + return _repositories[path]; + } + #endregion #region set up vsfake script @@ -155,14 +171,7 @@ namespace Sep.Git.Tfs.Test.Integration private string RevParse(string repodir, string gitref) { - // This really should delegate to libgit2, which isn't yet a part of GitTfs. - var gitpath = Path.Combine(Workdir, repodir, ".git"); - var resolved = ReadIfPresent(Path.Combine(gitpath, gitref)) ?? - ReadIfPresent(Path.Combine(gitpath, "refs", "heads", gitref)) ?? - ReadIfPresent(Path.Combine(gitpath, "refs", "remotes", gitref)); - if (resolved != null && resolved.StartsWith("ref:")) - return RevParse(repodir, resolved.Replace("ref:", "").Trim()); - return resolved; + return Repository(repodir).Lookup(gitref).Sha; } private string ReadIfPresent(string path) @@ -179,8 +188,7 @@ namespace Sep.Git.Tfs.Test.Integration public void AssertCleanWorkspace(string repodir) { - var repo = new LibGit2Sharp.Repository(Path.Combine(Workdir, repodir)); - var status = repo.Index.RetrieveStatus(); + var status = Repository(repodir).Index.RetrieveStatus(); AssertEqual(new List(), status.Select(statusEntry => "" + statusEntry.State + ": " + statusEntry.FilePath).ToList(), "repo status"); } @@ -193,8 +201,7 @@ namespace Sep.Git.Tfs.Test.Integration public void AssertCommitMessage(string repodir, string commitish, string message) { - var repo = new LibGit2Sharp.Repository(Path.Combine(Workdir, repodir)); - var commit = LibGit2Sharp.RepositoryExtensions.Lookup(repo, commitish); + var commit = Repository(repodir).Lookup(commitish); AssertEqual(message, commit.Message, "Commit message of " + commitish); } From ed016a21d8c5b0eee6754a473eb9f60b4f3eb1cd Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 4 Jan 2013 16:52:01 -0500 Subject: [PATCH 30/67] Run integration tests in-process. Hopefully this doesn't mess too many things up. --- GitTfs.VsFake/TfsHelper.VsFake.cs | 10 ++-- GitTfs.VsFake/TfsPlugin.cs | 16 +----- GitTfs/GitTfs.cs | 13 +++-- GitTfs/Program.cs | 10 ++-- GitTfsTest/Commands/InitBranchTest.cs | 6 +-- GitTfsTest/Integration/IntegrationHelper.cs | 54 +++++++++++++++------ 6 files changed, 63 insertions(+), 46 deletions(-) diff --git a/GitTfs.VsFake/TfsHelper.VsFake.cs b/GitTfs.VsFake/TfsHelper.VsFake.cs index 4812f7a5..26647470 100644 --- a/GitTfs.VsFake/TfsHelper.VsFake.cs +++ b/GitTfs.VsFake/TfsHelper.VsFake.cs @@ -16,12 +16,14 @@ namespace Sep.Git.Tfs.VsFake #region misc/null IContainer _container; - private TextWriter _stdout; + TextWriter _stdout; + Script _script; - public TfsHelper(IContainer container, TextWriter stdout) + public TfsHelper(IContainer container, TextWriter stdout, Script script) { _container = container; _stdout = stdout; + _script = script; } public string TfsClientLibraryVersion { get { return "(FAKE)"; } } @@ -51,12 +53,12 @@ namespace Sep.Git.Tfs.VsFake public ITfsChangeset GetLatestChangeset(GitTfsRemote remote) { - return TfsPlugin.Script.Changesets.LastOrDefault().AndAnd(x => BuildTfsChangeset(x, remote)); + return _script.Changesets.LastOrDefault().AndAnd(x => BuildTfsChangeset(x, remote)); } public IEnumerable GetChangesets(string path, long startVersion, GitTfsRemote remote) { - return TfsPlugin.Script.Changesets.Where(x => x.Id >= startVersion).Select(x => BuildTfsChangeset(x, remote)); + return _script.Changesets.Where(x => x.Id >= startVersion).Select(x => BuildTfsChangeset(x, remote)); } private ITfsChangeset BuildTfsChangeset(ScriptedChangeset changeset, GitTfsRemote remote) diff --git a/GitTfs.VsFake/TfsPlugin.cs b/GitTfs.VsFake/TfsPlugin.cs index e25db00f..b0922f0c 100644 --- a/GitTfs.VsFake/TfsPlugin.cs +++ b/GitTfs.VsFake/TfsPlugin.cs @@ -10,11 +10,12 @@ namespace Sep.Git.Tfs.VsFake { base.Initialize(scan); } + */ public override void Initialize(StructureMap.ConfigurationExpression config) { + config.For