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
68 commits
Select commit Hold shift + click to select a range
b834336
Add TeamExplorerContext MEF service
jcansdale Jan 16, 2018
4e9d1ec
Harden reflection code and add logging
jcansdale Jan 17, 2018
e374c36
Only fire StatusChanged on BranchName/HeadSha change
jcansdale Jan 17, 2018
416ec35
Ignore when ActiveRepositories is empty
jcansdale Jan 17, 2018
768eca1
Don't throw if CloneUrl is null
jcansdale Jan 17, 2018
00051bb
Change PullRequestSessionManager to use ITeamExplorerContext
jcansdale Jan 17, 2018
f06ff66
Change PR session if Number or Owner changes
jcansdale Jan 17, 2018
8079f17
GetSession doesn't accept a null pullRequest
jcansdale Jan 17, 2018
d31d9a4
Set ActiveRepository to null when solution isn't in Git
jcansdale Jan 18, 2018
6fbfb0a
Remove IVSGitExt hack from PullRequestDetailViewModel
jcansdale Jan 18, 2018
7c43171
Add tests for TeamExplorerContext
jcansdale Jan 18, 2018
4b67153
Add tests for TeamExplorerContext.PropertyChanged event
jcansdale Jan 18, 2018
cb8c1b6
Add tests for TeamExplorerContext.StatusChanged event
jcansdale Jan 18, 2018
034d09b
Change GitHubPaneViewModel to use ITeamExplorerContext
jcansdale Jan 18, 2018
03a41e7
Remove PullRequestDetailViewModel's dependency on IVSGitExt
jcansdale Jan 19, 2018
360ba9a
Fix CA error
jcansdale Jan 19, 2018
c8dd2f6
Add some docs for ITeamExplorerContext
jcansdale Jan 19, 2018
b119585
Don't log when unit testing
jcansdale Jan 19, 2018
834f39d
Log exceptions in Refresh event
jcansdale Jan 19, 2018
18eba88
Use IGitHubServiceProvider instead of IServiceProvider
jcansdale Jan 19, 2018
be1b5b4
Use Splat.ModeDetector.InUnitTestRunner to detect runner
jcansdale Jan 19, 2018
65d0be6
Use Rx for property change subscriptions
jcansdale Jan 19, 2018
04452ef
Format xmldoc comments
jcansdale Jan 19, 2018
a35dca7
Use Rx to marshal onto main thread
jcansdale Jan 19, 2018
c809b66
Add remarks about non-UI thread
jcansdale Jan 19, 2018
fd5b45f
Merge branch 'master' into fixes/1408-change-branch-event-squashed
jcansdale Jan 19, 2018
05a6e5b
Change xUnit test to NUnit test
jcansdale Jan 19, 2018
d8328e0
Merge branch 'master' into fixes/1408-change-branch-event-squashed
jcansdale Jan 19, 2018
06daff1
Move TeamExplorerContext tests into UnitTests project
jcansdale Jan 19, 2018
9706afd
Fire StatusChanged even when BranchName/HeadSha hasn't changed
jcansdale Jan 22, 2018
384519a
Use TeamExplorerContext.StatusChanged to refresh PR detail view
jcansdale Jan 22, 2018
23af5ec
Fire StatusChanged when tracked branch changes
jcansdale Jan 23, 2018
ed4bf01
Code cleanup and comments
jcansdale Jan 23, 2018
a2c3fe1
Simplify testing by using a nested interface
jcansdale Jan 23, 2018
850f089
Add test for when only TrackedSha changes
jcansdale Jan 23, 2018
bfba186
Use class instead of out params
jcansdale Jan 23, 2018
067e320
Add xmldoc comments for TeamExplorerContext
jcansdale Jan 23, 2018
218ba5a
Rename RefreshLater to RefreshIfActive
jcansdale Jan 23, 2018
fd3077e
Consistently use CreateTeamExplorerContext
jcansdale Jan 23, 2018
67a7c16
Fire property changed event from ActiveRepository setter
jcansdale Jan 23, 2018
687db88
Encapsulate reflection code in GitExt and RepositoryInfo
jcansdale Jan 23, 2018
f2678a1
Make teamExplorerContext default to ValidGitHubRepo
jcansdale Jan 23, 2018
5499f25
Use IVSGitExt rather than access IGitExt directly
jcansdale Jan 24, 2018
1bb4be9
Don't manually disable logging when unit testing
jcansdale Jan 25, 2018
3f53469
Clean up and comment
jcansdale Jan 25, 2018
375bdd6
Change VSGitExt to lazy-initialize using UIContextChanged
jcansdale Jan 25, 2018
572760b
Remove unused logger
jcansdale Jan 25, 2018
24d45b7
Add unit tests for VSGitExt
jcansdale Jan 25, 2018
34109f2
Remove UIContext watching responsibilities from TeamExplorerServiceHo…
jcansdale Jan 26, 2018
d69d358
Export single IVSUIContextFactory implementation in GH.TF.14
jcansdale Jan 26, 2018
470991d
Keep CA happy
jcansdale Jan 26, 2018
a801953
Remove IVSUIContextChangedEventArgs interface
jcansdale Jan 26, 2018
79ea850
Make IVSUIContextFactory filename consistent with service
jcansdale Jan 26, 2018
2ccabb1
Add logging for VSGitExt
jcansdale Jan 26, 2018
7e9405a
Update and add xmldoc comments
jcansdale Jan 26, 2018
77bd3b2
Ensure ActiveRepositories is read using ThreadPoolThread
jcansdale Jan 26, 2018
dd39202
Update comments on how we're using UIContext
jcansdale Jan 26, 2018
e2c51e8
Cleanup usings
jcansdale Jan 26, 2018
edf9ff8
Don't read ActiveRepositories on UIContext thread
jcansdale Jan 29, 2018
a65027c
Add repo model factory and tests for repository creation
jcansdale Jan 29, 2018
7ea9d81
Ensure repo changed event fires even when exception
jcansdale Jan 29, 2018
a41dbc1
Use IReadOnlyList and keep IVSGitExt consistent with IGitExt
jcansdale Jan 29, 2018
b420d4b
Merge branch 'master' into fixes/1408-change-branch-event-squashed
jcansdale Jan 29, 2018
6a494e3
Fix merge error
jcansdale Jan 29, 2018
a466295
Only fire property change event if property has changed
jcansdale Jan 29, 2018
01c81b2
Clean up redundant usings
jcansdale Jan 29, 2018
7f8011e
Update test to reflect no duplicate change events
jcansdale Jan 29, 2018
4995603
Merge branch 'master' into fixes/1408-change-branch-event-squashed
grokys Jan 29, 2018
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
4 changes: 4 additions & 0 deletions src/GitHub.App/GitHub.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
</PropertyGroup>
<Import Project="$(SolutionDir)\src\common\signing.props" />
<ItemGroup>
<Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="LibGit2Sharp, Version=0.23.1.0, Culture=neutral, PublicKeyToken=7cbde695407f0333, processorArchitecture=MSIL">
<HintPath>..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll</HintPath>
<Private>True</Private>
Expand Down Expand Up @@ -139,6 +142,7 @@
<Compile Include="Models\PullRequestDetailArgument.cs" />
<Compile Include="Services\EnterpriseCapabilitiesService.cs" />
<Compile Include="Services\GlobalConnection.cs" />
<Compile Include="Services\TeamExplorerContext.cs" />
<Compile Include="Services\OAuthCallbackListener.cs" />
<Compile Include="ViewModels\Dialog\GistCreationViewModel.cs" />
<Compile Include="ViewModels\Dialog\GitHubDialogWindowViewModel.cs" />
Expand Down
3 changes: 1 addition & 2 deletions src/GitHub.App/Services/PullRequestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using System.Reactive;
using System.Collections.Generic;
using LibGit2Sharp;
using System.Diagnostics;
using GitHub.Logging;

namespace GitHub.Services
Expand Down Expand Up @@ -287,7 +286,7 @@ public bool IsPullRequestFromRepository(ILocalRepositoryModel repository, IPullR
{
if (pullRequest.Head?.RepositoryCloneUrl != null)
{
return repository.CloneUrl.ToRepositoryUrl() == pullRequest.Head.RepositoryCloneUrl.ToRepositoryUrl();
return repository.CloneUrl?.ToRepositoryUrl() == pullRequest.Head.RepositoryCloneUrl.ToRepositoryUrl();
}

return false;
Expand Down
141 changes: 141 additions & 0 deletions src/GitHub.App/Services/TeamExplorerContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Linq;
using System.ComponentModel;
using System.ComponentModel.Composition;
using GitHub.Models;
using GitHub.Logging;
using Serilog;
using EnvDTE;

namespace GitHub.Services
{
/// <summary>
/// This implementation listenes to IGitExt for ActiveRepositories property change events and fires
/// <see cref="PropertyChanged"/> and <see cref="StatusChanged"/> events when appropriate.
/// </summary>
/// <remarks>
/// A <see cref="PropertyChanged"/> is fired when a solution is opened in a new repository (or not in a repository).
/// A <see cref="StatusChanged"/> event is only fired when the current branch, head SHA or tracked SHA changes (not
/// on every IGitExt property change event). <see cref="ActiveRepository"/> contains the active repository or null
/// if a solution is opened that isn't in a repository. No events are fired when the same solution is unloaded then
/// reloaded (e.g. when a .sln file is touched).
/// </remarks>
[Export(typeof(ITeamExplorerContext))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class TeamExplorerContext : ITeamExplorerContext
{
static ILogger log = LogManager.ForContext<TeamExplorerContext>();

readonly DTE dte;
readonly IVSGitExt gitExt;

string solutionPath;
string repositoryPath;
string branchName;
string headSha;
string trackedSha;

ILocalRepositoryModel repositoryModel;

[ImportingConstructor]
public TeamExplorerContext(IGitHubServiceProvider serviceProvider, IVSGitExt gitExt)
: this(gitExt, serviceProvider)
{
}

public TeamExplorerContext(IVSGitExt gitExt, IGitHubServiceProvider serviceProvider)
{
this.gitExt = gitExt;

// This is a standard service which should always be available.
dte = serviceProvider.GetService<DTE>();

Refresh();
gitExt.ActiveRepositoriesChanged += Refresh;
}

void Refresh()
{
try
{
var repo = gitExt.ActiveRepositories?.FirstOrDefault();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be done on a background thread, see TeamExplorerServiceHolder for how it does this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed it so that ActiveRepositories is initialized by VSGitExt on a thread pool thread.

var newSolutionPath = dte.Solution?.FullName;

if (repo == null && newSolutionPath == solutionPath)
{
// Ignore when ActiveRepositories is empty and solution hasn't changed.
// https://github.com/github/VisualStudio/issues/1421
log.Information("Ignoring no ActiveRepository when solution hasn't changed");
}
else
{
var newRepositoryPath = repo?.LocalPath;
var newBranchName = repo?.CurrentBranch?.Name;
var newHeadSha = repo?.CurrentBranch?.Sha;
var newTrackedSha = repo?.CurrentBranch?.TrackedSha;

if (newRepositoryPath != repositoryPath)
{
log.Information("Fire PropertyChanged event for ActiveRepository");
ActiveRepository = repo;
}
else if (newBranchName != branchName)
{
log.Information("Fire StatusChanged event when BranchName changes for ActiveRepository");
StatusChanged?.Invoke(this, EventArgs.Empty);
}
else if (newHeadSha != headSha)
{
log.Information("Fire StatusChanged event when HeadSha changes for ActiveRepository");
StatusChanged?.Invoke(this, EventArgs.Empty);
}
else if (newTrackedSha != trackedSha)
{
log.Information("Fire StatusChanged event when TrackedSha changes for ActiveRepository");
StatusChanged?.Invoke(this, EventArgs.Empty);
}

repositoryPath = newRepositoryPath;
branchName = newBranchName;
headSha = newHeadSha;
solutionPath = newSolutionPath;
trackedSha = newTrackedSha;
}
}
catch (Exception e)
{
log.Error(e, "Refreshing active repository");
}
}

/// <summary>
/// The active repository or null if not in a repository.
/// </summary>
public ILocalRepositoryModel ActiveRepository
{
get
{
return repositoryModel;
}

private set
{
if (value != repositoryModel)
{
repositoryModel = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ActiveRepository)));
}
}
}

/// <summary>
/// Fired when a solution is opened in a new repository (or that isn't in a repository).
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// Fired when the current branch, head SHA or tracked SHA changes.
/// </summary>
public event EventHandler StatusChanged;
}
}
28 changes: 11 additions & 17 deletions src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
readonly IViewViewModelFactory viewModelFactory;
readonly ISimpleApiClientFactory apiClientFactory;
readonly IConnectionManager connectionManager;
readonly ITeamExplorerServiceHolder teServiceHolder;
readonly ITeamExplorerContext teamExplorerContext;
readonly IVisualStudioBrowser browser;
readonly IUsageTracker usageTracker;
readonly INavigationViewModel navigator;
Expand All @@ -46,7 +46,6 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
readonly ReactiveCommand<Unit> refresh;
readonly ReactiveCommand<Unit> showPullRequests;
readonly ReactiveCommand<object> openInBrowser;
bool initialized;
IViewModel content;
ILocalRepositoryModel localRepository;
string searchQuery;
Expand All @@ -56,7 +55,7 @@ public GitHubPaneViewModel(
IViewViewModelFactory viewModelFactory,
ISimpleApiClientFactory apiClientFactory,
IConnectionManager connectionManager,
ITeamExplorerServiceHolder teServiceHolder,
ITeamExplorerContext teamExplorerContext,
IVisualStudioBrowser browser,
IUsageTracker usageTracker,
INavigationViewModel navigator,
Expand All @@ -67,7 +66,7 @@ public GitHubPaneViewModel(
Guard.ArgumentNotNull(viewModelFactory, nameof(viewModelFactory));
Guard.ArgumentNotNull(apiClientFactory, nameof(apiClientFactory));
Guard.ArgumentNotNull(connectionManager, nameof(connectionManager));
Guard.ArgumentNotNull(teServiceHolder, nameof(teServiceHolder));
Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext));
Guard.ArgumentNotNull(browser, nameof(browser));
Guard.ArgumentNotNull(usageTracker, nameof(usageTracker));
Guard.ArgumentNotNull(navigator, nameof(navigator));
Expand All @@ -78,7 +77,7 @@ public GitHubPaneViewModel(
this.viewModelFactory = viewModelFactory;
this.apiClientFactory = apiClientFactory;
this.connectionManager = connectionManager;
this.teServiceHolder = teServiceHolder;
this.teamExplorerContext = teamExplorerContext;
this.browser = browser;
this.usageTracker = usageTracker;
this.navigator = navigator;
Expand Down Expand Up @@ -199,8 +198,12 @@ public void Dispose()
/// <inheritdoc/>
public async Task InitializeAsync(IServiceProvider paneServiceProvider)
{
await UpdateContent(teServiceHolder.ActiveRepo);
teServiceHolder.Subscribe(this, x => UpdateContentIfRepositoryChanged(x).Forget());
await UpdateContent(teamExplorerContext.ActiveRepository);
teamExplorerContext.WhenAnyValue(x => x.ActiveRepository)
.Skip(1)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => UpdateContent(x).Forget());

connectionManager.Connections.CollectionChanged += (_, __) => UpdateContent(LocalRepository).Forget();

BindNavigatorCommand(paneServiceProvider, PkgCmdIDList.pullRequestCommand, showPullRequests);
Expand Down Expand Up @@ -274,7 +277,7 @@ public Task ShowCreatePullRequest()
/// <inheritdoc/>
public Task ShowPullRequests()
{
return NavigateTo<IPullRequestListViewModel>(x => x.InitializeAsync(LocalRepository, Connection));
return NavigateTo<IPullRequestListViewModel>(x => x.InitializeAsync(LocalRepository, Connection));
}

/// <inheritdoc/>
Expand Down Expand Up @@ -344,7 +347,6 @@ async Task NavigateTo<TViewModel>(Func<TViewModel, Task> initialize, Func<TViewM

async Task UpdateContent(ILocalRepositoryModel repository)
{
initialized = true;
LocalRepository = repository;
Connection = null;
Content = null;
Expand Down Expand Up @@ -388,14 +390,6 @@ async Task UpdateContent(ILocalRepositoryModel repository)
}
}

async Task UpdateContentIfRepositoryChanged(ILocalRepositoryModel repository)
{
if (!initialized || !Equals(repository, LocalRepository))
{
await UpdateContent(repository);
}
}

static async Task<bool> IsValidRepository(ISimpleApiClient client)
{
try
Expand Down
Loading