diff --git a/install.cmd b/install.cmd new file mode 100644 index 000000000..7a1477655 --- /dev/null +++ b/install.cmd @@ -0,0 +1,3 @@ +@set PATH=%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\Common7\IDE;%PATH% +vsixinstaller /q /u:"c3d3dc68-c977-411f-b3e8-03b0dccf7dfc" +vsixinstaller "%cd%\build\Debug\GitHub.VisualStudio.vsix" diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 0d258786b..88c969230 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -244,6 +244,10 @@ {08dd4305-7787-4823-a53f-4d0f725a07f3} Octokit + + {4A84E568-CA86-4510-8CD0-90D3EF9B65F9} + Rothko + {6afe2e2d-6db0-4430-a2ea-f5f5388d2f78} GitHub.Extensions diff --git a/src/GitHub.Exports/Services/IVSServices.cs b/src/GitHub.Exports/Services/IVSServices.cs index 1f8df8db3..0ed713d13 100644 --- a/src/GitHub.Exports/Services/IVSServices.cs +++ b/src/GitHub.Exports/Services/IVSServices.cs @@ -10,5 +10,6 @@ namespace GitHub.Services void ActivityLogMessage(string message); void ActivityLogWarning(string message); void ActivityLogError(string message); + bool TryOpenRepository(string directory); } } \ No newline at end of file diff --git a/src/GitHub.Exports/Services/Services.cs b/src/GitHub.Exports/Services/Services.cs index 04d2a2a59..a8019ee6a 100644 --- a/src/GitHub.Exports/Services/Services.cs +++ b/src/GitHub.Exports/Services/Services.cs @@ -4,7 +4,6 @@ using EnvDTE80; using GitHub.Info; using GitHub.Primitives; using GitHub.Services; -using LibGit2Sharp; using Microsoft.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Shell.Interop; diff --git a/src/GitHub.Exports/Services/VSServices.cs b/src/GitHub.Exports/Services/VSServices.cs index 95e8e01bb..18d600a7f 100644 --- a/src/GitHub.Exports/Services/VSServices.cs +++ b/src/GitHub.Exports/Services/VSServices.cs @@ -1,12 +1,13 @@ using System; -using System.Collections; -using System.Collections.Generic; +using System.IO; +using System.Linq; using System.ComponentModel.Composition; using System.Globalization; using GitHub.VisualStudio; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; -using System.Linq; +using DTE = EnvDTE.DTE; +using Rothko; namespace GitHub.Services { @@ -16,6 +17,10 @@ namespace GitHub.Services { readonly IGitHubServiceProvider serviceProvider; + // Use a prefix (~$) that is defined in the default VS gitignore. + public const string TempSolutionName = "~$GitHubVSTemp$~"; + + [ImportingConstructor] public VSServices(IGitHubServiceProvider serviceProvider) { @@ -68,6 +73,73 @@ namespace GitHub.Services } } + /// Open a repository in Team Explorer + /// + /// There doesn't appear to be a command that directly opens a target repo. + /// Our workaround is to create, open and delete a solution in the repo directory. + /// This triggers an event that causes the target repo to open. ;) + /// + /// The path to the repository to open + /// True if a transient solution was successfully created in target directory (which should trigger opening of repository). + public bool TryOpenRepository(string repoPath) + { + var os = serviceProvider.TryGetService(); + if (os == null) + { + VsOutputLogger.WriteLine("TryOpenRepository couldn't find IOperatingSystem service."); + return false; + } + + var dte = serviceProvider.TryGetService(); + if (dte == null) + { + VsOutputLogger.WriteLine("TryOpenRepository couldn't find DTE service."); + return false; + } + + var repoDir = os.Directory.GetDirectory(repoPath); + if(!repoDir.Exists) + { + return false; + } + + bool solutionCreated = false; + try + { + dte.Solution.Create(repoPath, TempSolutionName); + solutionCreated = true; + + dte.Solution.Close(false); // Don't create a .sln file when we close. + } + catch (Exception e) + { + VsOutputLogger.WriteLine("Error opening repository. {0}", e); + } + finally + { + TryCleanupSolutionUserFiles(os, repoPath, TempSolutionName); + } + return solutionCreated; + } + + void TryCleanupSolutionUserFiles(IOperatingSystem os, string repoPath, string slnName) + { + var vsTempPath = Path.Combine(repoPath, ".vs", slnName); + try + { + // Clean up the dummy solution's subdirectory inside `.vs`. + var vsTempDir = os.Directory.GetDirectory(vsTempPath); + if (vsTempDir.Exists) + { + vsTempDir.Delete(true); + } + } + catch (Exception e) + { + VsOutputLogger.WriteLine("Couldn't clean up {0}. {1}", vsTempPath, e); + } + } + const string RegistryRootKey = @"Software\Microsoft\VisualStudio"; const string EnvVersionKey = "EnvVersion"; string GetVSVersion() diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs index de9dbf250..fae132e38 100644 --- a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection.cs @@ -25,10 +25,12 @@ namespace GitHub.VisualStudio.TeamExplorer.Connect { public class GitHubConnectSection : TeamExplorerSectionBase, IGitHubConnectSection { + readonly IPackageSettings packageSettings; + readonly IVSServices vsServices; readonly int sectionIndex; + bool isCloning; bool isCreating; - IPackageSettings packageSettings; GitHubConnectSectionState settings; [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] @@ -92,6 +94,7 @@ namespace GitHub.VisualStudio.TeamExplorer.Connect ITeamExplorerServiceHolder holder, IConnectionManager manager, IPackageSettings packageSettings, + IVSServices vsServices, int index) : base(serviceProvider, apiFactory, holder, manager) { @@ -102,6 +105,7 @@ namespace GitHub.VisualStudio.TeamExplorer.Connect sectionIndex = index; this.packageSettings = packageSettings; + this.vsServices = vsServices; connectionManager.Connections.CollectionChanged += RefreshConnections; PropertyChanged += OnPropertyChange; @@ -344,22 +348,23 @@ namespace GitHub.VisualStudio.TeamExplorer.Connect public bool OpenRepository() { var old = Repositories.FirstOrDefault(x => x.Equals(Holder.ActiveRepo)); - // open the solution selection dialog when the user wants to switch to a different repo - // since there's no other way of changing the source control context in VS if (!Equals(SelectedRepository, old)) { - if (ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(SelectedRepository.LocalPath, 1))) + var opened = vsServices.TryOpenRepository(SelectedRepository.LocalPath); + if (!opened) { - ServiceProvider.TryGetService()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); - return true; - } - else - { - SelectedRepository = old; - return false; + // TryOpenRepository might fail because dir no longer exists. Let user find solution themselves. + opened = ErrorHandler.Succeeded(ServiceProvider.GetSolution().OpenSolutionViaDlg(SelectedRepository.LocalPath, 1)); + if (!opened) + { + return false; + } } } - return false; + + // Navigate away when we're on the correct source control contexts. + ServiceProvider.TryGetService()?.NavigateToPage(new Guid(TeamExplorerPageIds.Home), null); + return true; } void StartFlow(UIControllerFlow controllerFlow) diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection0.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection0.cs index d1522cb30..c7f0c033c 100644 --- a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection0.cs +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection0.cs @@ -18,8 +18,9 @@ namespace GitHub.VisualStudio.TeamExplorer.Connect ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, IConnectionManager manager, - IPackageSettings settings) - : base(serviceProvider, apiFactory, holder, manager, settings, 0) + IPackageSettings settings, + IVSServices vsServices) + : base(serviceProvider, apiFactory, holder, manager, settings, vsServices, 0) { } } diff --git a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection1.cs b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection1.cs index b616bfb56..f6020e6ed 100644 --- a/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection1.cs +++ b/src/GitHub.TeamFoundation.14/Connect/GitHubConnectSection1.cs @@ -18,8 +18,9 @@ namespace GitHub.VisualStudio.TeamExplorer.Connect ISimpleApiClientFactory apiFactory, ITeamExplorerServiceHolder holder, IConnectionManager manager, - IPackageSettings settings) - : base(serviceProvider, apiFactory, holder, manager, settings, 1) + IPackageSettings settings, + IVSServices vsServices) + : base(serviceProvider, apiFactory, holder, manager, settings, vsServices, 1) { } } diff --git a/src/UnitTests/GitHub.Exports/VSServicesTests.cs b/src/UnitTests/GitHub.Exports/VSServicesTests.cs index 436204d9b..bf23f4959 100644 --- a/src/UnitTests/GitHub.Exports/VSServicesTests.cs +++ b/src/UnitTests/GitHub.Exports/VSServicesTests.cs @@ -1,13 +1,112 @@ using System; +using System.IO; +using System.Runtime.InteropServices; using GitHub.Services; -using Microsoft.TeamFoundation.Git.Controls.Extensibility; using NSubstitute; using Xunit; +using DTE = EnvDTE.DTE; +using Rothko; public class VSServicesTests { + public class TheTryOpenRepositoryMethod : TestBaseClass + { + [Fact] + public void NoExceptions_ReturnsTrue() + { + var repoDir = @"x:\repo"; + var target = CreateVSServices(repoDir); + + var success = target.TryOpenRepository(repoDir); + + Assert.True(success); + } + + [Fact] + public void SolutionCreateThrows_ReturnsFalse() + { + var repoDir = @"x:\repo"; + var dte = Substitute.For(); + dte.Solution.When(s => s.Create(Arg.Any(), Arg.Any())).Do( + ci => { throw new COMException(); }); + var target = CreateVSServices(repoDir, dte: dte); + + var success = target.TryOpenRepository(""); + + Assert.False(success); + } + + [Fact] + public void RepoDirExistsFalse_ReturnFalse() + { + var repoDir = @"x:\repo"; + var os = Substitute.For(); + //var directoryInfo = Substitute.For(); + //directoryInfo.Exists.Returns(false); + //os.Directory.GetDirectory(repoDir).Returns(directoryInfo); + var target = CreateVSServices(null, os: os); + + var success = target.TryOpenRepository(repoDir); + + Assert.False(success); + } + + [Fact] + public void DeleteThrowsIOException_ReturnTrue() + { + var repoDir = @"x:\repo"; + var tempDir = Path.Combine(repoDir, ".vs", VSServices.TempSolutionName); + var os = Substitute.For(); + var directoryInfo = Substitute.For(); + directoryInfo.Exists.Returns(true); + os.Directory.GetDirectory(tempDir).Returns(directoryInfo); + directoryInfo.When(di => di.Delete(true)).Do( + ci => { throw new IOException(); }); + var target = CreateVSServices(repoDir, os: os); + + var success = target.TryOpenRepository(repoDir); + + Assert.True(success); + } + + [Fact] + public void SolutionCreate_DeleteVsSolutionSubdir() + { + var repoDir = @"x:\repo"; + var tempDir = Path.Combine(repoDir, ".vs", VSServices.TempSolutionName); + var os = Substitute.For(); + var directoryInfo = Substitute.For(); + directoryInfo.Exists.Returns(true); + os.Directory.GetDirectory(tempDir).Returns(directoryInfo); + var target = CreateVSServices(repoDir, os: os); + + var success = target.TryOpenRepository(repoDir); + + directoryInfo.Received().Delete(true); + } + + VSServices CreateVSServices(string repoDir, IOperatingSystem os = null, DTE dte = null) + { + os = os ?? Substitute.For(); + dte = dte ?? Substitute.For(); + + if (repoDir != null) + { + var directoryInfo = Substitute.For(); + directoryInfo.Exists.Returns(true); + os.Directory.GetDirectory(repoDir).Returns(directoryInfo); + } + + var provider = Substitute.For(); + provider.TryGetService().Returns(dte); + provider.TryGetService().Returns(os); + return new VSServices(provider); + } + } + public class TheCloneMethod : TestBaseClass { + /* [Theory] [InlineData(true, CloneOptions.RecurseSubmodule)] [InlineData(false, CloneOptions.None)] @@ -24,5 +123,6 @@ public class VSServicesTests gitRepositoriesExt.Received() .Clone("https://github.com/github/visualstudio", @"c:\fake\ghfvs", expectedCloneOptions); } + */ } } diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index d994a9456..1ce0630e9 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -35,6 +35,9 @@ 4 + + False + ..\..\packages\LibGit2Sharp.0.22.0\lib\net40\LibGit2Sharp.dll True @@ -238,7 +241,7 @@ - +