Skip to content

Commit d010df7

Browse files
committed
Merge branch 'main' into copilot/fix-worktree-merge-issue
2 parents 3800799 + 3755bdd commit d010df7

13 files changed

+415
-22
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@
4141
"treeItemMarkdownLabel",
4242
"treeViewMarkdownMessage"
4343
],
44-
"version": "0.128.0",
44+
"version": "0.130.0",
4545
"publisher": "GitHub",
4646
"engines": {
47-
"vscode": "^1.109.0"
47+
"vscode": "^1.110.0"
4848
},
4949
"categories": [
5050
"Other",

src/api/api.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ export interface Remote {
5454
}
5555

5656
export interface Worktree {
57-
readonly path: string;
5857
readonly name: string;
58+
readonly path: string;
5959
readonly ref: string;
6060
readonly main: boolean;
6161
readonly detached: boolean;

src/github/createPRViewProvider.ts

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
PullRequestDefaults,
1111
titleAndBodyFrom,
1212
} from './folderRepositoryManager';
13-
import { GitHubRepository } from './githubRepository';
13+
import { GitHubRepository, isRateLimitError, ViewerPermission } from './githubRepository';
1414
import { IAccount, ILabel, IMilestone, IProject, isITeam, ITeam, MergeMethod, RepoAccessAndMergeMethods } from './interface';
1515
import { BaseBranchMetadata, PullRequestGitHelper } from './pullRequestGitHelper';
1616
import { PullRequestModel } from './pullRequestModel';
@@ -211,13 +211,47 @@ export abstract class BaseCreatePullRequestViewProvider<T extends BasePullReques
211211

212212
const defaultBaseBranch = detectedBaseMetadata?.branch ?? this._pullRequestDefaults.base;
213213

214-
const [defaultTitleAndDescription, mergeConfiguration, viewerPermission, mergeQueueMethodForBranch, labels] = await Promise.all([
215-
this.getTitleAndDescription(defaultCompareBranch, defaultBaseBranch),
216-
this.getMergeConfiguration(defaultBaseRemote.owner, defaultBaseRemote.repositoryName),
217-
defaultOrigin.getViewerPermission(),
218-
this._folderRepositoryManager.mergeQueueMethodForBranch(defaultBaseBranch, defaultBaseRemote.owner, defaultBaseRemote.repositoryName),
219-
this.getPullRequestDefaultLabels(defaultBaseRemote)
220-
]);
214+
let defaultTitleAndDescription: { title: string; description: string };
215+
let mergeConfiguration: RepoAccessAndMergeMethods;
216+
let viewerPermission: ViewerPermission;
217+
let mergeQueueMethodForBranch: MergeMethod | undefined;
218+
let labels: ILabel[];
219+
try {
220+
[defaultTitleAndDescription, mergeConfiguration, viewerPermission, mergeQueueMethodForBranch, labels] = await Promise.all([
221+
this.getTitleAndDescription(defaultCompareBranch, defaultBaseBranch),
222+
this.getMergeConfiguration(defaultBaseRemote.owner, defaultBaseRemote.repositoryName),
223+
defaultOrigin.getViewerPermission(),
224+
this._folderRepositoryManager.mergeQueueMethodForBranch(defaultBaseBranch, defaultBaseRemote.owner, defaultBaseRemote.repositoryName),
225+
this.getPullRequestDefaultLabels(defaultBaseRemote)
226+
]);
227+
} catch (e) {
228+
if (isRateLimitError(e)) {
229+
vscode.window.showErrorMessage(vscode.l10n.t('GitHub API rate limit exceeded. Please wait and try again.'));
230+
}
231+
Logger.error(`Error initializing create pull request view: ${e}`, BaseCreatePullRequestViewProvider.ID);
232+
return {
233+
canModifyBranches: true,
234+
defaultBaseRemote,
235+
defaultBaseBranch,
236+
defaultCompareRemote,
237+
defaultCompareBranch: this._defaultCompareBranch,
238+
defaultTitle: '',
239+
defaultDescription: '',
240+
baseHasMergeQueue: false,
241+
remoteCount: remotes.length,
242+
autoMergeDefault: false,
243+
createError: '',
244+
isDraftDefault: false,
245+
isDarkTheme: vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark,
246+
generateTitleAndDescriptionTitle: undefined,
247+
creating: false,
248+
initializeWithGeneratedTitleAndDescription: false,
249+
preReviewState: PreReviewState.None,
250+
preReviewer: undefined,
251+
reviewing: false,
252+
usingTemplate: false,
253+
};
254+
}
221255

222256
const defaultCreateOption = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<'lastUsed' | 'create' | 'createDraft' | 'createAutoMerge'>(DEFAULT_CREATE_OPTION, 'lastUsed');
223257
const lastCreateMethod: { autoMerge: boolean, mergeMethod: MergeMethod | undefined, isDraft: boolean } | undefined = this._folderRepositoryManager.context.workspaceState.get<{ autoMerge: boolean, mergeMethod: MergeMethod, isDraft } | undefined>(PREVIOUS_CREATE_METHOD, undefined);
@@ -724,7 +758,8 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
724758

725759
const name = compareBranch.name;
726760
const branchNameTitle = (name: string) => {
727-
return `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
761+
const nameWithSpaces = name.replace(/[-_]/g, ' ');
762+
return `${nameWithSpaces.charAt(0).toUpperCase()}${nameWithSpaces.slice(1)}`;
728763
};
729764

730765
// If branchName is selected, use the branch name as the title
@@ -994,7 +1029,18 @@ Don't forget to commit your template file to the repository so that it can be us
9941029
}
9951030

9961031
private async processRemoteAndBranchResult(githubRepository: GitHubRepository, result: { remote: RemoteInfo, branch: string }, isBase: boolean) {
997-
const [defaultBranch, viewerPermission] = await Promise.all([githubRepository.getDefaultBranch(), githubRepository.getViewerPermission()]);
1032+
let viewerPermission: ViewerPermission;
1033+
try {
1034+
viewerPermission = await githubRepository.getViewerPermission();
1035+
} catch (e) {
1036+
if (isRateLimitError(e)) {
1037+
vscode.window.showErrorMessage(vscode.l10n.t('GitHub API rate limit exceeded. Please wait and try again.'));
1038+
viewerPermission = ViewerPermission.Unknown;
1039+
} else {
1040+
throw e;
1041+
}
1042+
}
1043+
const defaultBranch = await githubRepository.getDefaultBranch();
9981044

9991045
commands.setContext(contexts.CREATE_PR_PERMISSIONS, viewerPermission);
10001046
let chooseResult: ChooseBaseRemoteAndBranchResult | ChooseCompareRemoteAndBranchResult;

src/github/folderRepositoryManager.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ConflictModel } from './conflictGuide';
1111
import { ConflictResolutionCoordinator } from './conflictResolutionCoordinator';
1212
import { Conflict, ConflictResolutionModel } from './conflictResolutionModel';
1313
import { CredentialStore } from './credentials';
14-
import { CopilotWorkingStatus, GitHubRepository, ItemsData, PULL_REQUEST_PAGE_SIZE, PullRequestChangeEvent, PullRequestData, TeamReviewerRefreshKind, ViewerPermission } from './githubRepository';
14+
import { CopilotWorkingStatus, GitHubRepository, isRateLimitError, ItemsData, PULL_REQUEST_PAGE_SIZE, PullRequestChangeEvent, PullRequestData, TeamReviewerRefreshKind, ViewerPermission } from './githubRepository';
1515
import { PullRequestResponse, PullRequestState } from './graphql';
1616
import { IAccount, ILabel, IMilestone, IProject, IPullRequestsPagingOptions, Issue, ITeam, MergeMethod, PRType, PullRequestMergeability, RepoAccessAndMergeMethods, User } from './interface';
1717
import { IssueModel } from './issueModel';
@@ -2515,7 +2515,7 @@ export class FolderRepositoryManager extends Disposable {
25152515
}
25162516
}
25172517

2518-
if (pullRequest.item.mergeable !== PullRequestMergeability.Conflict) {
2518+
if (pullRequest.item.mergeable !== PullRequestMergeability.Conflict && !pullRequest.githubRepository.remote.isEnterprise) {
25192519
const result = await vscode.window.withProgress(
25202520
{ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Updating branch...') },
25212521
async () => {
@@ -2986,7 +2986,16 @@ export class FolderRepositoryManager extends Disposable {
29862986
pushRemote,
29872987
this.credentialStore,
29882988
);
2989-
const permission = await githubRepo.getViewerPermission();
2989+
let permission: ViewerPermission;
2990+
try {
2991+
permission = await githubRepo.getViewerPermission();
2992+
} catch (e) {
2993+
if (isRateLimitError(e)) {
2994+
vscode.window.showErrorMessage(vscode.l10n.t('GitHub API rate limit exceeded. Please wait and try again.'), { modal: true });
2995+
return;
2996+
}
2997+
throw e;
2998+
}
29902999
let selectedRemote: GitHubRemote | undefined;
29913000
if (
29923001
permission === ViewerPermission.Read ||

src/github/githubRepository.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,26 @@ export enum ViewerPermission {
124124
Write = 'WRITE',
125125
}
126126

127+
export class RateLimitError extends Error {
128+
constructor(message?: string) {
129+
super(message ?? 'GitHub API rate limit exceeded');
130+
this.name = 'RateLimitError';
131+
}
132+
}
133+
134+
export function isRateLimitError(e: unknown): boolean {
135+
if (e instanceof RateLimitError) {
136+
return true;
137+
}
138+
if (e instanceof Error) {
139+
const msg = e.message.toLowerCase();
140+
if (msg.includes('rate limit') || msg.includes('secondary rate') || msg.includes('abuse detection')) {
141+
return true;
142+
}
143+
}
144+
return false;
145+
}
146+
127147
export enum TeamReviewerRefreshKind {
128148
None,
129149
Try,
@@ -941,6 +961,9 @@ export class GitHubRepository extends Disposable {
941961
return parseGraphQLViewerPermission(data);
942962
} catch (e) {
943963
Logger.error(`Unable to fetch viewer permission: ${e}`, this.id);
964+
if (isRateLimitError(e)) {
965+
throw new RateLimitError();
966+
}
944967
return ViewerPermission.Unknown;
945968
}
946969
}

src/github/pullRequestOverview.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
274274
private isUpdateBranchWithGitHubEnabled(): boolean {
275275
// With the GraphQL UpdatePullRequestBranch API, we can update branches even when not checked out
276276
// (as long as there are no conflicts). If there are conflicts, we need the branch to be checked out.
277+
// GitHub Enterprise doesn't support the GraphQL UpdatePullRequestBranch mutation,
278+
// so we always need the branch to be checked out for enterprise.
279+
if (this._item.githubRepository.remote.isEnterprise) {
280+
return this._item.isActive;
281+
}
277282
const hasConflicts = this._item.item.mergeable === PullRequestMergeability.Conflict;
278283
if (hasConflicts) {
279284
return this._item.isActive;

src/github/repositoriesManager.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,39 @@ export class RepositoriesManager extends Disposable {
9191
folderManager.onDidChangeAnyPullRequests(e => this._onDidChangeAnyPullRequests.fire(e)),
9292
folderManager.onDidAddPullRequest(e => this._onDidAddPullRequest.fire(e)),
9393
folderManager.onDidChangeGithubRepositories(() => this._onDidAddAnyGitHubRepository.fire(folderManager)),
94+
folderManager.repository.state.onDidChange(() => this.checkWorktreeChanges(folderManager.repository)),
9495
];
9596
this._subs.set(folderManager, disposables);
9697
}
9798

99+
private _previousWorktrees: Map<string, Set<string>> = new Map();
100+
101+
private checkWorktreeChanges(repo: Repository): void {
102+
const worktrees = repo.state.worktrees;
103+
if (!worktrees) {
104+
return;
105+
}
106+
107+
const repoKey = repo.rootUri.toString();
108+
const currentPaths = new Set(worktrees.map(wt => vscode.Uri.file(wt.path).toString()));
109+
const previousPaths = this._previousWorktrees.get(repoKey);
110+
this._previousWorktrees.set(repoKey, currentPaths);
111+
112+
if (!previousPaths) {
113+
return;
114+
}
115+
116+
for (const previousPath of previousPaths) {
117+
if (!currentPaths.has(previousPath)) {
118+
const folderManager = this._folderManagers.find(m => m.repository.rootUri.toString() === previousPath);
119+
if (folderManager) {
120+
Logger.appendLine(`Removing folder manager for removed worktree ${previousPath}`, RepositoriesManager.ID);
121+
this.removeRepo(folderManager.repository);
122+
}
123+
}
124+
}
125+
}
126+
98127
insertFolderManager(folderManager: FolderRepositoryManager) {
99128
this.registerFolderListeners(folderManager);
100129

@@ -127,7 +156,7 @@ export class RepositoriesManager extends Disposable {
127156
const folderManager = this._folderManagers[existingFolderManagerIndex];
128157
disposeAll(this._subs.get(folderManager)!);
129158
this._subs.delete(folderManager);
130-
this._folderManagers.splice(existingFolderManagerIndex);
159+
this._folderManagers.splice(existingFolderManagerIndex, 1);
131160
folderManager.dispose();
132161
this.updateActiveReviewCount();
133162
this._onDidChangeFolderRepositories.fire({});

0 commit comments

Comments
 (0)