Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit ee77977

Browse files
authored
Merge pull request #1499 from github/fixes/1493-wrong-repo
Ensure correct ordering of VSGitExt.ActiveRepositoriesChanged.
2 parents 18b77ef + 976a47e commit ee77977

File tree

4 files changed

+83
-5
lines changed

4 files changed

+83
-5
lines changed

src/GitHub.App/Services/TeamExplorerContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void Refresh()
7676

7777
if (newRepositoryPath != repositoryPath)
7878
{
79-
log.Debug("Fire PropertyChanged event for ActiveRepository");
79+
log.Debug("ActiveRepository changed to {CloneUrl} @ {Path}", repo?.CloneUrl, newRepositoryPath);
8080
ActiveRepository = repo;
8181
}
8282
else if (newBranchName != branchName)

src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
using GitHub.Extensions;
1212
using GitHub.Factories;
1313
using GitHub.Info;
14+
using GitHub.Logging;
1415
using GitHub.Models;
1516
using GitHub.Primitives;
1617
using GitHub.Services;
1718
using GitHub.VisualStudio;
1819
using ReactiveUI;
20+
using Serilog;
1921
using OleMenuCommand = Microsoft.VisualStudio.Shell.OleMenuCommand;
2022

2123
namespace GitHub.ViewModels.GitHubPane
@@ -27,6 +29,7 @@ namespace GitHub.ViewModels.GitHubPane
2729
[PartCreationPolicy(CreationPolicy.NonShared)]
2830
public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, IDisposable
2931
{
32+
static readonly ILogger log = LogManager.ForContext<GitHubPaneViewModel>();
3033
static readonly Regex pullUri = CreateRoute("/:owner/:repo/pull/:number");
3134

3235
readonly IViewViewModelFactory viewModelFactory;
@@ -360,18 +363,22 @@ async Task NavigateTo<TViewModel>(Func<TViewModel, Task> initialize, Func<TViewM
360363

361364
async Task UpdateContent(ILocalRepositoryModel repository)
362365
{
366+
log.Debug("UpdateContent called with {CloneUrl}", repository?.CloneUrl);
367+
363368
LocalRepository = repository;
364369
Connection = null;
365370
Content = null;
366371
navigator.Clear();
367372

368373
if (repository == null)
369374
{
375+
log.Debug("Not a git repository: {CloneUrl}", repository?.CloneUrl);
370376
Content = notAGitRepository;
371377
return;
372378
}
373379
else if (string.IsNullOrWhiteSpace(repository.CloneUrl))
374380
{
381+
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
375382
Content = notAGitHubRepository;
376383
return;
377384
}
@@ -389,16 +396,19 @@ async Task UpdateContent(ILocalRepositoryModel repository)
389396

390397
if (Connection?.IsLoggedIn == true)
391398
{
399+
log.Debug("Found a GitHub repository: {CloneUrl}", repository?.CloneUrl);
392400
Content = navigator;
393401
await ShowDefaultPage();
394402
}
395403
else
396404
{
405+
log.Debug("Found a a GitHub repository but not logged in: {CloneUrl}", repository?.CloneUrl);
397406
Content = loggedOut;
398407
}
399408
}
400409
else
401410
{
411+
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
402412
Content = notAGitHubRepository;
403413
}
404414
}

src/GitHub.TeamFoundation.14/Services/VSGitExt.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class VSGitExt : IVSGitExt
2424
readonly IGitHubServiceProvider serviceProvider;
2525
readonly IVSUIContext context;
2626
readonly ILocalRepositoryModelFactory repositoryFactory;
27+
readonly object refreshLock = new object();
2728

2829
IGitExt gitService;
2930
IReadOnlyList<ILocalRepositoryModel> activeRepositories;
@@ -98,7 +99,15 @@ void RefreshActiveRepositories()
9899
{
99100
try
100101
{
101-
ActiveRepositories = gitService?.ActiveRepositories.Select(x => repositoryFactory.Create(x.RepositoryPath)).ToList();
102+
lock (refreshLock)
103+
{
104+
log.Debug(
105+
"IGitExt.ActiveRepositories (#{Id}) returned {Repositories}",
106+
gitService.GetHashCode(),
107+
gitService?.ActiveRepositories.Select(x => x.RepositoryPath));
108+
109+
ActiveRepositories = gitService?.ActiveRepositories.Select(x => repositoryFactory.Create(x.RepositoryPath)).ToList();
110+
}
102111
}
103112
catch (Exception e)
104113
{
@@ -118,6 +127,7 @@ private set
118127
{
119128
if (value != activeRepositories)
120129
{
130+
log.Debug("ActiveRepositories changed to {Repositories}", value?.Select(x => x.CloneUrl));
121131
activeRepositories = value;
122132
ActiveRepositoriesChanged?.Invoke();
123133
}

test/UnitTests/GitHub.TeamFoundation/VSGitExtTests.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
using NUnit.Framework;
1010
using NSubstitute;
1111
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
12+
using System.Threading.Tasks;
13+
using System.Linq;
14+
using GitHub.TeamFoundation.Services;
1215

1316
public class VSGitExtTests
1417
{
@@ -171,9 +174,26 @@ public void ExceptionRefreshingRepositories_ReturnsEmptyList()
171174
repoFactory.Received(1).Create(repoPath);
172175
Assert.That(activeRepositories.Count, Is.EqualTo(0));
173176
}
177+
178+
[Test]
179+
public async Task ActiveRepositoriesChangedOrderingShouldBeCorrectAcrossThreads()
180+
{
181+
var gitExt = new MockGitExt();
182+
var repoFactory = new MockRepositoryFactory();
183+
var target = CreateVSGitExt(gitExt: gitExt, repoFactory: repoFactory);
184+
var activeRepositories1 = CreateActiveRepositories("repo1");
185+
var activeRepositories2 = CreateActiveRepositories("repo2");
186+
var task1 = Task.Run(() => gitExt.ActiveRepositories = activeRepositories1);
187+
await Task.Delay(1);
188+
var task2 = Task.Run(() => gitExt.ActiveRepositories = activeRepositories2);
189+
190+
await Task.WhenAll(task1, task2);
191+
192+
Assert.That(target.ActiveRepositories.Single().LocalPath, Is.EqualTo("repo2"));
193+
}
174194
}
175195

176-
static IReadOnlyList<IGitRepositoryInfo> CreateActiveRepositories(IList<string> repositoryPaths)
196+
static IReadOnlyList<IGitRepositoryInfo> CreateActiveRepositories(params string[] repositoryPaths)
177197
{
178198
var repositories = new List<IGitRepositoryInfo>();
179199
foreach (var repositoryPath in repositoryPaths)
@@ -203,9 +223,8 @@ static VSGitExt CreateVSGitExt(IVSUIContext context = null, IGitExt gitExt = nul
203223
return vsGitExt;
204224
}
205225

206-
static IGitExt CreateGitExt(IList<string> repositoryPaths = null)
226+
static IGitExt CreateGitExt(params string[] repositoryPaths)
207227
{
208-
repositoryPaths = repositoryPaths ?? Array.Empty<string>();
209228
var gitExt = Substitute.For<IGitExt>();
210229
var repoList = CreateActiveRepositories(repositoryPaths);
211230
gitExt.ActiveRepositories.Returns(repoList);
@@ -218,4 +237,43 @@ static IVSUIContext CreateVSUIContext(bool isActive)
218237
context.IsActive.Returns(isActive);
219238
return context;
220239
}
240+
241+
class MockGitExt : IGitExt
242+
{
243+
IReadOnlyList<IGitRepositoryInfo> activeRepositories = new IGitRepositoryInfo[0];
244+
245+
public IReadOnlyList<IGitRepositoryInfo> ActiveRepositories
246+
{
247+
get { return activeRepositories; }
248+
set
249+
{
250+
if (activeRepositories != value)
251+
{
252+
activeRepositories = value;
253+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ActiveRepositories)));
254+
}
255+
}
256+
}
257+
258+
public event PropertyChangedEventHandler PropertyChanged;
259+
}
260+
261+
class MockRepositoryFactory : ILocalRepositoryModelFactory
262+
{
263+
public ILocalRepositoryModel Create(string localPath)
264+
{
265+
var result = Substitute.For<ILocalRepositoryModel>();
266+
result.LocalPath.Returns(localPath);
267+
268+
if (localPath == "repo1")
269+
{
270+
// Trying to force #1493 here by introducing a a delay on the first
271+
// ActiveRepositories changed notification so that the second completes
272+
// first.
273+
Thread.Sleep(10);
274+
}
275+
276+
return result;
277+
}
278+
}
221279
}

0 commit comments

Comments
 (0)