Skip to content
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
5 changes: 5 additions & 0 deletions contributions.json
Original file line number Diff line number Diff line change
Expand Up @@ -4804,6 +4804,11 @@
]
}
},
"gitlens.views.addPullRequestRemote": {
"label": "Add Pull Request Remote",
"icon": "$(add)",
"enablement": "!operationInProgress"
},
"gitlens.views.addRemote": {
"label": "Add Remote...",
"icon": "$(add)",
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7629,6 +7629,12 @@
"title": "Add Co-authors...",
"icon": "$(person-add)"
},
{
"command": "gitlens.views.addPullRequestRemote",
"title": "Add Pull Request Remote",
"icon": "$(add)",
"enablement": "!operationInProgress"
},
{
"command": "gitlens.views.addRemote",
"title": "Add Remote...",
Expand Down Expand Up @@ -11330,6 +11336,10 @@
"command": "gitlens.views.addAuthors",
"when": "false"
},
{
"command": "gitlens.views.addPullRequestRemote",
"when": "false"
},
{
"command": "gitlens.views.addRemote",
"when": "false"
Expand Down
1 change: 1 addition & 0 deletions src/constants.commands.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export type ContributedCommands =
| 'gitlens.views.addAuthor'
| 'gitlens.views.addAuthor.multi'
| 'gitlens.views.addAuthors'
| 'gitlens.views.addPullRequestRemote'
| 'gitlens.views.addRemote'
| 'gitlens.views.applyChanges'
| 'gitlens.views.associateIssueWithBranch'
Expand Down
3 changes: 3 additions & 0 deletions src/constants.commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ type InternalPlusCommands =
| 'gitlens.plus.showPlans'
| 'gitlens.plus.validate';

type InternalPullRequestViewCommands = 'gitlens.views.addPullRequestRemote';

type InternalScmGroupedViewCommands =
| 'gitlens.views.scm.grouped.welcome.dismiss'
| 'gitlens.views.scm.grouped.welcome.restore';
Expand Down Expand Up @@ -221,6 +223,7 @@ type InternalGlCommands =
| InternalHomeWebviewViewCommands
| InternalLaunchPadCommands
| InternalPlusCommands
| InternalPullRequestViewCommands
| InternalScmGroupedViewCommands
| InternalSearchAndCompareViewCommands
| InternalTimelineWebviewViewCommands
Expand Down
34 changes: 18 additions & 16 deletions src/git/utils/-webview/pullRequest.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { createRevisionRange } from '../revision.utils';
export async function ensurePullRequestRefs(
pr: PullRequest,
repo: Repository,
options?: { promptMessage?: string },
options?: { silent?: true; promptMessage?: never } | { silent?: never; promptMessage?: string },
refs?: PullRequestComparisonRefs,
): Promise<LeftRightCommitCountResult | undefined> {
if (pr.refs == null) return undefined;
Expand All @@ -32,7 +32,7 @@ export async function ensurePullRequestRefs(
export async function ensurePullRequestRemote(
pr: PullRequest,
repo: Repository,
options?: { promptMessage?: string },
options?: { silent?: true; promptMessage?: never } | { silent?: never; promptMessage?: string },
): Promise<boolean> {
const identity = getRepositoryIdentityForPullRequest(pr);
if (identity.remote.url == null) return false;
Expand All @@ -51,20 +51,22 @@ export async function ensurePullRequestRemote(

const confirm = { title: 'Add Remote' };
const cancel = { title: 'Cancel', isCloseAffordance: true };
const result = await window.showInformationMessage(
`${
options?.promptMessage ?? `Unable to find a remote for PR #${pr.id}.`
}\nWould you like to add a remote for '${identity.provider.repoDomain}?`,
{ modal: true },
confirm,
cancel,
);

if (result === confirm) {
await repo.git
.remotes()
.addRemoteWithResult?.(identity.provider.repoDomain, identity.remote.url, { fetch: true });
return true;
if (!options?.silent) {
const result = await window.showInformationMessage(
`${
options?.promptMessage ?? `Unable to find a remote for PR #${pr.id}.`
}\nWould you like to add a remote for '${identity.provider.repoDomain}?`,
{ modal: true },
confirm,
cancel,
);

if (result === confirm) {
await repo.git
.remotes()
.addRemoteWithResult?.(identity.provider.repoDomain, identity.remote.url, { fetch: true });
return true;
}
}

return false;
Expand Down
18 changes: 11 additions & 7 deletions src/plus/launchpad/launchpadProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export type LaunchpadItem = LaunchpadPullRequest & {

export type OpenRepository = {
repo: Repository;
remote: GitRemote;
remote?: GitRemote;
localBranch?: GitBranch;
};

Expand Down Expand Up @@ -551,12 +551,16 @@ export class LaunchpadProvider implements Disposable {
): Promise<OpenRepository | undefined> {
if (pr.repoIdentity.remote.url == null) return undefined;

const match =
matchingRemoteMap.get(pr.repoIdentity.remote.url) ??
(pr.underlyingPullRequest?.refs?.base?.url
? matchingRemoteMap.get(pr.underlyingPullRequest.refs.base.url)
: undefined);
if (match == null) return undefined;
let match = matchingRemoteMap.get(pr.repoIdentity.remote.url);
if (match == null) {
if (pr.underlyingPullRequest?.refs?.base?.url == null) return undefined;

match = matchingRemoteMap.get(pr.underlyingPullRequest.refs.base.url);
if (match == null) return undefined;

const [repo] = match;
return { repo: repo };
}

const [repo, remote] = match;

Expand Down
4 changes: 3 additions & 1 deletion src/views/nodes/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ export class CommandMessageNode extends MessageNode {
description?: string,
tooltip?: string,
iconPath?: TreeItem['iconPath'],
contextValue?: string,
resourceUri?: Uri,
) {
super(view, parent, message, description, tooltip, iconPath);
super(view, parent, message, description, tooltip, iconPath, contextValue, resourceUri);
}

override getTreeItem(): TreeItem | Promise<TreeItem> {
Expand Down
43 changes: 39 additions & 4 deletions src/views/nodes/pullRequestNode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MarkdownString, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { MarkdownString, ThemeColor, ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode';
import type { Colors } from '../../constants.colors';
import { GitUri } from '../../git/gitUri';
import { GitBranch } from '../../git/models/branch';
import type { GitCommit } from '../../git/models/commit';
Expand All @@ -7,16 +8,25 @@ import type { GitBranchReference } from '../../git/models/reference';
import type { Repository } from '../../git/models/repository';
import { getAheadBehindFilesQuery, getCommitsQuery } from '../../git/queryResults';
import { getIssueOrPullRequestMarkdownIcon, getIssueOrPullRequestThemeIcon } from '../../git/utils/-webview/icons';
import { ensurePullRequestRefs, getOrOpenPullRequestRepository } from '../../git/utils/-webview/pullRequest.utils';
import { getComparisonRefsForPullRequest } from '../../git/utils/pullRequest.utils';
import {
ensurePullRequestRefs,
ensurePullRequestRemote,
getOrOpenPullRequestRepository,
} from '../../git/utils/-webview/pullRequest.utils';
import {
getComparisonRefsForPullRequest,
getRepositoryIdentityForPullRequest,
} from '../../git/utils/pullRequest.utils';
import { createRevisionRange } from '../../git/utils/revision.utils';
import { createCommand } from '../../system/-webview/command';
import { pluralize } from '../../system/string';
import type { ViewsWithCommits } from '../viewBase';
import { createViewDecorationUri } from '../viewDecorationProvider';
import { CacheableChildrenViewNode } from './abstract/cacheableChildrenViewNode';
import type { ClipboardType, ViewNode } from './abstract/viewNode';
import { ContextValues, getViewNodeId } from './abstract/viewNode';
import { CodeSuggestionsNode } from './codeSuggestionsNode';
import { MessageNode } from './common';
import { CommandMessageNode, MessageNode } from './common';
import { ResultsCommitsNode } from './resultsCommitsNode';
import { ResultsFilesNode } from './resultsFilesNode';

Expand Down Expand Up @@ -159,6 +169,31 @@ export async function getPullRequestChildren(

const repoPath = repo.path;
const refs = getComparisonRefsForPullRequest(repoPath, pullRequest.refs!);
const identity = getRepositoryIdentityForPullRequest(pullRequest);
if (!(await ensurePullRequestRemote(pullRequest, repo, { silent: true }))) {
return [
new CommandMessageNode(
view,
parent,
createCommand<[ViewNode, PullRequest, Repository]>(
'gitlens.views.addPullRequestRemote',
'Add Pull Request Remote...',
parent,
pullRequest,
repo,
),
`Unable to find a remote for '${identity.provider.repoDomain}'`,
undefined,
`Click to add a remote for '${identity.provider.repoDomain}'`,
new ThemeIcon(
'question',
new ThemeColor('gitlens.decorations.workspaceRepoMissingForegroundColor' satisfies Colors),
),
undefined,
createViewDecorationUri('remote', { state: 'missing' }),
),
];
}

const counts = await ensurePullRequestRefs(
pullRequest,
Expand Down
2 changes: 1 addition & 1 deletion src/views/nodes/remoteNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class RemoteNode extends ViewNode<'remote', ViewsWithRemotes> {
if (this.remote.default) {
item.contextValue += '+default';
}
item.resourceUri = createViewDecorationUri('remote', { default: this.remote.default });
item.resourceUri = createViewDecorationUri('remote', { state: this.remote.default ? 'default' : undefined });

for (const { type, url } of this.remote.urls) {
tooltip += `\\\n${url} (${type})`;
Expand Down
13 changes: 13 additions & 0 deletions src/views/viewCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import * as StashActions from '../git/actions/stash';
import * as TagActions from '../git/actions/tag';
import * as WorktreeActions from '../git/actions/worktree';
import { GitUri } from '../git/gitUri';
import type { PullRequest } from '../git/models/pullRequest';
import { RemoteResourceType } from '../git/models/remoteResource';
import type { Repository } from '../git/models/repository';
import { deletedOrMissing } from '../git/models/revision';
import {
ensurePullRequestRefs,
Expand Down Expand Up @@ -238,6 +240,8 @@ export class ViewCommands implements Disposable {
registerViewCommand('gitlens.views.addAuthor', this.addAuthor, this),
registerViewCommand('gitlens.views.addAuthor.multi', this.addAuthor, this, true),

registerViewCommand('gitlens.views.addPullRequestRemote', this.addPullRequestRemote, this),

registerViewCommand(
'gitlens.views.openBranchOnRemote',
n => executeCommand(GlCommand.OpenBranchOnRemote, n),
Expand Down Expand Up @@ -481,6 +485,15 @@ export class ViewCommands implements Disposable {
return RemoteActions.add(getNodeRepoPath(node));
}

@log()
private async addPullRequestRemote(node: ViewNode, pr: PullRequest, repo: Repository) {
const identity = getRepositoryIdentityForPullRequest(pr);
if (identity.remote?.url == null) return;

await repo.git.remotes().addRemote?.(identity.provider.repoDomain, identity.remote.url, { fetch: true });
return node.triggerChange(true);
}

@log()
private applyChanges(node: ViewRefFileNode) {
if (node.is('results-file')) {
Expand Down
20 changes: 14 additions & 6 deletions src/views/viewDecorationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,17 +207,25 @@ function getCommitFileStatusDecoration(uri: Uri, _token: CancellationToken): Fil
}

interface RemoteViewDecoration {
default?: boolean;
state?: 'default' | 'missing';
}

function getRemoteDecoration(uri: Uri, _token: CancellationToken): FileDecoration | undefined {
const state = getViewDecoration<'remote'>(uri);

if (state?.default) {
return {
badge: GlyphChars.Check,
tooltip: 'Default Remote',
};
switch (state?.state) {
case 'default':
return {
badge: GlyphChars.Check,
tooltip: 'Default Remote',
};

case 'missing':
return {
badge: '?',
color: new ThemeColor('gitlens.decorations.workspaceRepoMissingForegroundColor' satisfies Colors),
tooltip: '',
};
}

return undefined;
Expand Down