Skip to content

Commit

Permalink
Merge pull request #4 from andrei-m/global-config
Browse files Browse the repository at this point in the history
Add an extension-wide global config setting
  • Loading branch information
andrei-m committed Feb 25, 2024
2 parents e1eca16 + ec9b84b commit 90a604a
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 41 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ All notable changes to the "open-in-browser" extension will be documented in thi
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.0.2] - 2024-02-24

### Added

- An extension-wide setting for the web platform. This is a convenience to avoid configuring many per-repo settings for repos that would otherwise be unrecognized.
- Notifications for common error modes such as editing files that are not managed in Git

### Changed

- Web platform resolution based on the remote URL's domain is now case-insensitive.

## [0.0.1] - 2024-02-09

Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,28 @@ The following remote.config.url-platform options are supported:
* gitlab
* stash

An extension-wide setting can also be configured as a default for all repos. The priority order for remote web platform resolution is:

1. The `remote.origin.url-platform` setting described above.
2. Well-known domains guessed from the remote host (github.com, etc).
3. The extension-wide setting.

## Requirements

VSCode 1.85.0 or newer

## Release Notes

### 0.0.1 (Latest)
### 0.0.2 (Latest)

#### Added

- An extension-wide setting for the web platform. This is a convenience to avoid configuring many per-repo settings for repos that would otherwise be unrecognized.
- Notifications for common error modes such as editing files that are not managed in Git

Initial release:
#### Changed

* Github, Gitlab, Stash, and Azure DevOps support
- Web platform resolution based on the remote URL's domain is now case-insensitive.

See [CHANGELOG.md](CHANGELOG.md) for release history and work-in-progress.

Expand Down
26 changes: 25 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,31 @@
"command": "open-in-browser.open",
"title": "Open in Browser"
}
]
],
"configuration": {
"title": "Git Open in Browser",
"properties": {
"git-open-in-browser.default-url-platform": {
"type": "string",
"default": "unset",
"description": "Git web platform default to use for unrecognized remote URIs",
"enum": [
"unset",
"ado",
"github",
"gitlab",
"stash"
],
"enumDescriptions": [
"Unset; unrecognized URLs will not open",
"Azure DevOps",
"Github",
"Gitlab",
"Atlassian Stash"
]
}
}
}
},
"scripts": {
"vscode:prepublish": "npm run compile",
Expand Down
10 changes: 10 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as vscode from 'vscode';
import { MaybeUrlPlatform, parseMaybeUrlPlatform } from './git';

export function getDefaultUrlPlatform(): MaybeUrlPlatform {
const defaultUrlPlatform = vscode.workspace.getConfiguration('git-open-in-browser').get('default-url-platform');
if (typeof defaultUrlPlatform === "string") {
return parseMaybeUrlPlatform(defaultUrlPlatform);
}
return null;
}
5 changes: 5 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import * as editor from './editor';
import * as git from './git';
import { open } from './open';
import { notify } from './log';

export function activate(context: vscode.ExtensionContext) {
const disposable = vscode.commands.registerCommand('open-in-browser.open', () => {
Expand All @@ -10,8 +11,12 @@ export function activate(context: vscode.ExtensionContext) {
git.getGitInfo().then(simpleGitInfo => {
if (simpleGitInfo) {
open(simpleGitInfo, editorInfo);
} else {
notify("Could not find git info or recognize the remote web platform; see logs in 'Output.'")
}
});
} else {
notify('No file editor found.');
}
});

Expand Down
68 changes: 55 additions & 13 deletions src/git.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as vscode from 'vscode';
import { simpleGit, SimpleGit, CleanOptions } from 'simple-git';
import GitUrlParse from 'git-url-parse';
import { log } from './log';
import { getDefaultUrlPlatform } from './config';

export class GitInfo {
url: UrlParsed
commitHash: string
urlPlatform: MaybeUrlPlatform
constructor(url: UrlParsed, commitHash: string, urlPlatform: MaybeUrlPlatform) {
urlPlatform: UrlPlatform
constructor(url: UrlParsed, commitHash: string, urlPlatform: UrlPlatform) {
this.url = url;
this.commitHash = commitHash;
this.urlPlatform = urlPlatform;
Expand All @@ -27,21 +29,31 @@ export class UrlParsed {
export async function getGitInfo(): Promise<MaybeGitInfo> {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders === undefined || workspaceFolders.length < 1) {
log('No workspace folders found');
return null;
}
const folderPath = workspaceFolders[0].uri.fsPath;
const git: SimpleGit = simpleGit(folderPath).clean(CleanOptions.FORCE);

const remotes = await git.getRemotes(true);
if (remotes.length < 1) {
log('No git remotes found');
return null;
}
const remoteUrl = remotes[0].refs.fetch;
const commitHash = await git.revparse(['HEAD']);
const urlPlatform = await getUrlPlatform(git, remotes[0].name)
const parsed = GitUrlParse(remoteUrl);
log(`using remote ${remoteUrl} and revision ${commitHash}`)
const url = GitUrlParse(remoteUrl);

return new GitInfo(parsed, commitHash, urlPlatform);
const extensionConfPlatform = getDefaultUrlPlatform();
const gitConfPlatform = await getConfiguredUrlPlatform(git, remotes[0].name)
const resolvedUrlPlatform = resolveUrlPlatform(gitConfPlatform, url, extensionConfPlatform);
if (resolvedUrlPlatform === null) {
return null;
}
log(`resolved URL platform: ${UrlPlatform[resolvedUrlPlatform]}`);

return new GitInfo(url, commitHash, resolvedUrlPlatform);
}

export enum UrlPlatform {
Expand All @@ -53,14 +65,8 @@ export enum UrlPlatform {

export type MaybeUrlPlatform = UrlPlatform | null;

async function getUrlPlatform(git: SimpleGit, remoteName: string): Promise<MaybeUrlPlatform> {
const urlPlatformKey = `remote.${remoteName}.url-platform`;
const urlPlatform = await git.getConfig(urlPlatformKey);
if (!urlPlatform.value) {
return null;
}

switch (urlPlatform.value.toLowerCase()) {
export function parseMaybeUrlPlatform(raw: string) {
switch (raw.toLowerCase()) {
case 'github':
return UrlPlatform.Github;
case 'gitlab':
Expand All @@ -69,7 +75,43 @@ async function getUrlPlatform(git: SimpleGit, remoteName: string): Promise<Maybe
return UrlPlatform.Stash;
case 'ado':
return UrlPlatform.AzureDevOps;
case 'unset':
return null;
}
return null;
}

async function getConfiguredUrlPlatform(git: SimpleGit, remoteName: string): Promise<MaybeUrlPlatform> {
const urlPlatformKey = `remote.${remoteName}.url-platform`;
const urlPlatform = await git.getConfig(urlPlatformKey);
if (!urlPlatform.value) {
return null;
}
return parseMaybeUrlPlatform(urlPlatform.value);
}

/*
Attempt to resolve the web platform using the following priorty order:
1. Git remote.$remoteName.url-platform configuration
2. Well-known domains (github.com, gitlab.com, etc.)
3. Extension-wide default setting
*/
export function resolveUrlPlatform(gitConfPlatform: MaybeUrlPlatform, url: UrlParsed, extensionConfPlatform: MaybeUrlPlatform): MaybeUrlPlatform {
if (gitConfPlatform) {
return gitConfPlatform;
}
const host = url.resource.toLowerCase();
if (host === 'github.com') {
return UrlPlatform.Github;
}
if (host === 'gitlab.com') {
return UrlPlatform.Gitlab;
}
if (host.endsWith('azure.com')) {
return UrlPlatform.AzureDevOps;
}
if (extensionConfPlatform) {
return extensionConfPlatform;
}
return null;
}
11 changes: 11 additions & 0 deletions src/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as vscode from 'vscode';

const out = vscode.window.createOutputChannel("Git Open in Browser");

export function log(msg: string) {
out.appendLine(msg);
}

export function notify(msg: string) {
vscode.window.showInformationMessage(msg);
}
29 changes: 14 additions & 15 deletions src/open.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import * as vscode from 'vscode';
import { Selection } from './editor';
import { GitInfo, MaybeUrlPlatform, UrlParsed, UrlPlatform } from './git';
import { GitInfo, UrlParsed, UrlPlatform } from './git';
import { notify } from './log';

export function open(gitInfo: GitInfo, selection: Selection ) {
const url = gitUrlToWebUrl(gitInfo.url, gitInfo.urlPlatform, gitInfo.commitHash, selection);
if (url) {
vscode.env.openExternal(vscode.Uri.parse(url));
} else {
notify(`Unrecognized web platform for ${gitInfo.url.resource}`);
}
}

export function gitUrlToWebUrl(url: UrlParsed, urlPlatform: MaybeUrlPlatform, commitHash: string, selection: Selection): string | null {
const host = url.resource;
if (host === 'github.com' || urlPlatform === UrlPlatform.Github) {
return githubUrlToWebUrl(url, commitHash, selection);
}
if (host === 'gitlab.com' || urlPlatform === UrlPlatform.Gitlab) {
return gitlabUrlToWebUrl(url, commitHash, selection);
}
if (urlPlatform === UrlPlatform.Stash) {
return stashUrlToWebUrl(url, commitHash, selection);
}
if (host.endsWith('azure.com') || urlPlatform === UrlPlatform.AzureDevOps) {
return azureDevopsUrlToWebUrl(url, commitHash, selection);
export function gitUrlToWebUrl(url: UrlParsed, urlPlatform: UrlPlatform, commitHash: string, selection: Selection): string {
switch (urlPlatform) {
case UrlPlatform.Github:
return githubUrlToWebUrl(url, commitHash, selection);
case UrlPlatform.Gitlab:
return gitlabUrlToWebUrl(url, commitHash, selection);
case UrlPlatform.Stash:
return stashUrlToWebUrl(url, commitHash, selection);
case UrlPlatform.AzureDevOps:
return azureDevopsUrlToWebUrl(url, commitHash, selection);
}
return null;
}

function githubUrlToWebUrl(url: UrlParsed, commitHash: string, selection: Selection): string {
Expand Down
58 changes: 58 additions & 0 deletions src/test/git.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as assert from 'assert';
import { UrlParsed, UrlPlatform, resolveUrlPlatform } from '../git';

suite('resolveUrlPlatform', () => {
test('git-configured setting is highest priority', () => {
const gitConfigured = UrlPlatform.AzureDevOps;
const url = new UrlParsed('github.com', '');
const extConfigured = UrlPlatform.Gitlab;

const result = resolveUrlPlatform(gitConfigured, url, extConfigured);
assert.strictEqual(result, UrlPlatform.AzureDevOps);
});

test('well-known domain: Github', () => {
const gitConfigured = null;
const url = new UrlParsed('github.com', '');
const extConfigured = UrlPlatform.Gitlab;

const result = resolveUrlPlatform(gitConfigured, url, extConfigured);
assert.strictEqual(result, UrlPlatform.Github);
});

test('well-known domain: Gitlab', () => {
const gitConfigured = null;
const url = new UrlParsed('gitlab.com', '');
const extConfigured = UrlPlatform.Stash;

const result = resolveUrlPlatform(gitConfigured, url, extConfigured);
assert.strictEqual(result, UrlPlatform.Gitlab);
});

test('well-known domain: Azure for Azure DevOps', () => {
const gitConfigured = null;
const url = new UrlParsed('azure.com', '');
const extConfigured = UrlPlatform.Stash;

const result = resolveUrlPlatform(gitConfigured, url, extConfigured);
assert.strictEqual(result, UrlPlatform.AzureDevOps);
});

test('domains are case-insentive', () => {
const gitConfigured = null;
const url = new UrlParsed('AzuRE.com', '');
const extConfigured = UrlPlatform.Stash;

const result = resolveUrlPlatform(gitConfigured, url, extConfigured);
assert.strictEqual(result, UrlPlatform.AzureDevOps);
});

test('fall back to the extension-configured default', () => {
const gitConfigured = null;
const url = new UrlParsed('example.org', '');
const extConfigured = UrlPlatform.Stash;

const result = resolveUrlPlatform(gitConfigured, url, extConfigured);
assert.strictEqual(result, UrlPlatform.Stash);
});
});
Loading

0 comments on commit 90a604a

Please sign in to comment.