Skip to content

Commit 9d32484

Browse files
committed
Substitutes repository of the selected issue
(#3621, #3698)
1 parent 7e1f729 commit 9d32484

File tree

5 files changed

+96
-4
lines changed

5 files changed

+96
-4
lines changed

src/commands/git/branch.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ interface CreateState {
6363
flags: CreateFlags[];
6464

6565
suggestNameOnly?: boolean;
66+
suggestRepoOnly?: boolean;
67+
}
68+
69+
function isCreateState(state: Partial<State> | undefined): state is Partial<CreateState> {
70+
return state?.subcommand === 'create';
6671
}
6772

6873
type DeleteFlags = '--force' | '--remotes';
@@ -172,6 +177,10 @@ export class BranchGitCommand extends QuickCommand {
172177
counter++;
173178
}
174179

180+
if (args.state.suggestRepoOnly && args.state.repo != null) {
181+
counter--;
182+
}
183+
175184
break;
176185
case 'delete':
177186
case 'prune':
@@ -251,7 +260,12 @@ export class BranchGitCommand extends QuickCommand {
251260
state.subcommand,
252261
);
253262

254-
if (state.counter < 2 || state.repo == null || typeof state.repo === 'string') {
263+
if (
264+
state.counter < 2 ||
265+
state.repo == null ||
266+
typeof state.repo === 'string' ||
267+
(isCreateState(state) && state.suggestRepoOnly)
268+
) {
255269
skippedStepTwo = false;
256270
if (context.repos.length === 1) {
257271
skippedStepTwo = true;

src/git/models/issue.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { ColorThemeKind, ThemeColor, ThemeIcon, window } from 'vscode';
1+
import { ColorThemeKind, ThemeColor, ThemeIcon, Uri, window } from 'vscode';
2+
import { Schemes } from '../../constants';
23
import type { Colors } from '../../constants.colors';
4+
import type { Container } from '../../container';
5+
import type { RepositoryIdentityDescriptor } from '../../gk/models/repositoryIdentities';
36
import type { ProviderReference } from './remoteProvider';
7+
import type { Repository } from './repository';
48

59
export type IssueOrPullRequestType = 'issue' | 'pullrequest';
610
export type IssueOrPullRequestState = 'opened' | 'closed' | 'merged';
@@ -45,6 +49,7 @@ export interface IssueRepository {
4549
owner: string;
4650
repo: string;
4751
accessLevel?: RepositoryAccessLevel;
52+
url?: string;
4853
}
4954

5055
export interface IssueShape extends IssueOrPullRequest {
@@ -217,6 +222,7 @@ export function serializeIssue(value: IssueShape): IssueShape {
217222
: {
218223
owner: value.repository.owner,
219224
repo: value.repository.repo,
225+
url: value.repository.url,
220226
},
221227
assignees: value.assignees.map(assignee => ({
222228
id: assignee.id,
@@ -261,3 +267,67 @@ export class Issue implements IssueShape {
261267
public readonly body?: string,
262268
) {}
263269
}
270+
271+
export type IssueRepositoryIdentityDescriptor = RequireSomeWithProps<
272+
RequireSome<RepositoryIdentityDescriptor<string>, 'provider'>,
273+
'provider',
274+
'id' | 'domain' | 'repoDomain' | 'repoName'
275+
> &
276+
RequireSomeWithProps<RequireSome<RepositoryIdentityDescriptor<string>, 'remote'>, 'remote', 'domain'>;
277+
278+
export function getRepositoryIdentityForIssue(issue: IssueShape | Issue): IssueRepositoryIdentityDescriptor {
279+
if (issue.repository == null) throw new Error('Missing repository');
280+
281+
return {
282+
remote: {
283+
url: issue.repository.url,
284+
domain: issue.provider.domain,
285+
},
286+
name: `${issue.repository.owner}/${issue.repository.repo}`,
287+
provider: {
288+
id: issue.provider.id,
289+
domain: issue.provider.domain,
290+
repoDomain: issue.repository.owner,
291+
repoName: issue.repository.repo,
292+
repoOwnerDomain: issue.repository.owner,
293+
},
294+
};
295+
}
296+
297+
export function getVirtualUriForIssue(issue: IssueShape | Issue): Uri | undefined {
298+
if (issue.repository == null) throw new Error('Missing repository');
299+
if (issue.provider.id !== 'github') return undefined;
300+
301+
const uri = Uri.parse(issue.repository.url ?? issue.url);
302+
return uri.with({ scheme: Schemes.Virtual, authority: 'github', path: uri.path });
303+
}
304+
305+
export async function getOrOpenIssueRepository(
306+
container: Container,
307+
issue: IssueShape | Issue,
308+
options?: { promptIfNeeded?: boolean; skipVirtual?: boolean },
309+
): Promise<Repository | undefined> {
310+
const identity = getRepositoryIdentityForIssue(issue);
311+
let repo = await container.repositoryIdentity.getRepository(identity, {
312+
openIfNeeded: true,
313+
keepOpen: false,
314+
prompt: false,
315+
});
316+
317+
if (repo == null && !options?.skipVirtual) {
318+
const virtualUri = getVirtualUriForIssue(issue);
319+
if (virtualUri != null) {
320+
repo = await container.git.getOrOpenRepository(virtualUri, { closeOnOpen: true, detectNested: false });
321+
}
322+
}
323+
324+
if (repo == null && options?.promptIfNeeded) {
325+
repo = await container.repositoryIdentity.getRepository(identity, {
326+
openIfNeeded: true,
327+
keepOpen: false,
328+
prompt: true,
329+
});
330+
}
331+
332+
return repo;
333+
}

src/plus/integrations/providers/github/github.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ repository {
190190
login
191191
}
192192
viewerPermission
193+
url
193194
}
194195
`;
195196

src/plus/integrations/providers/github/models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export interface GitHubIssue extends Omit<GitHubIssueOrPullRequest, '__typename'
133133
login: string;
134134
};
135135
viewerPermission: GitHubViewerPermission;
136+
url: string;
136137
};
137138
body: string;
138139
}
@@ -421,6 +422,7 @@ export function fromGitHubIssue(value: GitHubIssue, provider: Provider): Issue {
421422
owner: value.repository.owner.login,
422423
repo: value.repository.name,
423424
accessLevel: fromGitHubViewerPermissionToAccessLevel(value.repository.viewerPermission),
425+
url: value.repository.url,
424426
},
425427
value.assignees.nodes.map(assignee => ({
426428
id: assignee.login,

src/plus/startWork/startWork.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { HostingIntegrationId } from '../../constants.integrations';
2424
import type { Source, Sources, StartWorkTelemetryContext } from '../../constants.telemetry';
2525
import type { Container } from '../../container';
2626
import type { SearchedIssue } from '../../git/models/issue';
27+
import { getOrOpenIssueRepository } from '../../git/models/issue';
2728
import type { QuickPickItemOfT } from '../../quickpicks/items/common';
2829
import { createQuickPickItemOfT } from '../../quickpicks/items/common';
2930
import type { DirectiveQuickPickItem } from '../../quickpicks/items/directive';
@@ -138,6 +139,9 @@ export class StartWorkCommand extends QuickCommand<State> {
138139
assertsStartWorkStepState(state);
139140
state.action = 'start';
140141

142+
const issue = state.item.item.issue;
143+
const repo = await getOrOpenIssueRepository(this.container, issue);
144+
141145
if (typeof state.action === 'string') {
142146
switch (state.action) {
143147
case 'start':
@@ -147,9 +151,10 @@ export class StartWorkCommand extends QuickCommand<State> {
147151
command: 'branch',
148152
state: {
149153
subcommand: 'create',
150-
repo: undefined,
151-
name: slug(`${state.item.item.issue.id}-${state.item.item.issue.title}`),
154+
repo: repo,
155+
name: slug(`${issue.id}-${issue.title}`),
152156
suggestNameOnly: true,
157+
suggestRepoOnly: true,
153158
},
154159
},
155160
this.pickedVia,

0 commit comments

Comments
 (0)