Browse Source

Closes #2559 allows open pr w/o editor opened

main
Eric Amodio 1 year ago
parent
commit
8f5a203f96
5 changed files with 125 additions and 33 deletions
  1. +4
    -0
      CHANGELOG.md
  2. +1
    -1
      package.json
  3. +44
    -12
      src/commands/openAssociatedPullRequestOnRemote.ts
  4. +58
    -10
      src/quickpicks/repositoryPicker.ts
  5. +18
    -10
      src/system/array.ts

+ 4
- 0
CHANGELOG.md View File

@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
### Changed
- Changes _Open Associated Pull Request_ command to support opening associated pull requests with the current branch or the HEAD commit if no branch association was found — closes [#2559](https://github.com/gitkraken/vscode-gitlens/issues/2559)
## [13.4.0] - 2023-03-16
### Added

+ 1
- 1
package.json View File

@ -7976,7 +7976,7 @@
},
{
"command": "gitlens.openAssociatedPullRequestOnRemote",
"when": "gitlens:activeFileStatus =~ /blameable/ && gitlens:activeFileStatus =~ /remotes/"
"when": "gitlens:hasRemotes"
},
{
"command": "gitlens.openFileFromRemote",

+ 44
- 12
src/commands/openAssociatedPullRequestOnRemote.ts View File

@ -2,6 +2,7 @@ import type { TextEditor, Uri } from 'vscode';
import { Commands } from '../constants';
import type { Container } from '../container';
import { GitUri } from '../git/gitUri';
import { getRepositoryOrShowPicker } from '../quickpicks/repositoryPicker';
import { command, executeCommand } from '../system/command';
import { Logger } from '../system/logger';
import { ActiveEditorCommand, getCommandUri } from './base';
@ -14,27 +15,58 @@ export class OpenAssociatedPullRequestOnRemoteCommand extends ActiveEditorComman
}
async execute(editor?: TextEditor, uri?: Uri) {
if (editor == null) return;
uri = getCommandUri(uri, editor);
if (uri == null) return;
const gitUri = await GitUri.fromUri(uri);
const gitUri = uri != null ? await GitUri.fromUri(uri) : undefined;
if (editor != null && gitUri != null) {
const blameline = editor.selection.active.line;
if (blameline < 0) return;
try {
const blame = await this.container.git.getBlameForLine(gitUri, blameline);
if (blame == null) return;
const blameline = editor.selection.active.line;
if (blameline < 0) return;
await executeCommand<OpenPullRequestOnRemoteCommandArgs>(Commands.OpenPullRequestOnRemote, {
clipboard: false,
ref: blame.commit.sha,
repoPath: blame.commit.repoPath,
});
} catch (ex) {
Logger.error(ex, 'OpenAssociatedPullRequestOnRemoteCommand', `getBlameForLine(${blameline})`);
}
return;
}
try {
const blame = await this.container.git.getBlameForLine(gitUri, blameline);
if (blame == null) return;
const repo = await getRepositoryOrShowPicker('Open Pull Request Associated', undefined, {
filter: async r => (await this.container.git.getBestRemoteWithRichProvider(r.uri))?.provider != null,
});
if (repo == null) return;
const remote = await this.container.git.getBestRemoteWithRichProvider(repo.uri);
if (remote?.provider == null) return;
const branch = await repo.getBranch();
if (branch == null) return;
let pr = await this.container.git.getPullRequestForBranch(branch.ref, remote.provider);
if (pr == null) {
const commit = await repo.getCommit('HEAD');
if (commit == null) return;
pr = await this.container.git.getPullRequestForCommit(commit.ref, remote.provider);
if (pr == null) return;
}
await executeCommand<OpenPullRequestOnRemoteCommandArgs>(Commands.OpenPullRequestOnRemote, {
clipboard: false,
ref: blame.commit.sha,
repoPath: blame.commit.repoPath,
pr: {
url: pr.url,
},
});
} catch (ex) {
Logger.error(ex, 'OpenAssociatedPullRequestOnRemoteCommand', `getBlameForLine(${blameline})`);
Logger.error(ex, 'OpenAssociatedPullRequestOnRemoteCommand', 'No editor opened');
}
}
}

+ 58
- 10
src/quickpicks/repositoryPicker.ts View File

@ -2,6 +2,7 @@ import type { Disposable, TextEditor, Uri } from 'vscode';
import { window } from 'vscode';
import { Container } from '../container';
import type { Repository } from '../git/models/repository';
import { filterMapAsync } from '../system/array';
import { map } from '../system/iterable';
import { getQuickPickIgnoreFocusOut } from '../system/utils';
import { CommandQuickPickItem } from './items/common';
@ -12,11 +13,18 @@ export async function getBestRepositoryOrShowPicker(
uri: Uri | undefined,
editor: TextEditor | undefined,
title: string,
options?: { filter?: (r: Repository) => Promise<boolean> },
): Promise<Repository | undefined> {
const repository = Container.instance.git.getBestRepository(uri, editor);
let repository = Container.instance.git.getBestRepository(uri, editor);
if (repository != null && options?.filter != null) {
if (!(await options.filter(repository))) {
repository = undefined;
}
}
if (repository != null) return repository;
const pick = await showRepositoryPicker(title);
const pick = await showRepositoryPicker(title, undefined, options);
if (pick instanceof CommandQuickPickItem) {
await pick.execute();
return undefined;
@ -25,16 +33,26 @@ export async function getBestRepositoryOrShowPicker(
return pick?.item;
}
export async function getRepositoryOrShowPicker(title: string, uri?: Uri): Promise<Repository | undefined> {
export async function getRepositoryOrShowPicker(
title: string,
uri?: Uri,
options?: { filter?: (r: Repository) => Promise<boolean> },
): Promise<Repository | undefined> {
let repository;
if (uri == null) {
repository = Container.instance.git.highlander;
} else {
repository = await Container.instance.git.getOrOpenRepository(uri);
}
if (repository != null && options?.filter != null) {
if (!(await options.filter(repository))) {
repository = undefined;
}
}
if (repository != null) return repository;
const pick = await showRepositoryPicker(title);
const pick = await showRepositoryPicker(title, undefined, options);
if (pick instanceof CommandQuickPickItem) {
void (await pick.execute());
return undefined;
@ -45,14 +63,44 @@ export async function getRepositoryOrShowPicker(title: string, uri?: Uri): Promi
export async function showRepositoryPicker(
title: string | undefined,
placeholder: string = 'Choose a repository',
placeholder?: string,
repositories?: Repository[],
): Promise<RepositoryQuickPickItem | undefined>;
export async function showRepositoryPicker(
title: string | undefined,
placeholder?: string,
options?: { filter?: (r: Repository) => Promise<boolean> },
): Promise<RepositoryQuickPickItem | undefined>;
export async function showRepositoryPicker(
title: string | undefined,
placeholder: string = 'Choose a repository',
repositoriesOrOptions?: Repository[] | { filter?: (r: Repository) => Promise<boolean> },
): Promise<RepositoryQuickPickItem | undefined> {
const items = await Promise.all<Promise<RepositoryQuickPickItem>>([
...map(repositories ?? Container.instance.git.openRepositories, r =>
createRepositoryQuickPickItem(r, undefined, { branch: true, status: true }),
),
]);
if (
repositoriesOrOptions != null &&
!Array.isArray(repositoriesOrOptions) &&
repositoriesOrOptions.filter == null
) {
repositoriesOrOptions = undefined;
}
let items: RepositoryQuickPickItem[];
if (repositoriesOrOptions == null || Array.isArray(repositoriesOrOptions)) {
items = await Promise.all<Promise<RepositoryQuickPickItem>>([
...map(repositoriesOrOptions ?? Container.instance.git.openRepositories, r =>
createRepositoryQuickPickItem(r, undefined, { branch: true, status: true }),
),
]);
} else {
const { filter } = repositoriesOrOptions;
items = await filterMapAsync(Container.instance.git.openRepositories, async r =>
(await filter!(r))
? createRepositoryQuickPickItem(r, undefined, { branch: true, status: true })
: undefined,
);
}
if (items.length === 0) return undefined;
const quickpick = window.createQuickPick<RepositoryQuickPickItem>();
quickpick.ignoreFocusOut = getQuickPickIgnoreFocusOut();

+ 18
- 10
src/system/array.ts View File

@ -32,6 +32,16 @@ export function ensure(source: T | T[] | undefined): T[] | undefined {
return source == null ? undefined : Array.isArray(source) ? source : [source];
}
export async function filterAsync<T>(source: T[], predicate: (item: T) => Promise<boolean>): Promise<T[]> {
const filtered = [];
for (const item of source) {
if (await predicate(item)) {
filtered.push(item);
}
}
return filtered;
}
export function filterMap<T, TMapped>(
source: T[],
predicateMapper: (item: T, index: number) => TMapped | null | undefined,
@ -46,20 +56,18 @@ export function filterMap(
}, []);
}
export function filterMapAsync<T, TMapped>(
export async function filterMapAsync<T, TMapped>(
source: T[],
predicateMapper: (item: T, index: number) => Promise<TMapped | null | undefined>,
predicateMapper: (item: T) => Promise<TMapped | null | undefined>,
): Promise<TMapped[]> {
let index = 0;
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return source.reduce<any>(async (accumulator, current: T) => {
const mapped = await predicateMapper(current, index++);
const filteredAndMapped = [];
for (const item of source) {
const mapped = await predicateMapper(item);
if (mapped != null) {
accumulator.push(mapped);
filteredAndMapped.push(mapped);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return accumulator;
}, []);
}
return filteredAndMapped;
}
export function findLastIndex<T>(source: T[], predicate: (value: T, index: number, obj: T[]) => boolean): number {

Loading…
Cancel
Save