Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
104 changes: 97 additions & 7 deletions BrowseInRemoteGitRepo/BrowseInRemoteGitRepoCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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'};

/// <summary>
Expand All @@ -30,6 +31,93 @@ internal sealed class BrowseInRemoteGitRepoCommand
public const int BrowseCommandId_se = 0x0102;
public const int CopyCommandId_se = 0x0103;

/// <summary>
/// Server Type Enum
/// </summary>
enum ServerType { GitHub, GitLab, BitbucketCloud, BitbucketServer }

/// <summary>
/// Maps the config string (configKey_ServerType) to the matching enum value
/// </summary>
/// <param name="serverType">Config string</param>
/// <returns></returns>
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;
}
}

/// <summary>
/// Attempts to guess the server type from the baseUrl
/// </summary>
/// <param name="baseUrl">Base url</param>
/// <returns></returns>
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;
}

/// <summary>
/// Returns the associated url pattern for the serverType
/// </summary>
/// <param name="serverType">Server type enum</param>
/// <returns></returns>
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}";
}
}

/// <summary>
/// Returns the associated line anchor for the serverType
/// </summary>
/// <param name="serverType">Server type enum</param>
/// <param name="startline">Start line</param>
/// <param name="endLine">End line, should equal startLine if single line is selected</param>
/// <returns></returns>
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 : "");
}
}

/// <summary>
/// Command menu group (command set GUID).
/// </summary>
Expand Down Expand Up @@ -326,7 +414,11 @@ private void MenuItemCallback(object sender, EventArgs e, Action<string> 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;
}
Expand Down Expand Up @@ -357,12 +449,10 @@ private void MenuItemCallback(object sender, EventArgs e, Action<string> 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 &&
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<remote base URL>/blob/<branch name>/<file path and name>`. When clicking in the code editor, `#L<line number>` is added as well; and if the command is invoked over a multiline selection, `-L<end line number>` 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.

Expand All @@ -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>`.

|server type|URL format|Line anchor format|
|-----------|----------|-----------|
|"GitHub" or "GitLab"|`<remote base URL>/blob/<branch name>/<file path and name>`|`#L<line number>[-L<end line number>]`|
|"BitbucketCloud"|`<remote base URL>/src/<branch name>/<file path and name>`|`#line-<line number>[:<end line number>]`|
|"BitbucketServer"|`<remote base URL>/browse/<file path and name>?at=refs/heads/<branch name>` |`#<line number>[<end line number>]`|

### 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 <command template>`, 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.
Expand Down