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

Open from clipboard url #1771

Merged
merged 69 commits into from
Jul 13, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
1de94ef
Add GitHub.OpenFromUrl command
jcansdale Jun 22, 2018
516128a
Update UriString to handle extended GitHub URLs
jcansdale Jun 22, 2018
7071b43
Support opening target from GitHub URL
jcansdale Jun 22, 2018
199a79f
Allow navigating to URL from clipboard
jcansdale Jun 25, 2018
134f6a8
Checkout or open using default path
jcansdale Jun 25, 2018
d5b833d
Don't open folder if current solution is inside it
jcansdale Jun 25, 2018
c097098
Add support for navigating to a PR URL
jcansdale Jun 25, 2018
12eb8d5
Open at line number from GitHub URL
jcansdale Jun 25, 2018
9399711
Add Open from GitHub command to File > Open
jcansdale Jun 25, 2018
89484dc
Merge branch 'master' into fixes/1757-open-from-url
jcansdale Jun 26, 2018
0aade59
Update UriString to allow http URLs with owner
jcansdale Jun 26, 2018
70d9d90
Find GitHub context from Chrome window title
jcansdale Jun 27, 2018
a82390f
Add support for opening from topmost browser
jcansdale Jun 27, 2018
c4cac4d
Support syncing the file open in topmost browser
jcansdale Jun 27, 2018
d1b0231
Ask user before cloning or opening a repository
jcansdale Jun 28, 2018
0d264d5
Factor GitHub URL parsing into FindContextFromUrl
jcansdale Jun 28, 2018
19249b0
Convert from GitHub to Visual Studio line numbers
jcansdale Jun 28, 2018
9b0fe66
Add support for file links that use a commit SHA
jcansdale Jun 28, 2018
7320235
Fix a few typos
jcansdale Jun 28, 2018
e900d7d
Update title regex to work with Firefox
jcansdale Jun 29, 2018
5019370
Detect repository home with no description
jcansdale Jul 2, 2018
90c0aa9
Use regex for matching line
jcansdale Jul 3, 2018
0bed9b2
Find line end from GitHub URL
jcansdale Jul 3, 2018
0aae4ca
Add support for navigating to a range of lines
jcansdale Jul 3, 2018
0fc10b7
Highlight line when single line is selected
jcansdale Jul 3, 2018
cfdf410
Select just the target lines
jcansdale Jul 3, 2018
55b1570
Stop the tuple madness
jcansdale Jul 3, 2018
cb7816c
Explicitly match CommitSha in blob URLs
jcansdale Jul 3, 2018
2243891
Add ResolvePath to resolve Treeish + BlobName
jcansdale Jul 3, 2018
39f3a54
Rename Treeish to TreeishPath
jcansdale Jul 4, 2018
d06ba40
First try opening in active repository
jcansdale Jul 4, 2018
d9ce6f5
Add separate `GitHub.OpenFromClipboard` command
jcansdale Jul 4, 2018
dbcb792
Simplify OpenFromClipboardCommand
jcansdale Jul 4, 2018
8e968cc
Show alert when no GitHub URL in clipboard
jcansdale Jul 4, 2018
b31b14f
Add ResolveGitObject to find local object from URL
jcansdale Jul 5, 2018
5b16030
Merge branch 'master' into fixes/1757-open-from-url
jcansdale Jul 5, 2018
92ffa39
Show message when there is no repo to navigate
jcansdale Jul 5, 2018
1f0f5fb
Return commitish and path from ResolveGitObject
jcansdale Jul 5, 2018
1d468d9
Let ResolveGitObject resolve commit without path
jcansdale Jul 5, 2018
5058564
Warn user when changes in local version of file
jcansdale Jul 5, 2018
bc780c4
If target blob has changed then show AnnotateFile
jcansdale Jul 6, 2018
c175d97
Don't throw when navigating to non-blob
jcansdale Jul 6, 2018
7784364
Rename ResolveGitObject to ResolveBlob
jcansdale Jul 6, 2018
6a3d8e4
Remove legacy ResolvePath method
jcansdale Jul 6, 2018
11866fb
Remove Open from GitHub... from File > Open menu
jcansdale Jul 9, 2018
c5c493d
Warn when user isn't in target repository
jcansdale Jul 9, 2018
36d6b0b
Merge branch 'master' into feature/open-from-clipboard-url
jcansdale Jul 9, 2018
3029ccd
Merge branch 'feature/open-from-clipboard-url' of https://github.com/…
jcansdale Jul 9, 2018
de5726f
Make ResolveBlob return if commitish is a SHA
jcansdale Jul 9, 2018
ab3e7e9
Change ResolveBlob to resolve remote refs
jcansdale Jul 9, 2018
19be672
Return commit SHA from ResolveBlob
jcansdale Jul 10, 2018
381806b
Add support for resolving tags
jcansdale Jul 10, 2018
f81fb36
Warn if URL owner is different to current owner
jcansdale Jul 10, 2018
e1bfa23
Allow repository names to differ in case
jcansdale Jul 10, 2018
fd0f835
Merge branch 'master' into feature/open-from-clipboard-url
jcansdale Jul 12, 2018
c429d76
Show dialog when attempting to open non-blob URL
jcansdale Jul 12, 2018
9ad3ba7
We don't support vpath in a URL
jcansdale Jul 12, 2018
81b6d8a
Lose the App form GitHub.App.Services
jcansdale Jul 12, 2018
f651b19
Clean up and xmldoc the AnnotateFile method
jcansdale Jul 12, 2018
f12ab09
Remove FindContextFromBrowser from hidden command
jcansdale Jul 12, 2018
057a77f
Add xmldocs for GitHubContext
jcansdale Jul 12, 2018
513b562
Add xmldocs for GitHubContextService
jcansdale Jul 12, 2018
b26b2a9
Merge branch 'master' into feature/open-from-clipboard-url
jcansdale Jul 13, 2018
83610bf
Only show command when in context of git repo
jcansdale Jul 13, 2018
bec5eca
Add more xmldocs
jcansdale Jul 13, 2018
ce68103
Lazy initialize the UIContext
jcansdale Jul 13, 2018
e88a3e4
Merge branch 'master' into feature/open-from-clipboard-url
grokys Jul 13, 2018
22ec886
Use <inheritdoc/> on members not classes!
jcansdale Jul 13, 2018
edbec49
Make <summary> xmldoc elements a single sentence
jcansdale Jul 13, 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
Prev Previous commit
Next Next commit
Factor GitHub URL parsing into FindContextFromUrl
OpenFromUrlCommand now works with GitHubContext objects rather than
directly with URLs.
  • Loading branch information
jcansdale committed Jun 28, 2018
commit 0d264d50c57d701fa62d202f481075a6c6dbdce8
5 changes: 4 additions & 1 deletion src/GitHub.App/Services/GitHubContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace GitHub.App.Services
using System;

namespace GitHub.App.Services
{
public class GitHubContext
{
Expand All @@ -9,5 +11,6 @@ public class GitHubContext
public int? PullRequest { get; set; }
public int? Issue { get; set; }
public string Path { get; set; }
public int? Line { get; set; }
}
}
83 changes: 79 additions & 4 deletions src/GitHub.App/Services/GitHubContextService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,27 @@ public class GitHubContextService
static readonly Regex windowTitlePathRegex = new Regex($"{path} at {branch} · {owner}/{repo} - ", RegexOptions.Compiled);
static readonly Regex windowTitleBranchesRegex = new Regex($"Branches · {owner}/{repo} - ", RegexOptions.Compiled);

public GitHubContext FindContextFromUrl(UriString url)
public GitHubContext FindContextFromUrl(string url)
{
var uri = new UriString(url);
if (!uri.IsValidUri)
{
return null;
}

if (!uri.IsHypertextTransferProtocol)
{
return null;
}

return new GitHubContext
{
Host = url.Host,
Owner = url.Owner,
RepositoryName = url.RepositoryName,
Host = uri.Host,
Owner = uri.Owner,
RepositoryName = uri.RepositoryName,
Path = FindSubPath(uri, "/blob/master/"),
PullRequest = FindPullRequest(uri),
Line = FindLine(uri)
};
}

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

public Uri ToRepositoryUrl(GitHubContext context)
{
var builder = new UriBuilder("https", context.Host ?? "github.com");
builder.Path = $"{context.Owner}/{context.RepositoryName}";
return builder.Uri;
}

public GitHubContext FindContextFromWindowTitle(string windowTitle)
{
var (success, owner, repo, branch, pullRequest, issue, path) = MatchWindowTitle(windowTitle);
Expand Down Expand Up @@ -130,6 +151,60 @@ public GitHubContext FindContextFromWindowTitle(string windowTitle)
return (match.Success, null, null, null, null, null, null);
}


static int? FindLine(UriString gitHubUrl)
{
var prefix = "#L";
var url = gitHubUrl.ToString();
var index = url.LastIndexOf(prefix);
if (index == -1)
{
return null;
}

if (!int.TryParse(url.Substring(index + prefix.Length), out int lineNumber))
{
return null;
}

return lineNumber; // 1 based
}

static int? FindPullRequest(UriString gitHubUrl)
{
var pullRequest = FindSubPath(gitHubUrl, "/pull/")?.Split('/').First();
if (pullRequest == null)
{
return null;
}

if (!int.TryParse(pullRequest, out int number))
{
return null;
}

return number;
}

static string FindSubPath(UriString gitHubUrl, string matchPath)
{
var url = gitHubUrl.ToString();
var prefix = gitHubUrl.ToRepositoryUrl() + matchPath;
if (!url.StartsWith(prefix))
{
return null;
}

var endIndex = url.IndexOf('#');
if (endIndex == -1)
{
endIndex = gitHubUrl.Length;
}

var path = url.Substring(prefix.Length, endIndex - prefix.Length);
return path;
}

static class User32
{
[DllImport("user32.dll", SetLastError = true)]
Expand Down
91 changes: 17 additions & 74 deletions src/GitHub.VisualStudio/Commands/OpenFromUrlCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.ComponentModel.Composition;
using GitHub.Commands;
using GitHub.Services;
using GitHub.Primitives;
using GitHub.App.Services;
using GitHub.Services.Vssdk.Commands;
using EnvDTE;
Expand Down Expand Up @@ -65,34 +64,20 @@ public override async Task Execute(string url)
url = Clipboard.GetText(TextDataFormat.Text);
}

var gitHubUrl = new UriString(url);
if (!gitHubUrl.IsValidUri || !gitHubUrl.IsHypertextTransferProtocol)
{
gitHubUrl = null;
}

GitHubContext context = null;
if (gitHubUrl == null)
{
// HACK: Reconstruct a GitHub URL from the topmost browser window
context = gitHubContextService.Value.FindContextFromBrowser();
if (context != null)
{
gitHubUrl = $"https://github.com/{context.Owner}/{context.RepositoryName}";
}
}
var context = gitHubContextService.Value.FindContextFromUrl(url);
context = context ?? gitHubContextService.Value.FindContextFromBrowser();

if (gitHubUrl == null)
if (context == null)
{
return;
}

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

var cloneUrl = gitHubUrl.ToRepositoryUrl().ToString();
var targetDir = Path.Combine(repositoryCloneService.Value.DefaultClonePath, defaultSubPath, gitHubUrl.Owner);
var repositoryDirName = gitHubUrl.RepositoryName;
var cloneUrl = gitHubContextService.Value.ToRepositoryUrl(context).ToString();
var targetDir = Path.Combine(repositoryCloneService.Value.DefaultClonePath, defaultSubPath, context.Owner);
var repositoryDirName = context.RepositoryName;
var repositoryDir = Path.Combine(targetDir, repositoryDirName);

if (!Directory.Exists(repositoryDir))
Expand Down Expand Up @@ -139,8 +124,8 @@ public override async Task Execute(string url)
}
}

await TryOpenPullRequest(gitHubUrl);
TryOpenFile(gitHubUrl, context, repositoryDir);
await TryOpenPullRequest(context);
TryOpenFile(context, repositoryDir);
}

VSConstants.MessageBoxResult ShowInfoMessage(string message)
Expand Down Expand Up @@ -180,9 +165,9 @@ static string FindSolutionDirectory(Solution solution)
return null;
}

bool TryOpenFile(UriString gitHubUrl, GitHubContext context, string repositoryDir)
bool TryOpenFile(GitHubContext context, string repositoryDir)
{
var path = FindSubPath(gitHubUrl, "/blob/master/") ?? context?.Path;
var path = context.Path;
if (path == null)
{
return false;
Expand All @@ -204,70 +189,28 @@ bool TryOpenFile(UriString gitHubUrl, GitHubContext context, string repositoryDi

dte.Value.ItemOperations.OpenFile(fullPath);

var lineNumber = FindLineNumber(gitHubUrl);
if (lineNumber != -1)
var lineNumber = context.Line;
if (lineNumber == null)
{
var activeView = pullRequestEditorService.Value.FindActiveView();
ErrorHandler.ThrowOnFailure(activeView.SetCaretPos(lineNumber, 0));
ErrorHandler.ThrowOnFailure(activeView.CenterLines(lineNumber, 1));
ErrorHandler.ThrowOnFailure(activeView.SetCaretPos(lineNumber.Value, 0));
ErrorHandler.ThrowOnFailure(activeView.CenterLines(lineNumber.Value, 1));
}

return true;
}

async Task<bool> TryOpenPullRequest(UriString gitHubUrl)
async Task<bool> TryOpenPullRequest(GitHubContext context)
{
var pullRequest = FindSubPath(gitHubUrl, "/pull/");
var pullRequest = context.PullRequest;
if (pullRequest == null)
{
return false;
}

if (!int.TryParse(pullRequest, out int number))
{
return false;
}

var host = await gitHubToolWindowManager.Value.ShowGitHubPane();
await host.ShowPullRequest(gitHubUrl.Owner, gitHubUrl.RepositoryName, number);
await host.ShowPullRequest(context.Owner, context.RepositoryName, pullRequest.Value);
return true;
}

static string FindSubPath(UriString gitHubUrl, string matchPath)
{
var url = gitHubUrl.ToString();
var prefix = gitHubUrl.ToRepositoryUrl() + matchPath;
if (!url.StartsWith(prefix))
{
return null;
}

var endIndex = url.IndexOf('#');
if (endIndex == -1)
{
endIndex = gitHubUrl.Length;
}

var path = url.Substring(prefix.Length, endIndex - prefix.Length);
return path;
}

static int FindLineNumber(UriString gitHubUrl)
{
var prefix = "#L";
var url = gitHubUrl.ToString();
var index = url.LastIndexOf(prefix);
if (index == -1)
{
return -1;
}

if (!int.TryParse(url.Substring(index + prefix.Length), out int lineNumber))
{
return -1;
}

return lineNumber - 1;
}
}
}
75 changes: 74 additions & 1 deletion test/GitHub.App.UnitTests/Services/GitHubContextServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GitHub.App.Services;
using System;
using GitHub.App.Services;
using NUnit.Framework;

public class GitHubContextServiceTests
Expand Down Expand Up @@ -43,6 +44,78 @@ public void Host(string url, string expectHost)

Assert.That(context.Host, Is.EqualTo(expectHost));
}

[TestCase("https://github.com", null)]
[TestCase("https://github.com/github", null)]
[TestCase("https://github.com/github/VisualStudio", null)]
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md", null)]
[TestCase("https://github.com/github/VisualStudio/pull/1763", 1763)]
[TestCase("https://github.com/github/VisualStudio/pull/1763/commits", 1763)]
[TestCase("https://github.com/github/VisualStudio/pull/1763/files#diff-7384294e6c288e13bad0293bae232754R1", 1763)]
[TestCase("https://github.com/github/VisualStudio/pull/NaN", null)]
public void PullRequest(string url, int? expectPullRequest)
{
var target = new GitHubContextService();

var context = target.FindContextFromUrl(url);

Assert.That(context?.PullRequest, Is.EqualTo(expectPullRequest));
}

[TestCase("https://github.com", null)]
[TestCase("https://github.com/github", null)]
[TestCase("https://github.com/github/VisualStudio", null)]
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md", "README.md")]
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md#notices", "README.md")]
public void Path(string url, string expectPath)
{
var target = new GitHubContextService();

var context = target.FindContextFromUrl(url);

Assert.That(context.Path, Is.EqualTo(expectPath));
}

[TestCase("https://github.com", null)]
[TestCase("https://github.com/github", null)]
[TestCase("https://github.com/github/VisualStudio", null)]
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md", null)]
[TestCase("https://github.com/github/VisualStudio/blob/master/README.md#notices", null)]
[TestCase("https://github.com/github/VisualStudio/blob/master/src/GitHub.VisualStudio/GitHubPackage.cs#L38", 38)]
public void Line(string url, int? expectLine)
{
var target = new GitHubContextService();

var context = target.FindContextFromUrl(url);

Assert.That(context.Line, Is.EqualTo(expectLine));
}

[TestCase("foo", true)]
[TestCase("ssh://[email protected]:443/benstraub/libgit2", true)]
[TestCase("https://github.com/github/VisualStudio", false)]
public void IsNull(string url, bool expectNull)
{
var target = new GitHubContextService();

var context = target.FindContextFromUrl(url);

Assert.That(context, expectNull ? Is.Null : Is.Not.Null);
}
}

public class TheToMethod
{
[Test]
public void DefaultGitHubDotCom()
{
var context = new GitHubContext { Host = "github.com", Owner = "github", RepositoryName = "VisualStudio" };
var target = new GitHubContextService();

var uri = target.ToRepositoryUrl(context);

Assert.That(uri, Is.EqualTo(new Uri("https://github.com/github/VisualStudio")));
}
}

public class TheFindContextFromWindowTitleMethod
Expand Down