Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/GitHub.Exports/Services/IVSUIContextFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public VSUIContextChangedEventArgs(bool activated)
public interface IVSUIContext
{
bool IsActive { get; }
event EventHandler<VSUIContextChangedEventArgs> UIContextChanged;
void WhenActivated(Action action);
}
}
9 changes: 2 additions & 7 deletions src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Windows;
using System.Threading;
using System.Runtime.InteropServices;
using GitHub.Services;
Expand All @@ -22,13 +21,9 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
{
var usageTracker = (IUsageTracker)await GetServiceAsync(typeof(IUsageTracker));
var serviceProvider = (IGitHubServiceProvider)await GetServiceAsync(typeof(IGitHubServiceProvider));
var gitExt = (IVSGitExt)await GetServiceAsync(typeof(IVSGitExt));

// NOTE: ThreadingHelper.SwitchToMainThreadAsync() doesn't return until a solution is loaded. Using Dispatcher.Invoke instead.
Application.Current.Dispatcher.Invoke(() =>
{
var gitExt = serviceProvider.GetService<IVSGitExt>();
new PullRequestStatusBarManager(gitExt, usageTracker, serviceProvider);
});
new PullRequestStatusBarManager(gitExt, usageTracker, serviceProvider);
}
}
}
80 changes: 30 additions & 50 deletions src/GitHub.TeamFoundation.14/Services/VSGitExt.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using GitHub.Models;
Expand All @@ -8,91 +9,67 @@
using GitHub.TeamFoundation.Services;
using Serilog;
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
using Task = System.Threading.Tasks.Task;

namespace GitHub.VisualStudio.Base
{
/// <summary>
/// This service acts as an always available version of <see cref="IGitExt"/>.
/// </summary>
[Export(typeof(IVSGitExt))]
[PartCreationPolicy(CreationPolicy.Shared)]
/// <remarks>
/// Initialization for this service will be done asynchronously and the <see cref="IGitExt" /> service will be
/// retrieved using <see cref="GetServiceAsync" />. This means the service can be constructed and subscribed to from a background thread.
/// </remarks>
public class VSGitExt : IVSGitExt
{
static readonly ILogger log = LogManager.ForContext<VSGitExt>();

readonly IGitHubServiceProvider serviceProvider;
readonly IVSUIContext context;
readonly Func<Type, Task<object>> getServiceAsync;
readonly ILocalRepositoryModelFactory repositoryFactory;
readonly object refreshLock = new object();

IGitExt gitService;
IReadOnlyList<ILocalRepositoryModel> activeRepositories;

[ImportingConstructor]
public VSGitExt(IGitHubServiceProvider serviceProvider)
: this(serviceProvider, new VSUIContextFactory(), new LocalRepositoryModelFactory())
public VSGitExt(Func<Type, Task<object>> getServiceAsync)
: this(getServiceAsync, new VSUIContextFactory(), new LocalRepositoryModelFactory())
{
}

public VSGitExt(IGitHubServiceProvider serviceProvider, IVSUIContextFactory factory, ILocalRepositoryModelFactory repositoryFactory)
public VSGitExt(Func<Type, Task<object>> getServiceAsync, IVSUIContextFactory factory, ILocalRepositoryModelFactory repositoryFactory)
{
this.serviceProvider = serviceProvider;
this.getServiceAsync = getServiceAsync;
this.repositoryFactory = repositoryFactory;

// The IGitExt service isn't available when a TFS based solution is opened directly.
// It will become available when moving to a Git based solution (cause a UIContext event to fire).
context = factory.GetUIContext(new Guid(Guids.GitSccProviderId));

// Start with empty array until we have a change to initialize.
// Start with empty array until we have a chance to initialize.
ActiveRepositories = Array.Empty<ILocalRepositoryModel>();

if (context.IsActive && TryInitialize())
{
// Refresh ActiveRepositories on background thread so we don't delay startup.
InitializeTask = Task.Run(() => RefreshActiveRepositories());
}
else
{
// If we're not in the UIContext or TryInitialize fails, have another go when the UIContext changes.
context.UIContextChanged += ContextChanged;
log.Debug("VSGitExt will be initialized later");
InitializeTask = Task.CompletedTask;
}
// The IGitExt service isn't available when a TFS based solution is opened directly.
// It will become available when moving to a Git based solution (and cause a UIContext event to fire).
var context = factory.GetUIContext(new Guid(Guids.GitSccProviderId));
context.WhenActivated(() => Initialize());
}

void ContextChanged(object sender, VSUIContextChangedEventArgs e)
void Initialize()
{
// If we're in the UIContext and TryInitialize succeeds, we can stop listening for events.
// NOTE: this event can fire with UIContext=true in a TFS solution (not just Git).
if (e.Activated && TryInitialize())
PendingTasks = getServiceAsync(typeof(IGitExt)).ContinueWith(t =>
{
// Refresh ActiveRepositories on background thread so we don't delay UI context change.
InitializeTask = Task.Run(() => RefreshActiveRepositories());
context.UIContextChanged -= ContextChanged;
log.Debug("Initialized VSGitExt on UIContextChanged");
}
}
gitService = (IGitExt)t.Result;
if (gitService == null)
{
log.Error("Couldn't find IGitExt service");
return;
}

bool TryInitialize()
{
gitService = serviceProvider.GetService<IGitExt>();
if (gitService != null)
{
RefreshActiveRepositories();
gitService.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(gitService.ActiveRepositories))
{
RefreshActiveRepositories();
}
};

log.Debug("Found IGitExt service and initialized VSGitExt");
return true;
}

log.Error("Couldn't find IGitExt service");
return false;
}, TaskScheduler.Default);
}

void RefreshActiveRepositories()
Expand All @@ -104,7 +81,7 @@ void RefreshActiveRepositories()
log.Debug(
"IGitExt.ActiveRepositories (#{Id}) returned {Repositories}",
gitService.GetHashCode(),
gitService?.ActiveRepositories.Select(x => x.RepositoryPath));
gitService.ActiveRepositories.Select(x => x.RepositoryPath));

ActiveRepositories = gitService?.ActiveRepositories.Select(x => repositoryFactory.Create(x.RepositoryPath)).ToList();
}
Expand Down Expand Up @@ -136,6 +113,9 @@ private set

public event Action ActiveRepositoriesChanged;

public Task InitializeTask { get; private set; }
/// <summary>
/// Tasks that are pending execution on the thread pool.
/// </summary>
public Task PendingTasks { get; private set; } = Task.CompletedTask;
}
}
28 changes: 2 additions & 26 deletions src/GitHub.TeamFoundation.14/Services/VSUIContextFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Shell;
using GitHub.Services;

Expand All @@ -17,36 +15,14 @@ public IVSUIContext GetUIContext(Guid contextGuid)
class VSUIContext : IVSUIContext
{
readonly UIContext context;
readonly Dictionary<EventHandler<VSUIContextChangedEventArgs>, EventHandler<UIContextChangedEventArgs>> handlers =
new Dictionary<EventHandler<VSUIContextChangedEventArgs>, EventHandler<UIContextChangedEventArgs>>();

public VSUIContext(UIContext context)
{
this.context = context;
}

public bool IsActive { get { return context.IsActive; } }

public event EventHandler<VSUIContextChangedEventArgs> UIContextChanged
{
add
{
EventHandler<UIContextChangedEventArgs> handler = null;
if (!handlers.TryGetValue(value, out handler))
{
handler = (s, e) => value.Invoke(s, new VSUIContextChangedEventArgs(e.Activated));
handlers.Add(value, handler);
}
context.UIContextChanged += handler;
}
remove
{
EventHandler<UIContextChangedEventArgs> handler = null;
if (handlers.TryGetValue(value, out handler))
{
handlers.Remove(value);
context.UIContextChanged -= handler;
}
}
}
public void WhenActivated(Action action) => context.WhenActivated(action);
}
}
3 changes: 3 additions & 0 deletions src/GitHub.VisualStudio/GitHub.VisualStudio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@
<Compile Include="Services\UsageTracker.cs" />
<Compile Include="Services\LoginManagerDispatcher.cs" />
<Compile Include="Services\UsageTrackerDispatcher.cs" />
<Compile Include="Services\VSGitExtFactory.cs" />
<Compile Include="Settings\Constants.cs" />
<Compile Include="Services\ConnectionManager.cs" />
<Compile Include="Services\Program.cs" />
Expand Down Expand Up @@ -679,13 +680,15 @@
<Private>True</Private>
<IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX>
<IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly>
<Aliases>TF14</Aliases>
</ProjectReference>
<ProjectReference Include="..\GitHub.TeamFoundation.15\GitHub.TeamFoundation.15.csproj">
<Project>{161dbf01-1dbf-4b00-8551-c5c00f26720e}</Project>
<Name>GitHub.TeamFoundation.15</Name>
<Private>True</Private>
<IncludeOutputGroupsInVSIX>BuiltProjectOutputGroup;GetCopyToOutputDirectoryItems;DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIX>
<IncludeOutputGroupsInVSIXLocalOnly>DebugSymbolsProjectOutputGroup;</IncludeOutputGroupsInVSIXLocalOnly>
<Aliases>TF15</Aliases>
</ProjectReference>
<ProjectReference Include="..\GitHub.UI.Reactive\GitHub.UI.Reactive.csproj">
<Project>{158b05e8-fdbc-4d71-b871-c96e28d5adf5}</Project>
Expand Down
8 changes: 7 additions & 1 deletion src/GitHub.VisualStudio/GitHubPackage.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -113,6 +112,7 @@ public GHClient(IProgram program)
[ProvideService(typeof(IGitHubServiceProvider), IsAsyncQueryable = true)]
[ProvideService(typeof(IUsageTracker), IsAsyncQueryable = true)]
[ProvideService(typeof(IUsageService), IsAsyncQueryable = true)]
[ProvideService(typeof(IVSGitExt), IsAsyncQueryable = true)]
[ProvideService(typeof(IGitHubToolWindowManager))]
[Guid(ServiceProviderPackageId)]
public sealed class ServiceProviderPackage : AsyncPackage, IServiceProviderPackage, IGitHubToolWindowManager
Expand Down Expand Up @@ -150,6 +150,7 @@ Version VSVersion
protected override Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
AddService(typeof(IGitHubServiceProvider), CreateService, true);
AddService(typeof(IVSGitExt), CreateService, true);
AddService(typeof(IUsageTracker), CreateService, true);
AddService(typeof(IUsageService), CreateService, true);
AddService(typeof(ILoginManager), CreateService, true);
Expand Down Expand Up @@ -258,6 +259,11 @@ async Task<object> CreateService(IAsyncServiceContainer container, CancellationT
var serviceProvider = await GetServiceAsync(typeof(IGitHubServiceProvider)) as IGitHubServiceProvider;
return new UsageTracker(serviceProvider, usageService);
}
else if (serviceType == typeof(IVSGitExt))
{
var vsVersion = ApplicationInfo.GetHostVersionInfo().FileMajorPart;
return VSGitExtFactory.Create(vsVersion, this);
}
else if (serviceType == typeof(IGitHubToolWindowManager))
{
return this;
Expand Down
47 changes: 47 additions & 0 deletions src/GitHub.VisualStudio/Services/VSGitExtFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
extern alias TF14;
extern alias TF15;

using System;
using System.ComponentModel.Composition;
using GitHub.Info;
using GitHub.Logging;
using Serilog;
using Microsoft.VisualStudio.Shell;
using VSGitExt14 = TF14.GitHub.VisualStudio.Base.VSGitExt;
using VSGitExt15 = TF15.GitHub.VisualStudio.Base.VSGitExt;

namespace GitHub.Services
{
[PartCreationPolicy(CreationPolicy.Shared)]
public class VSGitExtFactory
{
static readonly ILogger log = LogManager.ForContext<VSGitExtFactory>();

[ImportingConstructor]
public VSGitExtFactory(IGitHubServiceProvider serviceProvider)
{
VSGitExt = serviceProvider.GetService<IVSGitExt>();
}

public static IVSGitExt Create(int vsVersion, IAsyncServiceProvider sp)
{
switch (vsVersion)
{
case 14:
return Create(() => new VSGitExt14(sp.GetServiceAsync));
case 15:
return Create(() => new VSGitExt15(sp.GetServiceAsync));
default:
log.Error("There is no IVSGitExt implementation for DTE version {Version}", vsVersion);
return null;
}
}

// NOTE: We're being careful to only reference VSGitExt14 and VSGitExt15 from inside a lambda expression.
// This ensures that only the type that's compatible with the running DTE version is loaded.
static IVSGitExt Create(Func<IVSGitExt> factory) => factory.Invoke();

[Export]
public IVSGitExt VSGitExt { get; }
}
}
Loading