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
42 commits
Select commit Hold shift + click to select a range
482d710
Add VsTippingService for callout notifications
jcansdale Oct 18, 2018
e25a915
Example: show tip when user changes GitHub repo
jcansdale Oct 18, 2018
053b6ec
Show callout when repository has no origin remote
jcansdale Nov 9, 2018
10d2f24
Rename IVsTippingService to ITippingService
jcansdale Nov 9, 2018
496cddd
Add view for when there is no origin remote
jcansdale Nov 9, 2018
40273b7
Move HasNoRemoteOrigin logic to GitService
jcansdale Nov 9, 2018
da21ea7
Rename NoOriginRemote to NoRemoteOrigin
jcansdale Nov 9, 2018
1fe4009
Split GitService unit and integration tests
jcansdale Nov 12, 2018
023f693
Add test for when there's no repository
jcansdale Nov 12, 2018
835ebb1
Fix CreateLocalRepositoryModel when no repository
jcansdale Nov 12, 2018
1d9563e
Add HasNoRemoteOrigin tests
jcansdale Nov 12, 2018
8c8ddca
Add no directory test
jcansdale Nov 12, 2018
5f98e7b
Merge branch 'master' into fixes/1729-warn-when-no-origin
jcansdale Nov 12, 2018
7348ae2
Fix type does not support direct content in XAML
jcansdale Nov 12, 2018
32e4df3
Add resource text for no "origin" message
jcansdale Nov 12, 2018
77a6176
Add TrackedRemoteName to BranchModel
jcansdale Nov 12, 2018
545627b
Fix issue when changing to repo with no CloneUrl
jcansdale Nov 13, 2018
cb7b1e2
Add test project for GitHub.Services.Vssdk
jcansdale Nov 13, 2018
04833b9
Add first test for TippingService
jcansdale Nov 13, 2018
0b7c421
Don't throw if VsTippingService API has changed
jcansdale Nov 13, 2018
600c9de
Check arguments passed to RequestCalloutDisplay
jcansdale Nov 13, 2018
d6f5107
Add xmldoc comments for TippingService
jcansdale Nov 13, 2018
ccebc24
Rename HasNoRemoteOrigin to HasRemotesButNoOrigin
jcansdale Nov 13, 2018
fb1c337
Add button to Edit Remotes
jcansdale Nov 14, 2018
35ab556
Add some explanatory text [wip]
jcansdale Nov 14, 2018
d352010
Merge branch 'master' into fixes/1729-warn-when-no-origin
jcansdale Nov 15, 2018
a4eb753
Remove redundant OnEditRemotesAsync
jcansdale Nov 21, 2018
a7d80b3
Use named args instead of vars
jcansdale Nov 21, 2018
8c4852f
Don't copy parameter types from current method
jcansdale Nov 21, 2018
13ba3f2
Find RequestCalloutDisplay on IVsTippingService
jcansdale Nov 21, 2018
b7fcf23
Name Guids for RepositorySettings / Remotes
jcansdale Nov 21, 2018
f895926
Use Rx to wait for IsLoaded==true
jcansdale Nov 21, 2018
7e51779
Fix null reference exception in Visual Studio 2015
jcansdale Nov 22, 2018
222f635
Don't throw when changing to non-Git solution
jcansdale Nov 22, 2018
5ff428f
Log any exceptions thrown by NoRemoteOriginCallout
jcansdale Nov 22, 2018
01abce3
Locate IVsTippingService using COM Guid
jcansdale Nov 26, 2018
436c3c6
Remove unused code
jcansdale Nov 26, 2018
8fd31a6
Make reflection code more robust
jcansdale Nov 26, 2018
a22bf5e
Merge branch 'master' into fixes/1729-warn-when-no-origin
jcansdale Nov 26, 2018
9f328eb
Merge branch 'master' into fixes/1729-warn-when-no-origin
jcansdale Nov 28, 2018
f755319
Move must have origin text to resource
jcansdale Nov 28, 2018
1e789ed
Merge branch 'master' into fixes/1729-warn-when-no-origin
jcansdale Nov 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
13 changes: 13 additions & 0 deletions GitHubVS.sln
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InstallAndStart", "test\Lau
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.TeamFoundation.16", "src\GitHub.TeamFoundation.16\GitHub.TeamFoundation.16.csproj", "{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.Services.Vssdk.UnitTests", "test\GitHub.Services.Vssdk.UnitTests\GitHub.Services.Vssdk.UnitTests.csproj", "{65542DEE-D3BE-4810-B85A-08E970413A21}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -471,6 +473,16 @@ Global
{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.Release|Any CPU.Build.0 = Release|Any CPU
{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU
{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.Release|Any CPU.Build.0 = Release|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU
{65542DEE-D3BE-4810-B85A-08E970413A21}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -502,6 +514,7 @@ Global
{86C54B27-717F-478C-AC8C-01F1C68A56C5} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9}
{C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9}
{E899B03C-6E8E-4375-AB65-FC925D721D8B} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9}
{65542DEE-D3BE-4810-B85A-08E970413A21} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {556014CF-5B35-4CE5-B3EF-6AB0007001AC}
Expand Down
19 changes: 16 additions & 3 deletions src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
readonly ILoggedOutViewModel loggedOut;
readonly INotAGitHubRepositoryViewModel notAGitHubRepository;
readonly INotAGitRepositoryViewModel notAGitRepository;
readonly INoRemoteOriginViewModel noRemoteOrigin;
readonly ILoginFailedViewModel loginFailed;
readonly SemaphoreSlim navigating = new SemaphoreSlim(1);
readonly ObservableAsPropertyHelper<ContentOverride> contentOverride;
Expand Down Expand Up @@ -72,6 +73,7 @@ public GitHubPaneViewModel(
ILoggedOutViewModel loggedOut,
INotAGitHubRepositoryViewModel notAGitHubRepository,
INotAGitRepositoryViewModel notAGitRepository,
INoRemoteOriginViewModel noRemoteOrigin,
ILoginFailedViewModel loginFailed)
{
Guard.ArgumentNotNull(viewModelFactory, nameof(viewModelFactory));
Expand All @@ -84,6 +86,7 @@ public GitHubPaneViewModel(
Guard.ArgumentNotNull(loggedOut, nameof(loggedOut));
Guard.ArgumentNotNull(notAGitHubRepository, nameof(notAGitHubRepository));
Guard.ArgumentNotNull(notAGitRepository, nameof(notAGitRepository));
Guard.ArgumentNotNull(noRemoteOrigin, nameof(noRemoteOrigin));
Guard.ArgumentNotNull(loginFailed, nameof(loginFailed));

this.viewModelFactory = viewModelFactory;
Expand All @@ -94,6 +97,7 @@ public GitHubPaneViewModel(
this.loggedOut = loggedOut;
this.notAGitHubRepository = notAGitHubRepository;
this.notAGitRepository = notAGitRepository;
this.noRemoteOrigin = noRemoteOrigin;
this.loginFailed = loginFailed;

var contentAndNavigatorContent = Observable.CombineLatest(
Expand Down Expand Up @@ -433,8 +437,17 @@ async Task UpdateContent(LocalRepositoryModel repository)
}
else if (string.IsNullOrWhiteSpace(repository.CloneUrl))
{
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
Content = notAGitHubRepository;
if (repository.HasRemotesButNoOrigin)
{
log.Debug("No origin remote");
Content = noRemoteOrigin;
}
else
{
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
Content = notAGitHubRepository;
}

return;
}

Expand Down Expand Up @@ -490,7 +503,7 @@ async Task UpdateContent(LocalRepositoryModel repository)
Content = loggedOut;
}
}

if (notGitHubRepo)
{
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
Expand Down
28 changes: 28 additions & 0 deletions src/GitHub.App/ViewModels/GitHubPane/NoRemoteOriginViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Reactive;
using System.Threading.Tasks;
using System.ComponentModel.Composition;
using GitHub.Services;
using ReactiveUI;

namespace GitHub.ViewModels.GitHubPane
{
/// <summary>
/// The view model for the "No Origin Remote" view in the GitHub pane.
/// </summary>
[Export(typeof(INoRemoteOriginViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class NoRemoteOriginViewModel : PanePageViewModelBase, INoRemoteOriginViewModel
{
ITeamExplorerServices teamExplorerServices;

[ImportingConstructor]
public NoRemoteOriginViewModel(ITeamExplorerServices teamExplorerServices)
{
this.teamExplorerServices = teamExplorerServices;
EditRemotes = ReactiveCommand.CreateFromTask(teamExplorerServices.ShowRepositorySettingsRemotesAsync);
}

public ReactiveCommand<Unit, Unit> EditRemotes { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Reactive;
using ReactiveUI;

namespace GitHub.ViewModels.GitHubPane
{
/// <summary>
/// Defines the view model for the "No Origin Remote" view in the GitHub pane.
/// </summary>
public interface INoRemoteOriginViewModel : IPanePageViewModel
{
/// <summary>
/// Gets a command that will allow the user to rename remotes.
/// </summary>
ReactiveCommand<Unit, Unit> EditRemotes { get; }
}
}
4 changes: 3 additions & 1 deletion src/GitHub.Exports/Models/BranchModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ namespace GitHub.Models
public class BranchModel : ICopyable<BranchModel>,
IEquatable<BranchModel>, IComparable<BranchModel>
{
public BranchModel(string name, RepositoryModel repo, string sha, bool isTracking, string trackedSha) :
public BranchModel(string name, RepositoryModel repo, string sha, bool isTracking, string trackedSha, string trackedRemoteName) :
this(name, repo)
{
IsTracking = isTracking;
Sha = sha;
TrackedSha = trackedSha;
TrackedRemoteName = trackedRemoteName;
}

public BranchModel(string name, RepositoryModel repo)
Expand All @@ -32,6 +33,7 @@ public BranchModel(string name, RepositoryModel repo)
public string DisplayName { get; set; }
public string Sha { get; private set; }
public string TrackedSha { get; private set; }
public string TrackedRemoteName { get; private set; }

#region Equality things
public void CopyFrom(BranchModel other)
Expand Down
10 changes: 10 additions & 0 deletions src/GitHub.Exports/Models/LocalRepositoryModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel;
using System.Collections.Generic;
using GitHub.Primitives;

namespace GitHub.Models
{
Expand All @@ -22,6 +24,14 @@ public string LocalPath
get; set;
}

/// <summary>
/// True if repository has remotes but none are named "origin".
/// </summary>
public bool HasRemotesButNoOrigin
{
get; set;
}

/// <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
Expand Down
46 changes: 35 additions & 11 deletions src/GitHub.Exports/Services/GitService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using GitHub.UI;
using GitHub.Models;
Expand Down Expand Up @@ -38,18 +39,29 @@ public LocalRepositoryModel CreateLocalRepositoryModel(string localPath)
throw new ArgumentException("Path does not exist", nameof(localPath));
}

var cloneUrl = GetUri(localPath);
var name = cloneUrl?.RepositoryName ?? dir.Name;

var model = new LocalRepositoryModel
using (var repository = GetRepository(localPath))
{
LocalPath = localPath,
CloneUrl = cloneUrl,
Name = name,
Icon = Octicon.repo
};
UriString cloneUrl = null;
bool noOrigin = false;
if (repository != null)
{
cloneUrl = GetUri(repository);
noOrigin = HasRemotesButNoOrigin(repository);
}

return model;
var name = cloneUrl?.RepositoryName ?? dir.Name;

var model = new LocalRepositoryModel
{
LocalPath = localPath,
CloneUrl = cloneUrl,
HasRemotesButNoOrigin = noOrigin,
Name = name,
Icon = Octicon.repo
};

return model;
}
}

public BranchModel GetBranch(LocalRepositoryModel model)
Expand All @@ -68,7 +80,8 @@ public BranchModel GetBranch(LocalRepositoryModel model)
repo: model,
sha: branch.Tip?.Sha,
isTracking: branch.IsTracking,
trackedSha: branch.TrackedBranch?.Tip?.Sha);
trackedSha: branch.TrackedBranch?.Tip?.Sha,
trackedRemoteName: branch.TrackedBranch?.RemoteName);
}
}

Expand Down Expand Up @@ -119,6 +132,17 @@ public IRepository GetRepository(string path)
return repoPath == null ? null : repositoryFacade.NewRepository(repoPath);
}

/// <summary>
/// Find out if repository has remotes but none are called "origin".
/// </summary>
/// <param name="repo">The target repository.</param>
/// <returns>True if repository has remotes but none are called "origin".</returns>
public bool HasRemotesButNoOrigin(IRepository repo)
{
var remotes = repo.Network.Remotes;
return remotes["origin"] == null && remotes.Any();
}

/// <summary>
/// Returns a <see cref="UriString"/> representing the uri of a remote
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/GitHub.Exports/Services/ITeamExplorerServices.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Windows.Input;
using System.Threading.Tasks;

namespace GitHub.Services
{
Expand All @@ -7,6 +7,7 @@ public interface ITeamExplorerServices : INotificationService
void ShowConnectPage();
void ShowHomePage();
void ShowPublishSection();
Task ShowRepositorySettingsRemotesAsync();
void ClearNotifications();
void OpenRepository(string repositoryPath);
}
Expand Down
28 changes: 28 additions & 0 deletions src/GitHub.Exports/Services/ITippingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Windows;

namespace GitHub.Services
{
/// <summary>
/// This service is a thin wrapper around <see cref="Microsoft.Internal.VisualStudio.Shell.Interop.IVsTippingService"/>.
/// </summary>
/// <remarks>
/// The <see cref="IVsTippingService"/> interface is public, but contained within the 'Microsoft.VisualStudio.Shell.UI.Internal' assembly.
/// To avoid a direct dependency on 'Microsoft.VisualStudio.Shell.UI.Internal', we use reflection to call this service.
/// </remarks>
public interface ITippingService
{
/// <summary>
/// Show a call-out notification with the option to execute a command.
/// </summary>
/// <param name="calloutId">A unique id for the callout so that is can be permanently dismissed.</param>
/// <param name="title">A clickable title for the callout.</param>
/// <param name="message">A plain text message for that callout that will automatically wrap.</param>
/// <param name="isPermanentlyDismissible">True for an option to never show again.</param>
/// <param name="targetElement">A UI element for the callout to appear above which must be visible.</param>
/// <param name="vsCommandGroupId">The group of the command to execute when title is clicked.</param>
/// <param name="vsCommandId">The ID of the command to execute when title is clicked.</param>
void RequestCalloutDisplay(Guid calloutId, string title, string message,
bool isPermanentlyDismissible, FrameworkElement targetElement, Guid vsCommandGroupId, uint vsCommandId);
}
}
3 changes: 3 additions & 0 deletions src/GitHub.Exports/Settings/Guids.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ public static class Guids
// Guids defined in InlineReviewsPackage.vsct
public const string CommandSetString = "C5F1193E-F300-41B3-B4C4-5A703DD3C1C6";
public static readonly Guid CommandSetGuid = new Guid(CommandSetString);

// Callout notification IDs
public static readonly Guid NoRemoteOriginCalloutId = new Guid("B5679412-58A1-49CD-96E9-8F093FE3DC79");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ async Task StatusChanged()

async Task<PullRequestSession> GetSessionInternal(string owner, string name, int number)
{
var cloneUrl = repository.CloneUrl;
if (cloneUrl == null)
{
// Can't create a session from a repository with no origin
return null;
}

PullRequestSession session = null;
WeakReference<PullRequestSession> weakSession;
var key = Tuple.Create(owner.ToLowerInvariant(), number);
Expand All @@ -238,7 +245,7 @@ async Task<PullRequestSession> GetSessionInternal(string owner, string name, int

if (session == null)
{
var address = HostAddress.Create(repository.CloneUrl);
var address = HostAddress.Create(cloneUrl);
var pullRequest = await sessionService.ReadPullRequestDetail(address, owner, name, number);

session = new PullRequestSession(
Expand Down
Loading