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

Commit b31b14f

Browse files
committed
Add ResolveGitObject to find local object from URL
Check that object exists before navigating.
1 parent 8e968cc commit b31b14f

File tree

5 files changed

+121
-10
lines changed

5 files changed

+121
-10
lines changed

src/GitHub.App/Services/GitHubContextService.cs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
using Microsoft.VisualStudio.Shell;
1414
using Microsoft.VisualStudio.Shell.Interop;
1515
using Microsoft.VisualStudio.TextManager.Interop;
16+
using LibGit2Sharp;
1617

1718
namespace GitHub.App.Services
1819
{
1920
[Export(typeof(IGitHubContextService))]
2021
public class GitHubContextService : IGitHubContextService
2122
{
2223
readonly IServiceProvider serviceProvider;
24+
readonly IGitService gitService;
2325

2426
// USERID_REGEX = /[a-z0-9][a-z0-9\-\_]*/i
2527
const string owner = "(?<owner>[a-zA-Z0-9][a-zA-Z0-9-_]*)";
@@ -53,9 +55,10 @@ public class GitHubContextService : IGitHubContextService
5355
static readonly Regex treeishBranchRegex = new Regex($"(?<branch>master)(/(?<tree>.+))?", RegexOptions.Compiled);
5456

5557
[ImportingConstructor]
56-
public GitHubContextService([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
58+
public GitHubContextService([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, IGitService gitService)
5759
{
5860
this.serviceProvider = serviceProvider;
61+
this.gitService = gitService;
5962
}
6063

6164
public GitHubContext FindContextFromClipboard()
@@ -303,6 +306,42 @@ public string ResolvePath(GitHubContext context)
303306
return null;
304307
}
305308

309+
public GitObject ResolveGitObject(string repositoryDir, GitHubContext context)
310+
{
311+
using (var repository = gitService.GetRepository(repositoryDir))
312+
{
313+
var path = context.TreeishPath;
314+
if (context.BlobName != null)
315+
{
316+
path += '/' + context.BlobName;
317+
}
318+
319+
foreach (var treeish in ToTreeish(path))
320+
{
321+
var gitObject = repository.Lookup(treeish);
322+
if (gitObject != null)
323+
{
324+
return gitObject;
325+
}
326+
}
327+
328+
return null;
329+
}
330+
}
331+
332+
static IEnumerable<string> ToTreeish(string treeishPath)
333+
{
334+
yield return treeishPath;
335+
336+
var index = 0;
337+
while ((index = treeishPath.IndexOf('/', index + 1)) != -1)
338+
{
339+
var commitish = treeishPath.Substring(0, index);
340+
var path = treeishPath.Substring(index + 1);
341+
yield return $"{commitish}:{path}";
342+
}
343+
}
344+
306345
IVsTextView OpenDocument(string fullPath)
307346
{
308347
var logicalView = VSConstants.LOGVIEWID.TextView_guid;

src/GitHub.Exports/Services/IGitHubContextService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using LibGit2Sharp;
34

45
namespace GitHub.Services
56
{
@@ -13,5 +14,6 @@ public interface IGitHubContextService
1314
string ResolvePath(GitHubContext context);
1415
Uri ToRepositoryUrl(GitHubContext context);
1516
bool TryOpenFile(string repositoryDir, GitHubContext context);
17+
GitObject ResolveGitObject(string repositoryDir, GitHubContext context);
1618
}
1719
}

src/GitHub.VisualStudio/Commands/OpenFromClipboardCommand.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace GitHub.VisualStudio.Commands
1111
public class OpenFromClipboardCommand : VsCommand<string>, IOpenFromClipboardCommand
1212
{
1313
public const string NoGitHubUrlMessage = "Couldn't a find a GitHub URL in clipboard";
14+
public const string NoResolveMessage = "Couldn't resolve Git object";
1415

1516
readonly Lazy<IGitHubContextService> gitHubContextService;
1617
readonly Lazy<ITeamExplorerContext> teamExplorerContext;
@@ -50,14 +51,21 @@ public override Task Execute(string url)
5051
return Task.CompletedTask;
5152
}
5253

53-
var activeDir = teamExplorerContext.Value.ActiveRepository?.LocalPath;
54+
var repositoryDir = teamExplorerContext.Value.ActiveRepository?.LocalPath;
5455
if (context == null)
5556
{
5657
// No active repository
5758
return Task.CompletedTask;
5859
}
5960

60-
if (!gitHubContextService.Value.TryOpenFile(activeDir, context))
61+
var gitObject = gitHubContextService.Value.ResolveGitObject(repositoryDir, context);
62+
if (gitObject == null)
63+
{
64+
vsServices.Value.ShowMessageBoxInfo(NoResolveMessage);
65+
return Task.CompletedTask;
66+
}
67+
68+
if (!gitHubContextService.Value.TryOpenFile(repositoryDir, context))
6169
{
6270
// Couldn't open file
6371
return Task.CompletedTask;

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using GitHub.App.Services;
44
using NSubstitute;
55
using NUnit.Framework;
6+
using LibGit2Sharp;
67

78
public class GitHubContextServiceTests
89
{
@@ -322,6 +323,28 @@ public void RepositoryHome(string windowTitle, string expectOwner, string expect
322323
}
323324
}
324325

326+
public class TheResolveGitObjectMethod
327+
{
328+
[TestCase("https://github.com/github/VisualStudio/blob/master/foo.cs", "master:foo.cs")]
329+
[TestCase("https://github.com/github/VisualStudio/blob/master/src/foo.cs", "master:src/foo.cs")]
330+
[TestCase("https://github.com/github/VisualStudio/blob/branch-name/src/foo.cs", "branch-name:src/foo.cs")]
331+
[TestCase("https://github.com/github/VisualStudio/blob/fixes/666-bug/src/foo.cs", "fixes/666-bug:src/foo.cs")]
332+
[TestCase("https://github.com/github/VisualStudio/blob/fixes/666-bug/A/B/foo.cs", "fixes/666-bug:A/B/foo.cs")]
333+
public void ResolveGitObject(string url, string treeish)
334+
{
335+
var repositoryDir = "repositoryDir";
336+
var repository = Substitute.For<IRepository>();
337+
var expectGitObject = Substitute.For<GitObject>();
338+
repository.Lookup(treeish).Returns(expectGitObject);
339+
var target = CreateGitHubContextService(repositoryDir, repository);
340+
var context = target.FindContextFromUrl(url);
341+
342+
var gitObject = target.ResolveGitObject(repositoryDir, context);
343+
344+
Assert.That(gitObject, Is.EqualTo(expectGitObject));
345+
}
346+
}
347+
325348
public class TheResolvePathMethod
326349
{
327350
[TestCase("https://github.com/github/VisualStudio/blob/ee863ce265fc6217f589e66766125fed1b5b8256/foo.cs", "foo.cs")]
@@ -343,9 +366,12 @@ public void ResolvePath(string url, string expectPath)
343366
}
344367
}
345368

346-
static GitHubContextService CreateGitHubContextService()
369+
static GitHubContextService CreateGitHubContextService(string repositoryDir = null, IRepository repository = null)
347370
{
348371
var sp = Substitute.For<IServiceProvider>();
349-
return new GitHubContextService(sp);
372+
var gitService = Substitute.For<IGitService>();
373+
gitService.GetRepository(repositoryDir).Returns(repository);
374+
375+
return new GitHubContextService(sp, gitService);
350376
}
351377
}

test/GitHub.VisualStudio.UnitTests/Commands/OpenFromClipboardCommandTests.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,71 @@
55
using Microsoft.VisualStudio;
66
using NSubstitute;
77
using NUnit.Framework;
8+
using LibGit2Sharp;
89

910
public class OpenFromClipboardCommandTests
1011
{
1112
public class TheExecuteMethod
1213
{
1314
[Test]
14-
public async Task Execute()
15+
public async Task NothingInClipboard()
1516
{
16-
var gitHubContextService = Substitute.For<IGitHubContextService>();
17-
gitHubContextService.FindContextFromClipboard().Returns(null as GitHubContext);
1817
var vsServices = Substitute.For<IVSServices>();
1918
vsServices.ShowMessageBoxInfo(null).Returns(VSConstants.MessageBoxResult.IDOK);
20-
var target = CreateOpenFromClipboardCommand(gitHubContextService, vsServices: vsServices);
19+
var target = CreateOpenFromClipboardCommand(vsServices: vsServices, contextFromClipboard: null);
2120

2221
await target.Execute(null);
2322

2423
vsServices.Received(1).ShowMessageBoxInfo(OpenFromClipboardCommand.NoGitHubUrlMessage);
2524
}
2625

26+
[Test]
27+
public async Task CouldNotResolve()
28+
{
29+
var context = new GitHubContext();
30+
var repositoryDir = "repositoryDir";
31+
var gitObject = null as GitObject;
32+
var vsServices = Substitute.For<IVSServices>();
33+
var target = CreateOpenFromClipboardCommand(vsServices: vsServices,
34+
contextFromClipboard: context, repositoryDir: repositoryDir, gitObject: gitObject);
35+
36+
await target.Execute(null);
37+
38+
vsServices.Received(1).ShowMessageBoxInfo(OpenFromClipboardCommand.NoResolveMessage);
39+
}
40+
41+
[Test]
42+
public async Task CouldResolve()
43+
{
44+
var context = new GitHubContext();
45+
var repositoryDir = "repositoryDir";
46+
var gitObject = Substitute.For<GitObject>();
47+
var vsServices = Substitute.For<IVSServices>();
48+
var target = CreateOpenFromClipboardCommand(vsServices: vsServices,
49+
contextFromClipboard: context, repositoryDir: repositoryDir, gitObject: gitObject);
50+
51+
await target.Execute(null);
52+
53+
vsServices.DidNotReceiveWithAnyArgs().ShowMessageBoxInfo(null);
54+
}
55+
2756
static OpenFromClipboardCommand CreateOpenFromClipboardCommand(
2857
IGitHubContextService gitHubContextService = null,
2958
ITeamExplorerContext teamExplorerContext = null,
30-
IVSServices vsServices = null)
59+
IVSServices vsServices = null,
60+
GitHubContext contextFromClipboard = null,
61+
string repositoryDir = null,
62+
GitObject gitObject = null)
3163
{
3264
var sp = Substitute.For<IServiceProvider>();
3365
gitHubContextService = gitHubContextService ?? Substitute.For<IGitHubContextService>();
3466
teamExplorerContext = teamExplorerContext ?? Substitute.For<ITeamExplorerContext>();
3567
vsServices = vsServices ?? Substitute.For<IVSServices>();
3668

69+
gitHubContextService.FindContextFromClipboard().Returns(contextFromClipboard);
70+
gitHubContextService.ResolveGitObject(repositoryDir, contextFromClipboard).Returns(gitObject);
71+
teamExplorerContext.ActiveRepository.LocalPath.Returns(repositoryDir);
72+
3773
return new OpenFromClipboardCommand(
3874
new Lazy<IGitHubContextService>(() => gitHubContextService),
3975
new Lazy<ITeamExplorerContext>(() => teamExplorerContext),

0 commit comments

Comments
 (0)