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

Commit 6eda710

Browse files
authored
Merge pull request #2026 from github/fixes/1729-warn-when-no-origin
Warn when no origin remote
2 parents e5709af + 1e789ed commit 6eda710

File tree

26 files changed

+968
-236
lines changed

26 files changed

+968
-236
lines changed

GitHubVS.sln

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InstallAndStart", "test\Lau
135135
EndProject
136136
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.TeamFoundation.16", "src\GitHub.TeamFoundation.16\GitHub.TeamFoundation.16.csproj", "{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}"
137137
EndProject
138+
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}"
139+
EndProject
138140
Global
139141
GlobalSection(SolutionConfigurationPlatforms) = preSolution
140142
Debug|Any CPU = Debug|Any CPU
@@ -471,6 +473,16 @@ Global
471473
{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.Release|Any CPU.Build.0 = Release|Any CPU
472474
{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU
473475
{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU
476+
{65542DEE-D3BE-4810-B85A-08E970413A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
477+
{65542DEE-D3BE-4810-B85A-08E970413A21}.Debug|Any CPU.Build.0 = Debug|Any CPU
478+
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU
479+
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU
480+
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU
481+
{65542DEE-D3BE-4810-B85A-08E970413A21}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU
482+
{65542DEE-D3BE-4810-B85A-08E970413A21}.Release|Any CPU.ActiveCfg = Release|Any CPU
483+
{65542DEE-D3BE-4810-B85A-08E970413A21}.Release|Any CPU.Build.0 = Release|Any CPU
484+
{65542DEE-D3BE-4810-B85A-08E970413A21}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU
485+
{65542DEE-D3BE-4810-B85A-08E970413A21}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU
474486
EndGlobalSection
475487
GlobalSection(SolutionProperties) = preSolution
476488
HideSolutionNode = FALSE
@@ -502,6 +514,7 @@ Global
502514
{86C54B27-717F-478C-AC8C-01F1C68A56C5} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9}
503515
{C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9}
504516
{E899B03C-6E8E-4375-AB65-FC925D721D8B} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9}
517+
{65542DEE-D3BE-4810-B85A-08E970413A21} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD}
505518
EndGlobalSection
506519
GlobalSection(ExtensibilityGlobals) = postSolution
507520
SolutionGuid = {556014CF-5B35-4CE5-B3EF-6AB0007001AC}

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I
4545
readonly ILoggedOutViewModel loggedOut;
4646
readonly INotAGitHubRepositoryViewModel notAGitHubRepository;
4747
readonly INotAGitRepositoryViewModel notAGitRepository;
48+
readonly INoRemoteOriginViewModel noRemoteOrigin;
4849
readonly ILoginFailedViewModel loginFailed;
4950
readonly SemaphoreSlim navigating = new SemaphoreSlim(1);
5051
readonly ObservableAsPropertyHelper<ContentOverride> contentOverride;
@@ -72,6 +73,7 @@ public GitHubPaneViewModel(
7273
ILoggedOutViewModel loggedOut,
7374
INotAGitHubRepositoryViewModel notAGitHubRepository,
7475
INotAGitRepositoryViewModel notAGitRepository,
76+
INoRemoteOriginViewModel noRemoteOrigin,
7577
ILoginFailedViewModel loginFailed)
7678
{
7779
Guard.ArgumentNotNull(viewModelFactory, nameof(viewModelFactory));
@@ -84,6 +86,7 @@ public GitHubPaneViewModel(
8486
Guard.ArgumentNotNull(loggedOut, nameof(loggedOut));
8587
Guard.ArgumentNotNull(notAGitHubRepository, nameof(notAGitHubRepository));
8688
Guard.ArgumentNotNull(notAGitRepository, nameof(notAGitRepository));
89+
Guard.ArgumentNotNull(noRemoteOrigin, nameof(noRemoteOrigin));
8790
Guard.ArgumentNotNull(loginFailed, nameof(loginFailed));
8891

8992
this.viewModelFactory = viewModelFactory;
@@ -94,6 +97,7 @@ public GitHubPaneViewModel(
9497
this.loggedOut = loggedOut;
9598
this.notAGitHubRepository = notAGitHubRepository;
9699
this.notAGitRepository = notAGitRepository;
100+
this.noRemoteOrigin = noRemoteOrigin;
97101
this.loginFailed = loginFailed;
98102

99103
var contentAndNavigatorContent = Observable.CombineLatest(
@@ -433,8 +437,17 @@ async Task UpdateContent(LocalRepositoryModel repository)
433437
}
434438
else if (string.IsNullOrWhiteSpace(repository.CloneUrl))
435439
{
436-
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
437-
Content = notAGitHubRepository;
440+
if (repository.HasRemotesButNoOrigin)
441+
{
442+
log.Debug("No origin remote");
443+
Content = noRemoteOrigin;
444+
}
445+
else
446+
{
447+
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
448+
Content = notAGitHubRepository;
449+
}
450+
438451
return;
439452
}
440453

@@ -490,7 +503,7 @@ async Task UpdateContent(LocalRepositoryModel repository)
490503
Content = loggedOut;
491504
}
492505
}
493-
506+
494507
if (notGitHubRepo)
495508
{
496509
log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Reactive;
3+
using System.Threading.Tasks;
4+
using System.ComponentModel.Composition;
5+
using GitHub.Services;
6+
using ReactiveUI;
7+
8+
namespace GitHub.ViewModels.GitHubPane
9+
{
10+
/// <summary>
11+
/// The view model for the "No Origin Remote" view in the GitHub pane.
12+
/// </summary>
13+
[Export(typeof(INoRemoteOriginViewModel))]
14+
[PartCreationPolicy(CreationPolicy.NonShared)]
15+
public class NoRemoteOriginViewModel : PanePageViewModelBase, INoRemoteOriginViewModel
16+
{
17+
ITeamExplorerServices teamExplorerServices;
18+
19+
[ImportingConstructor]
20+
public NoRemoteOriginViewModel(ITeamExplorerServices teamExplorerServices)
21+
{
22+
this.teamExplorerServices = teamExplorerServices;
23+
EditRemotes = ReactiveCommand.CreateFromTask(teamExplorerServices.ShowRepositorySettingsRemotesAsync);
24+
}
25+
26+
public ReactiveCommand<Unit, Unit> EditRemotes { get; }
27+
}
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Reactive;
2+
using ReactiveUI;
3+
4+
namespace GitHub.ViewModels.GitHubPane
5+
{
6+
/// <summary>
7+
/// Defines the view model for the "No Origin Remote" view in the GitHub pane.
8+
/// </summary>
9+
public interface INoRemoteOriginViewModel : IPanePageViewModel
10+
{
11+
/// <summary>
12+
/// Gets a command that will allow the user to rename remotes.
13+
/// </summary>
14+
ReactiveCommand<Unit, Unit> EditRemotes { get; }
15+
}
16+
}

src/GitHub.Exports/Models/BranchModel.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ namespace GitHub.Models
77
public class BranchModel : ICopyable<BranchModel>,
88
IEquatable<BranchModel>, IComparable<BranchModel>
99
{
10-
public BranchModel(string name, RepositoryModel repo, string sha, bool isTracking, string trackedSha) :
10+
public BranchModel(string name, RepositoryModel repo, string sha, bool isTracking, string trackedSha, string trackedRemoteName) :
1111
this(name, repo)
1212
{
1313
IsTracking = isTracking;
1414
Sha = sha;
1515
TrackedSha = trackedSha;
16+
TrackedRemoteName = trackedRemoteName;
1617
}
1718

1819
public BranchModel(string name, RepositoryModel repo)
@@ -32,6 +33,7 @@ public BranchModel(string name, RepositoryModel repo)
3233
public string DisplayName { get; set; }
3334
public string Sha { get; private set; }
3435
public string TrackedSha { get; private set; }
36+
public string TrackedRemoteName { get; private set; }
3537

3638
#region Equality things
3739
public void CopyFrom(BranchModel other)

src/GitHub.Exports/Models/LocalRepositoryModel.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Diagnostics;
33
using System.Globalization;
44
using System.ComponentModel;
5+
using System.Collections.Generic;
6+
using GitHub.Primitives;
57

68
namespace GitHub.Models
79
{
@@ -22,6 +24,14 @@ public string LocalPath
2224
get; set;
2325
}
2426

27+
/// <summary>
28+
/// True if repository has remotes but none are named "origin".
29+
/// </summary>
30+
public bool HasRemotesButNoOrigin
31+
{
32+
get; set;
33+
}
34+
2535
/// <summary>
2636
/// Note: We don't consider CloneUrl a part of the hash code because it can change during the lifetime
2737
/// of a repository. Equals takes care of any hash collisions because of this

src/GitHub.Exports/Services/GitService.cs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.Linq;
44
using System.Threading.Tasks;
5+
using System.Collections.Generic;
56
using System.ComponentModel.Composition;
67
using GitHub.UI;
78
using GitHub.Models;
@@ -38,18 +39,29 @@ public LocalRepositoryModel CreateLocalRepositoryModel(string localPath)
3839
throw new ArgumentException("Path does not exist", nameof(localPath));
3940
}
4041

41-
var cloneUrl = GetUri(localPath);
42-
var name = cloneUrl?.RepositoryName ?? dir.Name;
43-
44-
var model = new LocalRepositoryModel
42+
using (var repository = GetRepository(localPath))
4543
{
46-
LocalPath = localPath,
47-
CloneUrl = cloneUrl,
48-
Name = name,
49-
Icon = Octicon.repo
50-
};
44+
UriString cloneUrl = null;
45+
bool noOrigin = false;
46+
if (repository != null)
47+
{
48+
cloneUrl = GetUri(repository);
49+
noOrigin = HasRemotesButNoOrigin(repository);
50+
}
5151

52-
return model;
52+
var name = cloneUrl?.RepositoryName ?? dir.Name;
53+
54+
var model = new LocalRepositoryModel
55+
{
56+
LocalPath = localPath,
57+
CloneUrl = cloneUrl,
58+
HasRemotesButNoOrigin = noOrigin,
59+
Name = name,
60+
Icon = Octicon.repo
61+
};
62+
63+
return model;
64+
}
5365
}
5466

5567
public BranchModel GetBranch(LocalRepositoryModel model)
@@ -68,7 +80,8 @@ public BranchModel GetBranch(LocalRepositoryModel model)
6880
repo: model,
6981
sha: branch.Tip?.Sha,
7082
isTracking: branch.IsTracking,
71-
trackedSha: branch.TrackedBranch?.Tip?.Sha);
83+
trackedSha: branch.TrackedBranch?.Tip?.Sha,
84+
trackedRemoteName: branch.TrackedBranch?.RemoteName);
7285
}
7386
}
7487

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

135+
/// <summary>
136+
/// Find out if repository has remotes but none are called "origin".
137+
/// </summary>
138+
/// <param name="repo">The target repository.</param>
139+
/// <returns>True if repository has remotes but none are called "origin".</returns>
140+
public bool HasRemotesButNoOrigin(IRepository repo)
141+
{
142+
var remotes = repo.Network.Remotes;
143+
return remotes["origin"] == null && remotes.Any();
144+
}
145+
122146
/// <summary>
123147
/// Returns a <see cref="UriString"/> representing the uri of a remote
124148
/// </summary>

src/GitHub.Exports/Services/ITeamExplorerServices.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Windows.Input;
1+
using System.Threading.Tasks;
22

33
namespace GitHub.Services
44
{
@@ -7,6 +7,7 @@ public interface ITeamExplorerServices : INotificationService
77
void ShowConnectPage();
88
void ShowHomePage();
99
void ShowPublishSection();
10+
Task ShowRepositorySettingsRemotesAsync();
1011
void ClearNotifications();
1112
void OpenRepository(string repositoryPath);
1213
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Windows;
3+
4+
namespace GitHub.Services
5+
{
6+
/// <summary>
7+
/// This service is a thin wrapper around <see cref="Microsoft.Internal.VisualStudio.Shell.Interop.IVsTippingService"/>.
8+
/// </summary>
9+
/// <remarks>
10+
/// The <see cref="IVsTippingService"/> interface is public, but contained within the 'Microsoft.VisualStudio.Shell.UI.Internal' assembly.
11+
/// To avoid a direct dependency on 'Microsoft.VisualStudio.Shell.UI.Internal', we use reflection to call this service.
12+
/// </remarks>
13+
public interface ITippingService
14+
{
15+
/// <summary>
16+
/// Show a call-out notification with the option to execute a command.
17+
/// </summary>
18+
/// <param name="calloutId">A unique id for the callout so that is can be permanently dismissed.</param>
19+
/// <param name="title">A clickable title for the callout.</param>
20+
/// <param name="message">A plain text message for that callout that will automatically wrap.</param>
21+
/// <param name="isPermanentlyDismissible">True for an option to never show again.</param>
22+
/// <param name="targetElement">A UI element for the callout to appear above which must be visible.</param>
23+
/// <param name="vsCommandGroupId">The group of the command to execute when title is clicked.</param>
24+
/// <param name="vsCommandId">The ID of the command to execute when title is clicked.</param>
25+
void RequestCalloutDisplay(Guid calloutId, string title, string message,
26+
bool isPermanentlyDismissible, FrameworkElement targetElement, Guid vsCommandGroupId, uint vsCommandId);
27+
}
28+
}

src/GitHub.Exports/Settings/Guids.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,8 @@ public static class Guids
3939
// Guids defined in InlineReviewsPackage.vsct
4040
public const string CommandSetString = "C5F1193E-F300-41B3-B4C4-5A703DD3C1C6";
4141
public static readonly Guid CommandSetGuid = new Guid(CommandSetString);
42+
43+
// Callout notification IDs
44+
public static readonly Guid NoRemoteOriginCalloutId = new Guid("B5679412-58A1-49CD-96E9-8F093FE3DC79");
4245
}
4346
}

0 commit comments

Comments
 (0)