Skip to content

Commit

Permalink
Adds setting to control rich integrations - #1208
Browse files Browse the repository at this point in the history
  • Loading branch information
eamodio committed Dec 19, 2020
1 parent cb6e971 commit bf9208b
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 75 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Adds preview extensibility APIs
- Adds a preview _action runner_ extensibility point to provide a runner (handler) for a new _createPullRequest_ and _openPullRequest_ actions — see `gitlens.d.ts` for API definitions
- Preview APIs are only available in the Insiders edition
- Adds a `gitlens.integrations.enabled` setting to specify whether to enable rich integrations with any supported remote services — see [#1208](https://github.com/eamodio/vscode-gitlens/issues/1208)
- Adds a `gitlens.showWelcomeOnInstall` setting to specify whether to show the Welcome (Quick Setup) experience on first install — closes [#1049](https://github.com/eamodio/vscode-gitlens/issues/1049) thanks to [PR #1258](https://github.com/eamodio/vscode-gitlens/pull/1258) by Rickard ([@rickardp](https://github.com/rickardp))

### Changed
Expand Down
43 changes: 17 additions & 26 deletions README.md

Large diffs are not rendered by default.

38 changes: 21 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -909,9 +909,13 @@
"scope": "window"
},
"gitlens.insiders": {
"deprecationMessage": "Deprecated. Use the Insiders edition of GitLens instead",
"markdownDeprecationMessage": "Deprecated. Use the [Insiders edition](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens-insiders) of GitLens instead"
},
"gitlens.integrations.enabled": {
"type": "boolean",
"default": false,
"markdownDescription": "Specifies whether to enable experimental features",
"default": true,
"markdownDescription": "Specifies whether to enable rich integrations with any supported remote services",
"scope": "window"
},
"gitlens.keymap": {
Expand Down Expand Up @@ -3059,6 +3063,11 @@
"light": "images/light/icon-unplug.svg"
}
},
{
"command": "gitlens.resetRemoteConnectionAuthorization",
"title": "Reset Remote Connection Authorization",
"category": "GitLens"
},
{
"command": "gitlens.copyMessageToClipboard",
"title": "Copy Message",
Expand Down Expand Up @@ -4446,11 +4455,6 @@
"command": "gitlens.views.tags.setShowAvatarsOff",
"title": "Hide Avatars",
"category": "GitLens"
},
{
"command": "gitlens.resetRemoteConnectionAuthorization",
"title": "Reset Remote Connection Authorization",
"category": "GitLens"
}
],
"menus": {
Expand Down Expand Up @@ -4765,11 +4769,15 @@
},
{
"command": "gitlens.connectRemoteProvider",
"when": "false"
"when": "config.gitlens.integrations.enabled && gitlens:hasRichRemotes && !gitlens:hasConnectedRemotes"
},
{
"command": "gitlens.disconnectRemoteProvider",
"when": "false"
"when": "config.gitlens.integrations.enabled && gitlens:hasRichRemotes && gitlens:hasConnectedRemotes"
},
{
"command": "gitlens.resetRemoteConnectionAuthorization",
"when": "config.gitlens.integrations.enabled && gitlens:hasRichRemotes"
},
{
"command": "gitlens.copyMessageToClipboard",
Expand Down Expand Up @@ -5654,10 +5662,6 @@
{
"command": "gitlens.views.tags.setShowAvatarsOff",
"when": "false"
},
{
"command": "gitlens.resetRemoteConnectionAuthorization",
"when": "gitlens:hasRichRemotes"
}
],
"editor/context": [
Expand Down Expand Up @@ -7054,12 +7058,12 @@
},
{
"command": "gitlens.connectRemoteProvider",
"when": "viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+disconnected\\b)/",
"when": "config.gitlens.integrations.enabled && viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+disconnected\\b)/",
"group": "inline@98"
},
{
"command": "gitlens.disconnectRemoteProvider",
"when": "viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+connected\\b)/",
"when": "config.gitlens.integrations.enabled && viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+connected\\b)/",
"group": "inline@98"
},
{
Expand Down Expand Up @@ -7107,12 +7111,12 @@
},
{
"command": "gitlens.connectRemoteProvider",
"when": "viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+disconnected\\b)/",
"when": "config.gitlens.integrations.enabled && viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+disconnected\\b)/",
"group": "8_gitlens_actions@2"
},
{
"command": "gitlens.disconnectRemoteProvider",
"when": "viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+connected\\b)/",
"when": "config.gitlens.integrations.enabled && viewItem =~ /gitlens:remote\\b(?=.*?\\b\\+connected\\b)/",
"group": "8_gitlens_actions@2"
},
{
Expand Down
23 changes: 13 additions & 10 deletions src/avatars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ export function getAvatarUri(
query = getAvatarUriFromRemoteProvider(avatar, key, email, repoPathOrCommit, { size: size }).then(
uri => uri ?? avatar.uri ?? avatar.fallback!,
);
avatarQueue.set(key, query);
avatarQueue.set(
key,
query.finally(() => avatarQueue.delete(key)),
);
}

if (query != null) return query;
Expand Down Expand Up @@ -179,13 +182,15 @@ async function getAvatarUriFromRemoteProvider(

try {
let account;
// if (typeof repoPathOrCommit === 'string') {
// const remote = await Container.git.getRichRemoteProvider(repoPathOrCommit);
// account = await remote?.provider.getAccountForEmail(email, { avatarSize: size });
// } else {
if (typeof repoPathOrCommit !== 'string') {
const remote = await Container.git.getRichRemoteProvider(repoPathOrCommit.repoPath);
account = await remote?.provider.getAccountForCommit(repoPathOrCommit.ref, { avatarSize: size });
if (Container.config.integrations.enabled) {
// if (typeof repoPathOrCommit === 'string') {
// const remote = await Container.git.getRichRemoteProvider(repoPathOrCommit);
// account = await remote?.provider.getAccountForEmail(email, { avatarSize: size });
// } else {
if (typeof repoPathOrCommit !== 'string') {
const remote = await Container.git.getRichRemoteProvider(repoPathOrCommit.repoPath);
account = await remote?.provider.getAccountForCommit(repoPathOrCommit.ref, { avatarSize: size });
}
}
if (account == null) {
// If we have no account assume that won't change (without a reset), so set the timestamp to "never expire"
Expand Down Expand Up @@ -213,8 +218,6 @@ async function getAvatarUriFromRemoteProvider(
avatar.retries++;

return undefined;
} finally {
avatarQueue.delete(key);
}
}

Expand Down
89 changes: 80 additions & 9 deletions src/commands/remoteProviders.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';
import { GitCommit, GitRemote } from '../git/git';
import { GitCommit, GitRemote, Repository, RichRemoteProvider } from '../git/git';
import { command, Command, CommandContext, Commands, isCommandContextViewNodeHasRemote } from './common';
import { Container } from '../container';
import { RepositoryPicker } from '../quickpicks/repositoryPicker';
import { Iterables } from '../system';

export interface ConnectRemoteProviderCommandArgs {
remote: string;
Expand Down Expand Up @@ -39,14 +41,46 @@ export class ConnectRemoteProviderCommand extends Command {
}

async execute(args?: ConnectRemoteProviderCommandArgs): Promise<any> {
if (args?.repoPath == null || args?.remote == null) return false;
let remote: GitRemote<RichRemoteProvider> | undefined;
let remotes: GitRemote[] | undefined;
let repoPath;
if (args?.repoPath == null) {
const repos = new Map<Repository, GitRemote<RichRemoteProvider>>();

for (const repo of await Container.git.getOrderedRepositories()) {
const remote = await repo.getRichRemote();
if (remote?.provider != null && !(await remote.provider.isConnected())) {
repos.set(repo, remote);
}
}

if (repos.size === 0) return false;
if (repos.size === 1) {
let repo;
[repo, remote] = Iterables.first(repos);
repoPath = repo.path;
} else {
const pick = await RepositoryPicker.show('', '', [...repos.keys()]);
if (pick?.item == null) return undefined;

repoPath = pick.repoPath;
remote = repos.get(pick.item)!;
}
} else if (args?.remote == null) {
repoPath = args.repoPath;

remote = await Container.git.getRichRemoteProvider(repoPath, { includeDisconnected: true });
if (remote == null) return false;
} else {
repoPath = args.repoPath;

const remotes = await Container.git.getRemotes(args.repoPath);
const remote = remotes.find(r => r.id === args.remote);
if (!remote?.provider.hasApi()) return false;
remotes = await Container.git.getRemotes(repoPath);
remote = remotes.find(r => r.id === args.remote) as GitRemote<RichRemoteProvider> | undefined;
if (!remote?.provider.hasApi()) return false;
}

const connected = await remote.provider.connect();
if (connected && !remotes.some(r => r.default)) {
if (connected && !(remotes ?? (await Container.git.getRemotes(repoPath))).some(r => r.default)) {
await remote.setAsDefault(true);
}
return connected;
Expand Down Expand Up @@ -92,10 +126,47 @@ export class DisconnectRemoteProviderCommand extends Command {
}

async execute(args?: DisconnectRemoteProviderCommandArgs): Promise<any> {
if (args?.repoPath == null || args?.remote == null) return undefined;
let remote: GitRemote<RichRemoteProvider> | undefined;
let repoPath;
if (args?.repoPath == null) {
const repos = new Map<Repository, GitRemote<RichRemoteProvider>>();

for (const repo of await Container.git.getOrderedRepositories()) {
const remote = await repo.getRichRemote(true);
if (remote != null) {
repos.set(repo, remote);
}
}

if (repos.size === 0) return undefined;
if (repos.size === 1) {
let repo;
[repo, remote] = Iterables.first(repos);
repoPath = repo.path;
} else {
const pick = await RepositoryPicker.show(
'Choose which repository to disconnect the remote on',
undefined,
[...repos.keys()],
);
if (pick?.item == null) return undefined;

repoPath = pick.repoPath;
remote = repos.get(pick.item)!;
}
} else if (args?.remote == null) {
repoPath = args.repoPath;

remote = await Container.git.getRichRemoteProvider(repoPath, { includeDisconnected: false });
if (remote == null) return undefined;
} else {
repoPath = args.repoPath;

const remote = (await Container.git.getRemotes(args.repoPath)).find(r => r.id === args.remote);
if (!remote?.provider.hasApi()) return undefined;
remote = (await Container.git.getRemotes(repoPath)).find(r => r.id === args.remote) as
| GitRemote<RichRemoteProvider>
| undefined;
if (!remote?.provider.hasApi()) return undefined;
}

return remote.provider.disconnect();
}
Expand Down
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ export interface Config {
enabled: boolean;
};
};
insiders: boolean;
integrations: {
enabled: boolean;
};
keymap: KeyMap;
liveshare: {
allowGuestAccess: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/git/formatters/commitFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ export class CommitFormatter extends Formatter<GitCommit, CommitFormatOptions> {
}\n${GlyphChars.Dash.repeat(2)}\n${pr.title}\n${pr.state}, ${pr.formatDateFromNow()}")${separator}`;
} else if (pr instanceof Promises.CancellationError) {
commands += `[$(git-pull-request) PR $(sync~spin)](command:${Commands.RefreshHover} "Searching for a Pull Request (if any) that introduced this commit...")${separator}`;
} else if (pr.provider != null) {
} else if (pr.provider != null && Container.config.integrations.enabled) {
commands += `[$(plug) Connect to ${pr.provider.name}${
GlyphChars.Ellipsis
}](${ConnectRemoteProviderCommand.getMarkdownCommandArgs(pr)} "Connect to ${
Expand Down
3 changes: 2 additions & 1 deletion src/git/gitService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export class GitService implements Disposable {
resetAvatarCache('failed');
}
this._remotesWithApiProviderCache.clear();
void this.updateContext(this._repositoryTree);
}),
);
this.onConfigurationChanged(configuration.initializingChangeEvent);
Expand Down Expand Up @@ -450,7 +451,7 @@ export class GitService implements Disposable {
if (hasRepository) {
for (const repo of repositoryTree.values()) {
if (!hasConnectedRemotes) {
hasConnectedRemotes = await repo.hasConnectedRemote();
hasConnectedRemotes = await repo.hasRichRemote(true);

if (hasConnectedRemotes) {
hasRichRemotes = true;
Expand Down
15 changes: 6 additions & 9 deletions src/git/models/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,10 @@ export class Repository implements Disposable {
return this._remotes;
}

async getRichRemote(connectedOnly: boolean = false): Promise<GitRemote<RichRemoteProvider> | undefined> {
return Container.git.getRichRemoteProvider(await this.getRemotes(), { includeDisconnected: !connectedOnly });
}

private resetRemotesCache() {
this._remotes = undefined;
this._remotesDisposable?.dispose();
Expand Down Expand Up @@ -541,20 +545,13 @@ export class Repository implements Disposable {
return Container.git.getTags(this.path, options);
}

async hasConnectedRemote(): Promise<boolean> {
const remote = await Container.git.getRichRemoteProvider(await this.getRemotes());
return remote?.provider != null;
}

async hasRemotes(): Promise<boolean> {
const remotes = await this.getRemotes();
return remotes?.length > 0;
}

async hasRichRemote(): Promise<boolean> {
const remote = await Container.git.getRichRemoteProvider(await this.getRemotes(), {
includeDisconnected: true,
});
async hasRichRemote(connectedOnly: boolean = false): Promise<boolean> {
const remote = await this.getRichRemote(connectedOnly);
return remote?.provider != null;
}

Expand Down
2 changes: 2 additions & 0 deletions src/git/remotes/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@ export abstract class RichRemoteProvider extends RemoteProvider {
private async ensureSession(createIfNeeded: boolean): Promise<AuthenticationSession | undefined> {
if (this._session != null) return this._session;

if (!Container.config.integrations.enabled) return undefined;

if (createIfNeeded) {
await Promise.all([
Container.context.workspaceState.update(this.connectedKey, undefined),
Expand Down
4 changes: 3 additions & 1 deletion src/quickpicks/repositoryPicker.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
'use strict';
import { Disposable, window } from 'vscode';
import { Container } from '../container';
import { Repository } from '../git/git';
import { getQuickPickIgnoreFocusOut, RepositoryQuickPickItem } from '../quickpicks';
import { Iterables } from '../system';

export namespace RepositoryPicker {
export async function show(
title: string,
placeholder: string = 'Choose a repository',
repositories?: Repository[],
): Promise<RepositoryQuickPickItem | undefined> {
const items: RepositoryQuickPickItem[] = await Promise.all([
...Iterables.map(await Container.git.getOrderedRepositories(), r =>
...Iterables.map(repositories ?? (await Container.git.getOrderedRepositories()), r =>
RepositoryQuickPickItem.create(r, undefined, { branch: true, status: true }),
),
]);
Expand Down
1 change: 1 addition & 0 deletions src/views/remotesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export class RemotesView extends ViewBase<RemotesViewNode, RemotesViewConfig> {
!configuration.changed(e, 'defaultDateSource') &&
!configuration.changed(e, 'defaultDateStyle') &&
!configuration.changed(e, 'defaultGravatarsStyle') &&
!configuration.changed(e, 'integrations', 'enabled') &&
!configuration.changed(e, 'sortBranchesBy')
) {
return false;
Expand Down

0 comments on commit bf9208b

Please sign in to comment.