From 30b7e4219193ab747de1dcfa7e2ec7107f4e6288 Mon Sep 17 00:00:00 2001 From: Weston Renoud Date: Fri, 16 Feb 2018 15:56:27 +0100 Subject: [PATCH 1/2] configurable url pattern --- BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs | 11 ++++++++--- README.md | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs b/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs index 37d1172..b0a66de 100644 --- a/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs +++ b/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs @@ -19,7 +19,8 @@ internal sealed class BrowseInRemoteGitRepoCommand { const string configKey_BrowseCommandFormatString = "Konamiman.BrowseInRemoteGitRepo.BrowseCommandTemplate"; const string configKey_BaseUrl = "Konamiman.BrowseInRemoteGitRepo.BaseUrl"; - + const string configKey_UrlPattern = "Konamiman.BrowseInRemoteGitRepo.UrlPattern"; + private readonly char[] newLineSeparators = new char[] {'\r', '\n'}; /// @@ -326,7 +327,11 @@ private void MenuItemCallback(object sender, EventArgs e, Action process RunGitCommand($"config --get {configKey_BaseUrl}") ?? RunGitCommand("config --get remote.origin.url"); - if(baseUrl == null) { + var urlPattern = + RunGitCommand($"config --get {configKey_UrlPattern}") ?? + "{baseUrl}/blob/{branch}/{filepath}"; + + if (baseUrl == null) { Show($"There's no remote (remote.origin.url) nor manual base URL ({configKey_BaseUrl}) configured for this repository"); return; } @@ -357,7 +362,7 @@ private void MenuItemCallback(object sender, EventArgs e, Action process gittedFilename = RunGitCommand($"ls-files \"{gittedFilename}\"", baseLocalRepoRoot) .Split(newLineSeparators, StringSplitOptions.RemoveEmptyEntries)[0]; - var fullUrl = baseUrl + "/blob/" + branch + "/" + gittedFilename; + var fullUrl = urlPattern.Replace("{baseUrl}", baseUrl).Replace("{branch}", branch).Replace("{filepath}", gittedFilename); if (line != -1) { fullUrl += "#L" + (Math.Min(line, endLine) + 1); if (endLine != line) { diff --git a/README.md b/README.md index b866a4e..31f63ce 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ For the cases where this does not work as expected (the schema for the remote UR If the supplied value includes the token `{0}`, it will be replaced with the last part of the local repository directory name (so `xyz` for `c:\abc\def\xyz`). This may be useful if you have all your projects under the same account of the same provider and want to set a global value for the setting (add `--global` when creating the setting for this). +### Configuring the URL pattern ### + +By default the full remote URL is constructed with the following pattern `{baseUrl}/blob/{branch}/{filepath}` where `{baseUrl}` is substituted with the base URL as discussed, `{branch}` is substituted with the branch name, and `{filepath}` is substituted with the relative path and name of the file in the repository. + +This pattern can be overridden by executing `git config Konamiman.BrowseInRemoteGitRepo.UrlPattern `. + ### Configuring the command executed for browsing ### The default action of the _"Browse in remote repository"_ command is to open the full remote URL of the file in the default browser. If you want a different behavior (such as using a different browser or adding extra command line options) you can do so by executing `git config Konamiman.BrowseInRemoteGitRepo.BrowseCommandTemplate `, you must add a `{0}` token to the template that will be replaced with the remote URL of the file. Again, add `--global` if necessary. From 0e4bb6dc7a7a2015f255195493f61982b94ef5e3 Mon Sep 17 00:00:00 2001 From: Weston Renoud Date: Wed, 21 Aug 2019 12:48:09 +0200 Subject: [PATCH 2/2] Switch to using preconfigured url patterns with a server type option to select the appropriate pattern. This adds support for Bitbucket Cloud and Server. Server type is guessed based on baseUrl, if not overriden. It will need to be overriden for custom domains or Bitbucket Server. --- .../BrowseInRemoteGitRepoCommand.cs | 103 ++++++++++++++++-- README.md | 14 ++- 2 files changed, 104 insertions(+), 13 deletions(-) diff --git a/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs b/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs index b0a66de..c04c050 100644 --- a/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs +++ b/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs @@ -19,7 +19,7 @@ internal sealed class BrowseInRemoteGitRepoCommand { const string configKey_BrowseCommandFormatString = "Konamiman.BrowseInRemoteGitRepo.BrowseCommandTemplate"; const string configKey_BaseUrl = "Konamiman.BrowseInRemoteGitRepo.BaseUrl"; - const string configKey_UrlPattern = "Konamiman.BrowseInRemoteGitRepo.UrlPattern"; + const string configKey_ServerType = "Konamiman.BrowseInRemoteGitRepo.ServerType"; private readonly char[] newLineSeparators = new char[] {'\r', '\n'}; @@ -31,6 +31,93 @@ internal sealed class BrowseInRemoteGitRepoCommand public const int BrowseCommandId_se = 0x0102; public const int CopyCommandId_se = 0x0103; + /// + /// Server Type Enum + /// + enum ServerType { GitHub, GitLab, BitbucketCloud, BitbucketServer } + + /// + /// Maps the config string (configKey_ServerType) to the matching enum value + /// + /// Config string + /// + private ServerType? ServerTypeFromString(string serverType) + { + switch(serverType.ToLower()) + { + case "github": + return ServerType.GitHub; + case "gitlab": + return ServerType.GitLab; + case "bitbucketcloud": + return ServerType.BitbucketCloud; + case "bitbucketserver": + return ServerType.BitbucketServer; + default: + return null; + } + } + + /// + /// Attempts to guess the server type from the baseUrl + /// + /// Base url + /// + private ServerType GuessServerTypeFromBaseUrl(string baseUrl) + { + if(baseUrl.Contains("github.com")) + return ServerType.GitHub; + if(baseUrl.Contains("gitlab.com")) + return ServerType.GitLab; + if(baseUrl.Contains("bitbucket.org")) + return ServerType.BitbucketCloud; + + // default + return ServerType.GitHub; + } + + /// + /// Returns the associated url pattern for the serverType + /// + /// Server type enum + /// + private string ServerUrlPattern(ServerType serverType) + { + switch(serverType) + { + default: + case ServerType.GitHub: + case ServerType.GitLab: + return "{baseUrl}/blob/{branch}/{filePath}"; + case ServerType.BitbucketCloud: + return "{baseUrl}/src/{branch}/{filePath}"; + case ServerType.BitbucketServer: + return "{baseUrl}/browse/{filePath}?at=refs/heads/{branch}"; + } + } + + /// + /// Returns the associated line anchor for the serverType + /// + /// Server type enum + /// Start line + /// End line, should equal startLine if single line is selected + /// + private string ServerLinePattern(ServerType serverType, int startLine, int endLine) + { + switch(serverType) + { + default: + case ServerType.GitHub: + case ServerType.GitLab: + return "#L" + startLine + ((startLine != endLine) ? "-L" + endLine : ""); + case ServerType.BitbucketCloud: + return "#lines-" + startLine + ((startLine != endLine) ? ":" + endLine : ""); + case ServerType.BitbucketServer: + return "#" + startLine + ((startLine != endLine) ? "-" + endLine : ""); + } + } + /// /// Command menu group (command set GUID). /// @@ -327,9 +414,9 @@ private void MenuItemCallback(object sender, EventArgs e, Action process RunGitCommand($"config --get {configKey_BaseUrl}") ?? RunGitCommand("config --get remote.origin.url"); - var urlPattern = - RunGitCommand($"config --get {configKey_UrlPattern}") ?? - "{baseUrl}/blob/{branch}/{filepath}"; + var serverType = + ServerTypeFromString(RunGitCommand($"config --get {configKey_ServerType}")) ?? + GuessServerTypeFromBaseUrl(baseUrl); if (baseUrl == null) { Show($"There's no remote (remote.origin.url) nor manual base URL ({configKey_BaseUrl}) configured for this repository"); @@ -362,12 +449,10 @@ private void MenuItemCallback(object sender, EventArgs e, Action process gittedFilename = RunGitCommand($"ls-files \"{gittedFilename}\"", baseLocalRepoRoot) .Split(newLineSeparators, StringSplitOptions.RemoveEmptyEntries)[0]; - var fullUrl = urlPattern.Replace("{baseUrl}", baseUrl).Replace("{branch}", branch).Replace("{filepath}", gittedFilename); + var fullUrl = ServerUrlPattern(serverType).Replace("{baseUrl}", baseUrl).Replace("{branch}", branch).Replace("{filePath}", gittedFilename); + if (line != -1) { - fullUrl += "#L" + (Math.Min(line, endLine) + 1); - if (endLine != line) { - fullUrl += "-L" + (Math.Max(line, endLine) + 1); - } + fullUrl += ServerLinePattern(serverType, Math.Min(line, endLine) + 1, Math.Max(line, endLine) + 1); } if(validateUrl && diff --git a/README.md b/README.md index 31f63ce..a750565 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is a simple Visual Studio extension that adds two entries, _"Browse in remote repository"_ and _"Copy URL of remote repository version"_, to the context menu when right clicking a file in solution Explorer, or in the code editor for open files, provided that the solution lives in a Git repository with a configured remote. This is useful when you are coding collaboratively and want to point a specific piece of code to another person. -The format of the generated URL is `/blob//`. When clicking in the code editor, `#L` is added as well; and if the command is invoked over a multiline selection, `-L` is added to that. This format is compatible with [GitHub](http://github.com) and [GitLab](http://gitlab.com). +Supports [GitHub](http://github.com), [GitLab](http://gitlab.com), [Bitbucket Cloud](https://bitbucket.org/), and [Bitbucket Server](https://www.atlassian.com/software/bitbucket/enterprise). See server type configuration for associated URL formats **Note:** You need Visual Studio 2017 to open the solution. @@ -21,11 +21,17 @@ For the cases where this does not work as expected (the schema for the remote UR If the supplied value includes the token `{0}`, it will be replaced with the last part of the local repository directory name (so `xyz` for `c:\abc\def\xyz`). This may be useful if you have all your projects under the same account of the same provider and want to set a global value for the setting (add `--global` when creating the setting for this). -### Configuring the URL pattern ### +### Configuring the server type ### -By default the full remote URL is constructed with the following pattern `{baseUrl}/blob/{branch}/{filepath}` where `{baseUrl}` is substituted with the base URL as discussed, `{branch}` is substituted with the branch name, and `{filepath}` is substituted with the relative path and name of the file in the repository. +By default the server type is guessed based on the base URL and should only need to be overridden for a custom domain. -This pattern can be overridden by executing `git config Konamiman.BrowseInRemoteGitRepo.UrlPattern `. +The server type can be overridden by executing `git config Konamiman.BrowseInRemoteGitRepo.ServerType `. + +|server type|URL format|Line anchor format| +|-----------|----------|-----------| +|"GitHub" or "GitLab"|`/blob//`|`#L[-L]`| +|"BitbucketCloud"|`/src//`|`#line-[:]`| +|"BitbucketServer"|`/browse/?at=refs/heads/` |`#[]`| ### Configuring the command executed for browsing ###