Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an extension-wide global config setting #4

Merged
merged 4 commits into from
Feb 25, 2024
Merged
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
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