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

Commit 0d264d5

Browse files
committed
Factor GitHub URL parsing into FindContextFromUrl
OpenFromUrlCommand now works with GitHubContext objects rather than directly with URLs.
1 parent d1b0231 commit 0d264d5

File tree

4 files changed

+174
-80
lines changed

4 files changed

+174
-80
lines changed

src/GitHub.App/Services/GitHubContext.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace GitHub.App.Services
1+
using System;
2+
3+
namespace GitHub.App.Services
24
{
35
public class GitHubContext
46
{
@@ -9,5 +11,6 @@ public class GitHubContext
911
public int? PullRequest { get; set; }
1012
public int? Issue { get; set; }
1113
public string Path { get; set; }
14+
public int? Line { get; set; }
1215
}
1316
}

src/GitHub.App/Services/GitHubContextService.cs

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,27 @@ public class GitHubContextService
3535
static readonly Regex windowTitlePathRegex = new Regex($"{path} at {branch} · {owner}/{repo} - ", RegexOptions.Compiled);
3636
static readonly Regex windowTitleBranchesRegex = new Regex($"Branches · {owner}/{repo} - ", RegexOptions.Compiled);
3737

38-
public GitHubContext FindContextFromUrl(UriString url)
38+
public GitHubContext FindContextFromUrl(string url)
3939
{
40+
var uri = new UriString(url);
41+
if (!uri.IsValidUri)
42+
{
43+
return null;
44+
}
45+
46+
if (!uri.IsHypertextTransferProtocol)
47+
{
48+
return null;
49+
}
50+
4051
return new GitHubContext
4152
{
42-
Host = url.Host,
43-
Owner = url.Owner,
44-
RepositoryName = url.RepositoryName,
53+
Host = uri.Host,
54+
Owner = uri.Owner,
55+
RepositoryName = uri.RepositoryName,
56+
Path = FindSubPath(uri, "/blob/master/"),
57+
PullRequest = FindPullRequest(uri),
58+
Line = FindLine(uri)
4559
};
4660
}
4761

@@ -68,6 +82,13 @@ public IEnumerable<string> FindWindowTitlesForClass(string className = "Chrome_W
6882
}
6983
}
7084

85+
public Uri ToRepositoryUrl(GitHubContext context)
86+
{
87+
var builder = new UriBuilder("https", context.Host ?? "github.com");
88+
builder.Path = $"{context.Owner}/{context.RepositoryName}";
89+
return builder.Uri;
90+
}
91+
7192
public GitHubContext FindContextFromWindowTitle(string windowTitle)
7293
{
7394
var (success, owner, repo, branch, pullRequest, issue, path) = MatchWindowTitle(windowTitle);
@@ -130,6 +151,60 @@ public GitHubContext FindContextFromWindowTitle(string windowTitle)
130151
return (match.Success, null, null, null, null, null, null);
131152
}
132153

154+
155+
static int? FindLine(UriString gitHubUrl)
156+
{
157+
var prefix = "#L";
158+
var url = gitHubUrl.ToString();
159+
var index = url.LastIndexOf(prefix);
160+
if (index == -1)
161+
{
162+
return null;
163+
}
164+
165+
if (!int.TryParse(url.Substring(index + prefix.Length), out int lineNumber))
166+
{
167+
return null;
168+
}
169+
170+
return lineNumber; // 1 based
171+
}
172+
173+
static int? FindPullRequest(UriString gitHubUrl)
174+
{
175+
var pullRequest = FindSubPath(gitHubUrl, "/pull/")?.Split('/').First();
176+
if (pullRequest == null)
177+
{
178+
return null;
179+
}
180+
181+
if (!int.TryParse(pullRequest, out int number))
182+
{
183+
return null;
184+
}
185+
186+
return number;
187+
}
188+
189+
static string FindSubPath(UriString gitHubUrl, string matchPath)
190+
{
191+
var url = gitHubUrl.ToString();
192+
var prefix = gitHubUrl.ToRepositoryUrl() + matchPath;
193+
if (!url.StartsWith(prefix))
194+
{
195+
return null;
196+
}
197+
198+
var endIndex = url.IndexOf('#');
199+
if (endIndex == -1)
200+
{
201+
endIndex = gitHubUrl.Length;
202+
}
203+
204+
var path = url.Substring(prefix.Length, endIndex - prefix.Length);
205+
return path;
206+
}
207+
133208
static class User32
134209
{
135210
[DllImport("user32.dll", SetLastError = true)]

src/GitHub.VisualStudio/Commands/OpenFromUrlCommand.cs

Lines changed: 17 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.ComponentModel.Composition;
77
using GitHub.Commands;
88
using GitHub.Services;
9-
using GitHub.Primitives;
109
using GitHub.App.Services;
1110
using GitHub.Services.Vssdk.Commands;
1211
using EnvDTE;
@@ -65,34 +64,20 @@ public override async Task Execute(string url)
6564
url = Clipboard.GetText(TextDataFormat.Text);
6665
}
6766

68-
var gitHubUrl = new UriString(url);
69-
if (!gitHubUrl.IsValidUri || !gitHubUrl.IsHypertextTransferProtocol)
70-
{
71-
gitHubUrl = null;
72-
}
73-
74-
GitHubContext context = null;
75-
if (gitHubUrl == null)
76-
{
77-
// HACK: Reconstruct a GitHub URL from the topmost browser window
78-
context = gitHubContextService.Value.FindContextFromBrowser();
79-
if (context != null)
80-
{
81-
gitHubUrl = $"https://github.com/{context.Owner}/{context.RepositoryName}";
82-
}
83-
}
67+
var context = gitHubContextService.Value.FindContextFromUrl(url);
68+
context = context ?? gitHubContextService.Value.FindContextFromBrowser();
8469

85-
if (gitHubUrl == null)
70+
if (context == null)
8671
{
8772
return;
8873
}
8974

9075
// Keep repos in unique dir while testing
9176
var defaultSubPath = "GitHubCache";
9277

93-
var cloneUrl = gitHubUrl.ToRepositoryUrl().ToString();
94-
var targetDir = Path.Combine(repositoryCloneService.Value.DefaultClonePath, defaultSubPath, gitHubUrl.Owner);
95-
var repositoryDirName = gitHubUrl.RepositoryName;
78+
var cloneUrl = gitHubContextService.Value.ToRepositoryUrl(context).ToString();
79+
var targetDir = Path.Combine(repositoryCloneService.Value.DefaultClonePath, defaultSubPath, context.Owner);
80+
var repositoryDirName = context.RepositoryName;
9681
var repositoryDir = Path.Combine(targetDir, repositoryDirName);
9782

9883
if (!Directory.Exists(repositoryDir))
@@ -139,8 +124,8 @@ public override async Task Execute(string url)
139124
}
140125
}
141126

142-
await TryOpenPullRequest(gitHubUrl);
143-
TryOpenFile(gitHubUrl, context, repositoryDir);
127+
await TryOpenPullRequest(context);
128+
TryOpenFile(context, repositoryDir);
144129
}
145130

146131
VSConstants.MessageBoxResult ShowInfoMessage(string message)
@@ -180,9 +165,9 @@ static string FindSolutionDirectory(Solution solution)
180165
return null;
181166
}
182167

183-
bool TryOpenFile(UriString gitHubUrl, GitHubContext context, string repositoryDir)
168+
bool TryOpenFile(GitHubContext context, string repositoryDir)
184169
{
185-
var path = FindSubPath(gitHubUrl, "/blob/master/") ?? context?.Path;
170+
var path = context.Path;
186171
if (path == null)
187172
{
188173
return false;
@@ -204,70 +189,28 @@ bool TryOpenFile(UriString gitHubUrl, GitHubContext context, string repositoryDi
204189

205190
dte.Value.ItemOperations.OpenFile(fullPath);
206191

207-
var lineNumber = FindLineNumber(gitHubUrl);
208-
if (lineNumber != -1)
192+
var lineNumber = context.Line;
193+
if (lineNumber == null)
209194
{
210195
var activeView = pullRequestEditorService.Value.FindActiveView();
211-
ErrorHandler.ThrowOnFailure(activeView.SetCaretPos(lineNumber, 0));
212-
ErrorHandler.ThrowOnFailure(activeView.CenterLines(lineNumber, 1));
196+
ErrorHandler.ThrowOnFailure(activeView.SetCaretPos(lineNumber.Value, 0));
197+
ErrorHandler.ThrowOnFailure(activeView.CenterLines(lineNumber.Value, 1));
213198
}
214199

215200
return true;
216201
}
217202

218-
async Task<bool> TryOpenPullRequest(UriString gitHubUrl)
203+
async Task<bool> TryOpenPullRequest(GitHubContext context)
219204
{
220-
var pullRequest = FindSubPath(gitHubUrl, "/pull/");
205+
var pullRequest = context.PullRequest;
221206
if (pullRequest == null)
222207
{
223208
return false;
224209
}
225210

226-
if (!int.TryParse(pullRequest, out int number))
227-
{
228-
return false;
229-
}
230-
231211
var host = await gitHubToolWindowManager.Value.ShowGitHubPane();
232-
await host.ShowPullRequest(gitHubUrl.Owner, gitHubUrl.RepositoryName, number);
212+
await host.ShowPullRequest(context.Owner, context.RepositoryName, pullRequest.Value);
233213
return true;
234214
}
235-
236-
static string FindSubPath(UriString gitHubUrl, string matchPath)
237-
{
238-
var url = gitHubUrl.ToString();
239-
var prefix = gitHubUrl.ToRepositoryUrl() + matchPath;
240-
if (!url.StartsWith(prefix))
241-
{
242-
return null;
243-
}
244-
245-
var endIndex = url.IndexOf('#');
246-
if (endIndex == -1)
247-
{
248-
endIndex = gitHubUrl.Length;
249-
}
250-
251-
var path = url.Substring(prefix.Length, endIndex - prefix.Length);
252-
return path;
253-
}
254-
255-
static int FindLineNumber(UriString gitHubUrl)
256-
{
257-
var prefix = "#L";
258-
var url = gitHubUrl.ToString();
259-
var index = url.LastIndexOf(prefix);
260-
if (index == -1)
261-
{
262-
return -1;
263-
}
264-
265-
if (!int.TryParse(url.Substring(index + prefix.Length), out int lineNumber))
266-
{
267-
return -1;
268-
}
269-
270-
return lineNumber - 1;
271-
}
272215
}
273216
}

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

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using GitHub.App.Services;
1+
using System;
2+
using GitHub.App.Services;
23
using NUnit.Framework;
34

45
public class GitHubContextServiceTests
@@ -43,6 +44,78 @@ public void Host(string url, string expectHost)
4344

4445
Assert.That(context.Host, Is.EqualTo(expectHost));
4546
}
47+
48+
[TestCase("https://github.com", null)]
49+
[TestCase("https://github.com/github", null)]
50+
[TestCase("https://github.com/github/VisualStudio", null)]
51+
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md", null)]
52+
[TestCase("https://github.com/github/VisualStudio/pull/1763", 1763)]
53+
[TestCase("https://github.com/github/VisualStudio/pull/1763/commits", 1763)]
54+
[TestCase("https://github.com/github/VisualStudio/pull/1763/files#diff-7384294e6c288e13bad0293bae232754R1", 1763)]
55+
[TestCase("https://github.com/github/VisualStudio/pull/NaN", null)]
56+
public void PullRequest(string url, int? expectPullRequest)
57+
{
58+
var target = new GitHubContextService();
59+
60+
var context = target.FindContextFromUrl(url);
61+
62+
Assert.That(context?.PullRequest, Is.EqualTo(expectPullRequest));
63+
}
64+
65+
[TestCase("https://github.com", null)]
66+
[TestCase("https://github.com/github", null)]
67+
[TestCase("https://github.com/github/VisualStudio", null)]
68+
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md", "README.md")]
69+
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md#notices", "README.md")]
70+
public void Path(string url, string expectPath)
71+
{
72+
var target = new GitHubContextService();
73+
74+
var context = target.FindContextFromUrl(url);
75+
76+
Assert.That(context.Path, Is.EqualTo(expectPath));
77+
}
78+
79+
[TestCase("https://github.com", null)]
80+
[TestCase("https://github.com/github", null)]
81+
[TestCase("https://github.com/github/VisualStudio", null)]
82+
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md", null)]
83+
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md#notices", null)]
84+
[TestCase("https://github.com/github/VisualStudio/blob/master/src/GitHub.VisualStudio/GitHubPackage.cs#L38", 38)]
85+
public void Line(string url, int? expectLine)
86+
{
87+
var target = new GitHubContextService();
88+
89+
var context = target.FindContextFromUrl(url);
90+
91+
Assert.That(context.Line, Is.EqualTo(expectLine));
92+
}
93+
94+
[TestCase("foo", true)]
95+
[TestCase("ssh://[email protected]:443/benstraub/libgit2", true)]
96+
[TestCase("https://github.com/github/VisualStudio", false)]
97+
public void IsNull(string url, bool expectNull)
98+
{
99+
var target = new GitHubContextService();
100+
101+
var context = target.FindContextFromUrl(url);
102+
103+
Assert.That(context, expectNull ? Is.Null : Is.Not.Null);
104+
}
105+
}
106+
107+
public class TheToMethod
108+
{
109+
[Test]
110+
public void DefaultGitHubDotCom()
111+
{
112+
var context = new GitHubContext { Host = "github.com", Owner = "github", RepositoryName = "VisualStudio" };
113+
var target = new GitHubContextService();
114+
115+
var uri = target.ToRepositoryUrl(context);
116+
117+
Assert.That(uri, Is.EqualTo(new Uri("https://github.com/github/VisualStudio")));
118+
}
46119
}
47120

48121
public class TheFindContextFromWindowTitleMethod

0 commit comments

Comments
 (0)