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

Commit 865845e

Browse files
authored
Merge pull request #2060 from github/feature/open-file-from-github
Open file from GitHub (MVP)
2 parents 2b46a3c + 8f81858 commit 865845e

File tree

15 files changed

+195
-64
lines changed

15 files changed

+195
-64
lines changed

src/GitHub.App/Services/DialogService.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,31 @@ public class DialogService : IDialogService
1616
{
1717
readonly IViewViewModelFactory factory;
1818
readonly IShowDialogService showDialog;
19+
readonly IGitHubContextService gitHubContextService;
1920

2021
[ImportingConstructor]
2122
public DialogService(
2223
IViewViewModelFactory factory,
23-
IShowDialogService showDialog)
24+
IShowDialogService showDialog,
25+
IGitHubContextService gitHubContextService)
2426
{
2527
Guard.ArgumentNotNull(factory, nameof(factory));
2628
Guard.ArgumentNotNull(showDialog, nameof(showDialog));
29+
Guard.ArgumentNotNull(showDialog, nameof(gitHubContextService));
2730

2831
this.factory = factory;
2932
this.showDialog = showDialog;
33+
this.gitHubContextService = gitHubContextService;
3034
}
3135

3236
public async Task<CloneDialogResult> ShowCloneDialog(IConnection connection, string url = null)
3337
{
38+
if (string.IsNullOrEmpty(url))
39+
{
40+
var clipboardContext = gitHubContextService.FindContextFromClipboard();
41+
url = clipboardContext?.Url;
42+
}
43+
3444
var viewModel = factory.CreateViewModel<IRepositoryCloneViewModel>();
3545
if (url != null)
3646
{

src/GitHub.App/Services/GitHubContextService.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Text;
44
using System.Linq;
55
using System.Windows;
6+
using System.Globalization;
67
using System.Threading.Tasks;
78
using System.Collections.Generic;
89
using System.ComponentModel.Composition;
@@ -25,6 +26,7 @@ public class GitHubContextService : IGitHubContextService
2526
{
2627
readonly IGitHubServiceProvider serviceProvider;
2728
readonly IGitService gitService;
29+
readonly IVSServices vsServices;
2830
readonly Lazy<IVsTextManager2> textManager;
2931

3032
// USERID_REGEX = /[a-z0-9][a-z0-9\-\_]*/i
@@ -61,13 +63,38 @@ public class GitHubContextService : IGitHubContextService
6163
static readonly Regex tempFileObjectishRegex = new Regex(@"\\TFSTemp\\[^\\]*[.](?<objectish>[a-z0-9]{8})[.][^.\\]*$", RegexOptions.Compiled);
6264

6365
[ImportingConstructor]
64-
public GitHubContextService(IGitHubServiceProvider serviceProvider, IGitService gitService)
66+
public GitHubContextService(IGitHubServiceProvider serviceProvider, IGitService gitService, IVSServices vsServices)
6567
{
6668
this.serviceProvider = serviceProvider;
6769
this.gitService = gitService;
70+
this.vsServices = vsServices;
6871
textManager = new Lazy<IVsTextManager2>(() => serviceProvider.GetService<SVsTextManager, IVsTextManager2>());
6972
}
7073

74+
/// <inheritdoc/>
75+
public void TryNavigateToContext(string repositoryDir, GitHubContext context)
76+
{
77+
if (context?.LinkType == LinkType.Blob)
78+
{
79+
var (commitish, path, commitSha) = ResolveBlob(repositoryDir, context);
80+
if (commitish == null && path == null)
81+
{
82+
var message = string.Format(CultureInfo.CurrentCulture, Resources.CouldntFindCorrespondingFile, context.Url);
83+
vsServices.ShowMessageBoxInfo(message);
84+
return;
85+
}
86+
87+
var hasChanges = HasChangesInWorkingDirectory(repositoryDir, commitish, path);
88+
if (hasChanges)
89+
{
90+
var message = string.Format(CultureInfo.CurrentCulture, Resources.ChangesInWorkingDirectoryMessage, commitish);
91+
vsServices.ShowMessageBoxInfo(message);
92+
}
93+
94+
TryOpenFile(repositoryDir, context);
95+
}
96+
}
97+
7198
/// <inheritdoc/>
7299
public GitHubContext FindContextFromClipboard()
73100
{

src/GitHub.App/Services/RepositoryCloneService.cs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ public class RepositoryCloneService : IRepositoryCloneService
3636
readonly IVSGitServices vsGitServices;
3737
readonly ITeamExplorerServices teamExplorerServices;
3838
readonly IGraphQLClientFactory graphqlFactory;
39+
readonly IGitHubContextService gitHubContextService;
3940
readonly IUsageTracker usageTracker;
41+
readonly Lazy<EnvDTE.DTE> dte;
4042
ICompiledQuery<ViewerRepositoriesModel> readViewerRepositories;
4143

4244
[ImportingConstructor]
@@ -45,13 +47,17 @@ public RepositoryCloneService(
4547
IVSGitServices vsGitServices,
4648
ITeamExplorerServices teamExplorerServices,
4749
IGraphQLClientFactory graphqlFactory,
48-
IUsageTracker usageTracker)
50+
IGitHubContextService gitHubContextService,
51+
IUsageTracker usageTracker,
52+
IGitHubServiceProvider sp)
4953
{
5054
this.operatingSystem = operatingSystem;
5155
this.vsGitServices = vsGitServices;
5256
this.teamExplorerServices = teamExplorerServices;
5357
this.graphqlFactory = graphqlFactory;
58+
this.gitHubContextService = gitHubContextService;
5459
this.usageTracker = usageTracker;
60+
dte = new Lazy<EnvDTE.DTE>(() => sp.GetService<EnvDTE.DTE>());
5561

5662
defaultClonePath = GetLocalClonePathFromGitProvider(operatingSystem.Environment.GetUserRepositoriesPath());
5763
}
@@ -125,7 +131,10 @@ public async Task CloneOrOpenRepository(
125131
var isDotCom = HostAddress.IsGitHubDotComUri(repositoryUrl);
126132
if (DestinationDirectoryExists(repositoryPath))
127133
{
128-
teamExplorerServices.OpenRepository(repositoryPath);
134+
if (!IsSolutionInRepository(repositoryPath))
135+
{
136+
teamExplorerServices.OpenRepository(repositoryPath);
137+
}
129138

130139
if (isDotCom)
131140
{
@@ -153,6 +162,36 @@ public async Task CloneOrOpenRepository(
153162

154163
// Give user a chance to choose a solution
155164
teamExplorerServices.ShowHomePage();
165+
166+
// Navigate to context for supported URL types (e.g. /blob/ URLs)
167+
var context = gitHubContextService.FindContextFromUrl(url);
168+
if (context != null)
169+
{
170+
gitHubContextService.TryNavigateToContext(repositoryPath, context);
171+
}
172+
}
173+
174+
bool IsSolutionInRepository(string repositoryPath)
175+
{
176+
var solutionPath = dte.Value.Solution.FileName;
177+
if (string.IsNullOrEmpty(solutionPath))
178+
{
179+
return false;
180+
}
181+
182+
var isFolder = operatingSystem.Directory.DirectoryExists(solutionPath);
183+
var solutionDir = isFolder ? solutionPath : Path.GetDirectoryName(solutionPath);
184+
if (string.Equals(repositoryPath, solutionDir, StringComparison.OrdinalIgnoreCase))
185+
{
186+
return true;
187+
}
188+
189+
if (solutionDir.StartsWith(repositoryPath + '\\', StringComparison.OrdinalIgnoreCase))
190+
{
191+
return true;
192+
}
193+
194+
return false;
156195
}
157196

158197
/// <inheritdoc/>

src/GitHub.App/ViewModels/Dialog/Clone/RepositoryCloneViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ public async Task InitializeAsync(IConnection connection)
137137

138138
this.WhenAnyValue(x => x.SelectedTabIndex).Subscribe(x => tabs[x].Activate().Forget());
139139

140-
// Users in group A will see the URL tab by default
141-
if (await IsGroupA().ConfigureAwait(false))
140+
// When a clipboard URL has been set or a user is in group A, show the URL tab by default
141+
if (!string.IsNullOrEmpty(UrlTab.Url) || await IsGroupA().ConfigureAwait(false))
142142
{
143143
SelectedTabIndex = 2;
144144
}

src/GitHub.Exports/Services/IGitHubContextService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ namespace GitHub.Services
88
/// </summary>
99
public interface IGitHubContextService
1010
{
11+
/// <summary>
12+
/// Attempt to navigate to the equivalent context inside Visual Studio.
13+
/// </summary>
14+
/// <param name="repositoryDir">The target repository</param>
15+
/// <param name="context">The context to open.</param>
16+
void TryNavigateToContext(string repositoryDir, GitHubContext context);
17+
1118
/// <summary>
1219
/// Find the context from a URL in the clipboard if any.
1320
/// </summary>

src/GitHub.Resources/Resources.Designer.cs

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/GitHub.Resources/Resources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ https://git-scm.com/download/win</value>
810810
<value>There is no active repository to navigate</value>
811811
</data>
812812
<data name="ChangesInWorkingDirectoryMessage" xml:space="preserve">
813-
<value>This file has changed since the permalink was created</value>
813+
<value>The working file is different to the file at '{0}'. Please checkout the corresponding branch, pull request or commit.</value>
814814
</data>
815815
<data name="DifferentRepositoryMessage" xml:space="preserve">
816816
<value>Please open the repository '{0}' and try again</value>
@@ -845,4 +845,7 @@ https://git-scm.com/download/win</value>
845845
<data name="RepositoriesMustHaveRemoteOriginHowToFix" xml:space="preserve">
846846
<value>Please rename one of your existing remotes to 'origin' or add a new remote named 'origin' and fetch. This can be done from the command line or by clicking the button below.</value>
847847
</data>
848+
<data name="CouldntFindCorrespondingFile" xml:space="preserve">
849+
<value>Couldn't find file corresponding to '{0}' in the repository. Please do a 'git fetch' or checkout the target pull request.</value>
850+
</data>
848851
</root>

src/GitHub.VisualStudio/Commands/OpenFromClipboardCommand.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ namespace GitHub.VisualStudio.Commands
1212
[Export(typeof(IOpenFromClipboardCommand))]
1313
public class OpenFromClipboardCommand : VsCommand<string>, IOpenFromClipboardCommand
1414
{
15-
16-
1715
readonly Lazy<IGitHubContextService> gitHubContextService;
1816
readonly Lazy<ITeamExplorerContext> teamExplorerContext;
1917
readonly Lazy<IVSServices> vsServices;

src/GitHub.VisualStudio/Commands/OpenFromUrlCommand.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ namespace GitHub.VisualStudio.Commands
1111
public class OpenFromUrlCommand : VsCommand<string>, IOpenFromUrlCommand
1212
{
1313
readonly Lazy<IDialogService> dialogService;
14-
readonly Lazy<IGitHubContextService> gitHubContextService;
1514
readonly Lazy<IRepositoryCloneService> repositoryCloneService;
1615

1716
/// <summary>
@@ -27,12 +26,10 @@ public class OpenFromUrlCommand : VsCommand<string>, IOpenFromUrlCommand
2726
[ImportingConstructor]
2827
public OpenFromUrlCommand(
2928
Lazy<IDialogService> dialogService,
30-
Lazy<IGitHubContextService> gitHubContextService,
3129
Lazy<IRepositoryCloneService> repositoryCloneService) :
3230
base(CommandSet, CommandId)
3331
{
3432
this.dialogService = dialogService;
35-
this.gitHubContextService = gitHubContextService;
3633
this.repositoryCloneService = repositoryCloneService;
3734

3835
// See https://code.msdn.microsoft.com/windowsdesktop/AllowParams-2005-9442298f
@@ -41,12 +38,6 @@ public OpenFromUrlCommand(
4138

4239
public override async Task Execute(string url)
4340
{
44-
if (string.IsNullOrEmpty(url))
45-
{
46-
var clipboardContext = gitHubContextService.Value.FindContextFromClipboard();
47-
url = clipboardContext?.Url;
48-
}
49-
5041
var cloneDialogResult = await dialogService.Value.ShowCloneDialog(null, url);
5142
if (cloneDialogResult != null)
5243
{

test/GitHub.App.UnitTests/Services/GitHubContextServiceTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,9 @@ static GitHubContextService CreateGitHubContextService(string repositoryDir = nu
498498
{
499499
var sp = Substitute.For<IGitHubServiceProvider>();
500500
var gitService = Substitute.For<IGitService>();
501+
var vsServices = Substitute.For<IVSServices>();
501502
gitService.GetRepository(repositoryDir).Returns(repository);
502503

503-
return new GitHubContextService(sp, gitService);
504+
return new GitHubContextService(sp, gitService, vsServices);
504505
}
505506
}

0 commit comments

Comments
 (0)