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
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: 3 additions & 1 deletion src/GitHub.App/SampleData/SampleViewModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ public long PrivateReposInPlan
}

[ExcludeFromCodeCoverage]
public class RepositoryModelDesigner : IRepositoryModel
public class RepositoryModelDesigner : NotificationAwareObject, IRepositoryModel
{
public RepositoryModelDesigner() : this("repo")
{
Expand Down Expand Up @@ -416,6 +416,8 @@ public void SetIcon(bool isPrivate, bool isFork)
public Octicon Icon { get; set; }

public IAccount Owner { get; set; }

public void Refresh() { }
}

public class RepositoryCloneViewModelDesigner : BaseViewModelDesigner, IRepositoryCloneViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ public static ISimpleRepositoryModel ToModel(this IGitRepositoryInfo repo)
{
if (repo == null)
return null;
var uri = repo.GetUriFromRepository();
var name = uri?.NameWithOwner;
return name != null ? new SimpleRepositoryModel(name, uri, repo.RepositoryPath) : null;
return SimpleRepositoryModel.Create(repo.RepositoryPath);
}

public static bool HasCommits(this ISimpleRepositoryModel repository)
Expand Down
9 changes: 8 additions & 1 deletion src/GitHub.Exports/Models/ISimpleRepositoryModel.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
using GitHub.Primitives;
using GitHub.UI;
using System.ComponentModel;

namespace GitHub.Models
{
public interface ISimpleRepositoryModel
public interface ISimpleRepositoryModel : INotifyPropertyChanged
{
string Name { get; }
UriString CloneUrl { get; }
string LocalPath { get; }
Octicon Icon { get; }

void SetIcon(bool isPrivate, bool isFork);


/// <summary>
/// Updates the url information based on the local path
/// </summary>
void Refresh();
}
}
50 changes: 47 additions & 3 deletions src/GitHub.Exports/Models/SimpleRepositoryModel.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using GitHub.Primitives;
using GitHub.Extensions;
using GitHub.Primitives;
using GitHub.UI;
using GitHub.VisualStudio;
using GitHub.VisualStudio.Helpers;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;

namespace GitHub.Models
{
Expand All @@ -18,6 +21,32 @@ public SimpleRepositoryModel(string name, UriString cloneUrl, string localPath =
Icon = Octicon.repo;
}

public SimpleRepositoryModel(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
var dir = new DirectoryInfo(path);
if (!dir.Exists)
throw new ArgumentException("Path does not exist", nameof(path));
var uri = GitHelpers.GetRepoFromPath(path)?.GetUri();
var name = uri?.NameWithOwner;
if (name == null)
name = dir.Name;
Name = name;
LocalPath = path;
CloneUrl = uri;
Icon = Octicon.repo;
}

public static ISimpleRepositoryModel Create(string path)
{
if (path == null)
return null;
if (!Directory.Exists(path))
return null;
return new SimpleRepositoryModel(path);
}

public void SetIcon(bool isPrivate, bool isFork)
{
Icon = isPrivate
Expand All @@ -27,15 +56,30 @@ public void SetIcon(bool isPrivate, bool isFork)
: Octicon.repo;
}

public void Refresh()
{
if (LocalPath == null)
return;
var uri = GitHelpers.GetRepoFromPath(LocalPath)?.GetUri();
if (CloneUrl != uri)
CloneUrl = uri;
}

public string Name { get; private set; }
public UriString CloneUrl { get; private set; }
UriString cloneUrl;
public UriString CloneUrl { get { return cloneUrl; } set { cloneUrl = value; this.RaisePropertyChange(); } }
public string LocalPath { get; private set; }
Octicon icon;
public Octicon Icon { get { return icon; } set { icon = value; this.RaisePropertyChange(); } }

/// <summary>
/// Note: We don't consider CloneUrl a part of the hash code because it can change during the lifetime
/// of a repository. Equals takes care of any hash collisions because of this
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return (Name?.GetHashCode() ?? 0) ^ (CloneUrl?.GetHashCode() ?? 0) ^ (LocalPath?.TrimEnd('\\').ToUpperInvariant().GetHashCode() ?? 0);
return (Name?.GetHashCode() ?? 0) ^ (LocalPath?.TrimEnd('\\').ToUpperInvariant().GetHashCode() ?? 0);
}

public override bool Equals(object obj)
Expand Down
12 changes: 9 additions & 3 deletions src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using GitHub.Primitives;
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
using GitHub.Models;

namespace GitHub.Services
{
Expand Down Expand Up @@ -28,25 +29,30 @@ public interface ITeamExplorerServiceHolder
/// <summary>
/// A IGitRepositoryInfo representing the currently active repository
/// </summary>
IGitRepositoryInfo ActiveRepo { get; }
ISimpleRepositoryModel ActiveRepo { get; }
/// <summary>
/// Subscribe to be notified when the active repository is set and Notify is called.
/// </summary>
/// <param name="who">The instance that is interested in being called (or a unique key/object for that instance)</param>
/// <param name="handler">The handler to call when ActiveRepo is set</param>
void Subscribe(object who, Action<IGitRepositoryInfo> handler);
void Subscribe(object who, Action<ISimpleRepositoryModel> handler);
/// <summary>
/// Unsubscribe from notifications
/// </summary>
/// <param name="who">The instance/key that previously subscribed to notifications</param>
void Unsubscribe(object who);

IGitAwareItem HomeSection { get; }

/// <summary>
/// Refresh the information on the active repo (in case of remote url changes or other such things)
/// </summary>
void Refresh();
}

public interface IGitAwareItem
{
IGitRepositoryInfo ActiveRepo { get; }
ISimpleRepositoryModel ActiveRepo { get; }

/// <summary>
/// Represents the web URL of the repository on GitHub.com, even if the origin is an SSH address.
Expand Down
7 changes: 4 additions & 3 deletions src/GitHub.VisualStudio/Base/TeamExplorerGitRepoInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GitHub.Primitives;
using GitHub.Models;
using GitHub.Primitives;
using GitHub.Services;
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
using NullGuard;
Expand All @@ -12,9 +13,9 @@ public TeamExplorerGitRepoInfo()
ActiveRepo = null;
}

IGitRepositoryInfo activeRepo;
ISimpleRepositoryModel activeRepo;
[AllowNull]
public IGitRepositoryInfo ActiveRepo
public ISimpleRepositoryModel ActiveRepo
{
[return: AllowNull]
get { return activeRepo; }
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.VisualStudio/Base/TeamExplorerItemBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected virtual void RepoChanged()
var repo = ActiveRepo;
if (repo != null)
{
var uri = repo.GetUriFromRepository();
var uri = repo.CloneUrl;
if (uri?.RepositoryName != null)
{
ActiveRepoUri = uri;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.TeamFoundation.Git.Extensibility;
using NullGuard;
using GitHub.Models;

namespace GitHub.VisualStudio.Base
{
Expand Down Expand Up @@ -56,7 +57,7 @@ void OnThemeChanged()
}
}

void UpdateRepo(IGitRepositoryInfo repo)
void UpdateRepo(ISimpleRepositoryModel repo)
{
ActiveRepo = repo;
RepoChanged();
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.VisualStudio/Base/TeamExplorerSectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public virtual void SaveContext(object sender, SectionSaveContextEventArgs e)

void SubscribeToRepoChanges()
{
holder.Subscribe(this, (IGitRepositoryInfo repo) =>
holder.Subscribe(this, (ISimpleRepositoryModel repo) =>
{
ActiveRepo = repo;
RepoChanged();
Expand Down
61 changes: 44 additions & 17 deletions src/GitHub.VisualStudio/Base/TeamExplorerServiceHolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
using System.Linq;
using System.Threading;
using System.Globalization;
using GitHub.Models;

namespace GitHub.VisualStudio.Base
{
[Export(typeof(ITeamExplorerServiceHolder))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class TeamExplorerServiceHolder : ITeamExplorerServiceHolder
{
readonly Dictionary<object, Action<IGitRepositoryInfo>> activeRepoHandlers = new Dictionary<object, Action<IGitRepositoryInfo>>();
IGitRepositoryInfo activeRepo;
readonly Dictionary<object, Action<ISimpleRepositoryModel>> activeRepoHandlers = new Dictionary<object, Action<ISimpleRepositoryModel>>();
ISimpleRepositoryModel activeRepo;
bool activeRepoNotified = false;

IServiceProvider serviceProvider;
Expand Down Expand Up @@ -48,35 +49,50 @@ public IServiceProvider ServiceProvider
if (serviceProvider == null)
return;
GitUIContext = GitUIContext ?? UIContext.FromUIContextGuid(new Guid("11B8E6D7-C08B-4385-B321-321078CDD1F8"));
UIContextChanged(GitUIContext?.IsActive ?? false);
UIContextChanged(GitUIContext?.IsActive ?? false, false);
}
}

[AllowNull]
public IGitRepositoryInfo ActiveRepo
public ISimpleRepositoryModel ActiveRepo
{
[return: AllowNull] get { return activeRepo; }
private set
{
if (activeRepo.Compare(value))
if (activeRepo == value)
return;
if (activeRepo != null)
activeRepo.PropertyChanged -= ActiveRepoPropertyChanged;
activeRepo = value;
if (activeRepo != null)
activeRepo.PropertyChanged += ActiveRepoPropertyChanged;
NotifyActiveRepo();
}
}

public void Subscribe(object who, Action<IGitRepositoryInfo> handler)
public void Subscribe(object who, Action<ISimpleRepositoryModel> handler)
{
bool notificationsExist;
ISimpleRepositoryModel repo;
lock(activeRepoHandlers)
{
var repo = ActiveRepo;
repo = ActiveRepo;
notificationsExist = activeRepoNotified;
if (!activeRepoHandlers.ContainsKey(who))
activeRepoHandlers.Add(who, handler);
else
activeRepoHandlers[who] = handler;
if (activeRepoNotified)
handler(repo);
}

// the repo url might have changed and we don't get notifications
// for that, so this is a good place to refresh it in case that happened
repo?.Refresh();

// if the active repo hasn't changed and there's notifications queued up,
// notify the subscriber. If the repo has changed, the set above will trigger
// notifications so we don't have to do it here.
if (repo == ActiveRepo && notificationsExist)
handler(repo);
}

public void Unsubscribe(object who)
Expand All @@ -100,6 +116,12 @@ public void ClearServiceProvider(IServiceProvider provider)
ServiceProvider = null;
}

public void Refresh()
{
GitUIContext = GitUIContext ?? UIContext.FromUIContextGuid(new Guid("11B8E6D7-C08B-4385-B321-321078CDD1F8"));
UIContextChanged(GitUIContext?.IsActive ?? false, true);
}

void NotifyActiveRepo()
{
lock (activeRepoHandlers)
Expand All @@ -113,10 +135,10 @@ void NotifyActiveRepo()
void UIContextChanged(object sender, UIContextChangedEventArgs e)
{
ActiveRepo = null;
UIContextChanged(e.Activated);
UIContextChanged(e.Activated, false);
}

async void UIContextChanged(bool active)
async void UIContextChanged(bool active, bool refresh)
{
Debug.Assert(ServiceProvider != null, "UIContextChanged called before service provider is set");
if (ServiceProvider == null)
Expand All @@ -125,7 +147,7 @@ async void UIContextChanged(bool active)
if (active)
{
GitService = GitService ?? ServiceProvider.GetService<IGitExt>();
if (ActiveRepo == null)
if (ActiveRepo == null || refresh)
ActiveRepo = await System.Threading.Tasks.Task.Run(() =>
{
var repos = GitService?.ActiveRepositories;
Expand All @@ -140,7 +162,7 @@ async void UIContextChanged(bool active)
if (repos == null)
VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "Error 2002: ActiveRepositories is null. GitService: '{0}'", GitService));
}
return repos?.FirstOrDefault();
return repos?.FirstOrDefault()?.ToModel();
});
}
else
Expand All @@ -156,11 +178,16 @@ void CheckAndUpdate(object sender, System.ComponentModel.PropertyChangedEventArg
if (service == null)
return;

var repo = service.ActiveRepositories.FirstOrDefault();
// this comparison is safe, the extension method supports null instances
if (!repo.Compare(ActiveRepo))
var repo = service.ActiveRepositories.FirstOrDefault()?.ToModel();
if (repo != ActiveRepo)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

FWIW, this comparison is the actual fix to #48.

// so annoying that this is on the wrong thread
syncContext.Post(r => ActiveRepo = r as IGitRepositoryInfo, repo);
syncContext.Post(r => ActiveRepo = r as ISimpleRepositoryModel, repo);
}

void ActiveRepoPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "CloneUrl")
ActiveRepo = sender as ISimpleRepositoryModel;
}

public IGitAwareItem HomeSection
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.VisualStudio/GitHubPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using GitHub.UI;
using GitHub.Services;
using GitHub.Models;
using GitHub.VisualStudio.UI;
using GitHub.Extensions;

namespace GitHub.VisualStudio
{
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.VisualStudio/Services/ConnectionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public void RequestLogout(IConnection connection)
public void RefreshRepositories()
{
var list = vsServices.GetKnownRepositories();
list.GroupBy(r => Connections.FirstOrDefault(c => c.HostAddress.Equals(HostAddress.Create(r.CloneUrl))))
list.GroupBy(r => Connections.FirstOrDefault(c => r.CloneUrl != null && c.HostAddress.Equals(HostAddress.Create(r.CloneUrl))))
.Where(g => g.Key != null)
.ForEach(g =>
{
Expand Down
Loading