diff --git a/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs b/BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs index 37d1172..c04c050 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_ServerType = "Konamiman.BrowseInRemoteGitRepo.ServerType"; + private readonly char[] newLineSeparators = new char[] {'\r', '\n'}; /// @@ -30,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). /// @@ -326,7 +414,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 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"); return; } @@ -357,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 = baseUrl + "/blob/" + branch + "/" + 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 b866a4e..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,6 +21,18 @@ 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 server type ### + +By default the server type is guessed based on the base URL and should only need to be overridden for a custom domain. + +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 ### 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.