Skip to content

Commit 99f2032

Browse files
sergeibbbchivorotkiv
authored andcommitted
Substitutes repository of the selected issue
(#3621, #3698)
1 parent f104e5b commit 99f2032

File tree

5 files changed

+100
-4
lines changed

5 files changed

+100
-4
lines changed

src/commands/git/branch.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ 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';
71+
}
72+
73+
function castCreateState(state: Partial<State> | undefined): Partial<CreateState> | undefined {
74+
return isCreateState(state) ? state : undefined;
6675
}
6776

6877
type DeleteFlags = '--force' | '--remotes';
@@ -172,6 +181,10 @@ export class BranchGitCommand extends QuickCommand {
172181
counter++;
173182
}
174183

184+
if (args.state.suggestRepoOnly && args.state.repo != null) {
185+
counter--;
186+
}
187+
175188
break;
176189
case 'delete':
177190
case 'prune':
@@ -251,7 +264,12 @@ export class BranchGitCommand extends QuickCommand {
251264
state.subcommand,
252265
);
253266

254-
if (state.counter < 2 || state.repo == null || typeof state.repo === 'string') {
267+
if (
268+
state.counter < 2 ||
269+
state.repo == null ||
270+
typeof state.repo === 'string' ||
271+
castCreateState(state)?.suggestRepoOnly
272+
) {
255273
skippedStepTwo = false;
256274
if (context.repos.length === 1) {
257275
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)