Skip to content

Commit 6de5b34

Browse files
authored
Adopt isUpdating in pr webview view and throw if re-entrant. (#7979)
1 parent d7dd39b commit 6de5b34

File tree

2 files changed

+129
-114
lines changed

2 files changed

+129
-114
lines changed

src/github/activityBarViewProvider.ts

Lines changed: 126 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ReviewManager } from '../view/reviewManager';
2323
export class PullRequestViewProvider extends WebviewViewBase implements vscode.WebviewViewProvider {
2424
public override readonly viewType = 'github:activePullRequest';
2525
private _existingReviewers: ReviewState[] = [];
26+
private _isUpdating: boolean = false;
2627

2728
constructor(
2829
extensionUri: vscode.Uri,
@@ -182,132 +183,143 @@ export class PullRequestViewProvider extends WebviewViewBase implements vscode.W
182183
disposeAll(this._prDisposables);
183184
}
184185
this._prDisposables = [];
185-
this._prDisposables.push(pullRequestModel.onDidChange(() => this.updatePullRequest(pullRequestModel)));
186+
this._prDisposables.push(pullRequestModel.onDidChange(e => {
187+
if ((e.state || e.comments || e.reviewers) && !this._isUpdating) {
188+
this.updatePullRequest(pullRequestModel);
189+
}
190+
}));
186191
this._prDisposables.push(pullRequestModel.onDidChangePendingReviewState(() => this.updatePullRequest(pullRequestModel)));
187192
}
188193

189194
private _updatePendingVisibility: vscode.Disposable | undefined = undefined;
190195
public async updatePullRequest(pullRequestModel: PullRequestModel): Promise<void> {
191-
if (this._view && !this._view.visible) {
192-
this._updatePendingVisibility?.dispose();
193-
this._updatePendingVisibility = this._view.onDidChangeVisibility(async () => {
194-
this.updatePullRequest(pullRequestModel);
195-
this._updatePendingVisibility?.dispose();
196-
});
196+
if (this._isUpdating) {
197+
throw new Error('Already updating pull request view');
197198
}
199+
this._isUpdating = true;
198200

199-
if ((this._prDisposables === undefined) || (pullRequestModel.number !== this._item.number)) {
200-
this.registerPrSpecificListeners(pullRequestModel);
201-
}
202-
this._item = pullRequestModel;
203-
return Promise.all([
204-
this._folderRepositoryManager.resolvePullRequest(
205-
pullRequestModel.remote.owner,
206-
pullRequestModel.remote.repositoryName,
207-
pullRequestModel.number,
208-
),
209-
this._folderRepositoryManager.getPullRequestRepositoryAccessAndMergeMethods(pullRequestModel),
210-
pullRequestModel.getTimelineEvents(),
211-
pullRequestModel.getReviewRequests(),
212-
this._folderRepositoryManager.getBranchNameForPullRequest(pullRequestModel),
213-
this._folderRepositoryManager.getPullRequestRepositoryDefaultBranch(pullRequestModel),
214-
this._folderRepositoryManager.getCurrentUser(pullRequestModel.githubRepository),
215-
pullRequestModel.canEdit(),
216-
pullRequestModel.validateDraftMode(),
217-
ensureEmojis(this._folderRepositoryManager.context)
218-
])
219-
.then(result => {
220-
const [pullRequest, repositoryAccess, timelineEvents, requestedReviewers, branchInfo, defaultBranch, currentUser, viewerCanEdit, hasReviewDraft] = result;
221-
if (!pullRequest) {
222-
throw new Error(
223-
`Fail to resolve Pull Request #${pullRequestModel.number} in ${pullRequestModel.remote.owner}/${pullRequestModel.remote.repositoryName}`,
224-
);
225-
}
201+
try {
202+
if (this._view && !this._view.visible) {
203+
this._updatePendingVisibility?.dispose();
204+
this._updatePendingVisibility = this._view.onDidChangeVisibility(async () => {
205+
this.updatePullRequest(pullRequestModel);
206+
this._updatePendingVisibility?.dispose();
207+
});
208+
}
226209

227-
this._item = pullRequest;
228-
if (!this._view) {
229-
// If the there is no PR webview, then there is nothing else to update.
230-
return;
231-
}
210+
if ((this._prDisposables === undefined) || (pullRequestModel.number !== this._item.number)) {
211+
this.registerPrSpecificListeners(pullRequestModel);
212+
}
213+
this._item = pullRequestModel;
214+
const [pullRequest, repositoryAccess, timelineEvents, requestedReviewers, branchInfo, defaultBranch, currentUser, viewerCanEdit, hasReviewDraft] = await Promise.all([
215+
this._folderRepositoryManager.resolvePullRequest(
216+
pullRequestModel.remote.owner,
217+
pullRequestModel.remote.repositoryName,
218+
pullRequestModel.number,
219+
),
220+
this._folderRepositoryManager.getPullRequestRepositoryAccessAndMergeMethods(pullRequestModel),
221+
pullRequestModel.getTimelineEvents(),
222+
pullRequestModel.getReviewRequests(),
223+
this._folderRepositoryManager.getBranchNameForPullRequest(pullRequestModel),
224+
this._folderRepositoryManager.getPullRequestRepositoryDefaultBranch(pullRequestModel),
225+
this._folderRepositoryManager.getCurrentUser(pullRequestModel.githubRepository),
226+
pullRequestModel.canEdit(),
227+
pullRequestModel.validateDraftMode(),
228+
ensureEmojis(this._folderRepositoryManager.context)
229+
]);
230+
231+
if (!pullRequest) {
232+
throw new Error(
233+
`Fail to resolve Pull Request #${pullRequestModel.number} in ${pullRequestModel.remote.owner}/${pullRequestModel.remote.repositoryName}`,
234+
);
235+
}
232236

233-
try {
234-
this._view.title = `${vscode.l10n.t('Review Pull Request')} #${pullRequestModel.number.toString()}`;
235-
} catch (e) {
236-
// If we ry to set the title of the webview too early it will throw an error.
237-
}
237+
this._item = pullRequest;
238+
if (!this._view) {
239+
// If the there is no PR webview, then there is nothing else to update.
240+
return;
241+
}
238242

239-
const isCurrentlyCheckedOut = pullRequestModel.equals(this._folderRepositoryManager.activePullRequest);
240-
const hasWritePermission = repositoryAccess!.hasWritePermission;
241-
const mergeMethodsAvailability = repositoryAccess!.mergeMethodsAvailability;
242-
const canEdit = hasWritePermission || viewerCanEdit;
243-
const defaultMergeMethod = getDefaultMergeMethod(mergeMethodsAvailability);
244-
this._existingReviewers = parseReviewers(
245-
requestedReviewers ?? [],
246-
timelineEvents ?? [],
247-
pullRequest.author,
248-
);
243+
try {
244+
this._view.title = `${vscode.l10n.t('Review Pull Request')} #${pullRequestModel.number.toString()}`;
245+
} catch (e) {
246+
// If we ry to set the title of the webview too early it will throw an error.
247+
}
249248

250-
const isCrossRepository =
251-
pullRequest.base &&
252-
pullRequest.head &&
253-
!pullRequest.base.repositoryCloneUrl.equals(pullRequest.head.repositoryCloneUrl);
254-
255-
const continueOnGitHub = !!(isCrossRepository && isInCodespaces());
256-
const reviewState = this.getCurrentUserReviewState(this._existingReviewers, currentUser);
257-
258-
const context: Partial<PullRequest> = {
259-
number: pullRequest.number,
260-
title: pullRequest.title,
261-
url: pullRequest.html_url,
262-
createdAt: pullRequest.createdAt,
263-
body: pullRequest.body,
264-
bodyHTML: pullRequest.bodyHTML,
265-
labels: pullRequest.item.labels.map(label => ({ ...label, displayName: emojify(label.name) })),
266-
author: {
267-
login: pullRequest.author.login,
268-
name: pullRequest.author.name,
269-
avatarUrl: pullRequest.userAvatar,
270-
url: pullRequest.author.url,
271-
email: pullRequest.author.email,
272-
id: pullRequest.author.id,
273-
accountType: pullRequest.author.accountType,
274-
},
275-
state: pullRequest.state,
276-
isCurrentlyCheckedOut: isCurrentlyCheckedOut,
277-
isRemoteBaseDeleted: pullRequest.isRemoteBaseDeleted,
278-
base: pullRequest.base.label,
279-
isRemoteHeadDeleted: pullRequest.isRemoteHeadDeleted,
280-
isLocalHeadDeleted: !branchInfo,
281-
head: pullRequest.head?.label ?? '',
282-
canEdit: canEdit,
283-
hasWritePermission,
284-
mergeable: pullRequest.item.mergeable,
285-
isDraft: pullRequest.isDraft,
286-
status: null,
287-
reviewRequirement: null,
288-
canUpdateBranch: pullRequest.item.viewerCanUpdate,
289-
events: timelineEvents,
290-
mergeMethodsAvailability,
291-
defaultMergeMethod,
292-
repositoryDefaultBranch: defaultBranch,
293-
isIssue: false,
294-
isAuthor: currentUser.login === pullRequest.author.login,
295-
reviewers: this._existingReviewers,
296-
continueOnGitHub,
297-
isDarkTheme: vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark,
298-
isEnterprise: pullRequest.githubRepository.remote.isEnterprise,
299-
hasReviewDraft,
300-
currentUserReviewState: reviewState
301-
};
302-
303-
this._postMessage({
304-
command: 'pr.initialize',
305-
pullrequest: context,
306-
});
307-
})
308-
.catch(e => {
309-
vscode.window.showErrorMessage(`Error updating active pull request view: ${formatError(e)}`);
249+
const isCurrentlyCheckedOut = pullRequestModel.equals(this._folderRepositoryManager.activePullRequest);
250+
const hasWritePermission = repositoryAccess!.hasWritePermission;
251+
const mergeMethodsAvailability = repositoryAccess!.mergeMethodsAvailability;
252+
const canEdit = hasWritePermission || viewerCanEdit;
253+
const defaultMergeMethod = getDefaultMergeMethod(mergeMethodsAvailability);
254+
this._existingReviewers = parseReviewers(
255+
requestedReviewers ?? [],
256+
timelineEvents ?? [],
257+
pullRequest.author,
258+
);
259+
260+
const isCrossRepository =
261+
pullRequest.base &&
262+
pullRequest.head &&
263+
!pullRequest.base.repositoryCloneUrl.equals(pullRequest.head.repositoryCloneUrl);
264+
265+
const continueOnGitHub = !!(isCrossRepository && isInCodespaces());
266+
const reviewState = this.getCurrentUserReviewState(this._existingReviewers, currentUser);
267+
268+
const context: Partial<PullRequest> = {
269+
number: pullRequest.number,
270+
title: pullRequest.title,
271+
url: pullRequest.html_url,
272+
createdAt: pullRequest.createdAt,
273+
body: pullRequest.body,
274+
bodyHTML: pullRequest.bodyHTML,
275+
labels: pullRequest.item.labels.map(label => ({ ...label, displayName: emojify(label.name) })),
276+
author: {
277+
login: pullRequest.author.login,
278+
name: pullRequest.author.name,
279+
avatarUrl: pullRequest.userAvatar,
280+
url: pullRequest.author.url,
281+
email: pullRequest.author.email,
282+
id: pullRequest.author.id,
283+
accountType: pullRequest.author.accountType,
284+
},
285+
state: pullRequest.state,
286+
isCurrentlyCheckedOut: isCurrentlyCheckedOut,
287+
isRemoteBaseDeleted: pullRequest.isRemoteBaseDeleted,
288+
base: pullRequest.base.label,
289+
isRemoteHeadDeleted: pullRequest.isRemoteHeadDeleted,
290+
isLocalHeadDeleted: !branchInfo,
291+
head: pullRequest.head?.label ?? '',
292+
canEdit: canEdit,
293+
hasWritePermission,
294+
mergeable: pullRequest.item.mergeable,
295+
isDraft: pullRequest.isDraft,
296+
status: null,
297+
reviewRequirement: null,
298+
canUpdateBranch: pullRequest.item.viewerCanUpdate,
299+
events: timelineEvents,
300+
mergeMethodsAvailability,
301+
defaultMergeMethod,
302+
repositoryDefaultBranch: defaultBranch,
303+
isIssue: false,
304+
isAuthor: currentUser.login === pullRequest.author.login,
305+
reviewers: this._existingReviewers,
306+
continueOnGitHub,
307+
isDarkTheme: vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark,
308+
isEnterprise: pullRequest.githubRepository.remote.isEnterprise,
309+
hasReviewDraft,
310+
currentUserReviewState: reviewState
311+
};
312+
313+
this._postMessage({
314+
command: 'pr.initialize',
315+
pullrequest: context,
310316
});
317+
318+
} catch (e) {
319+
vscode.window.showErrorMessage(`Error updating active pull request view: ${formatError(e)}`);
320+
} finally {
321+
this._isUpdating = false;
322+
}
311323
}
312324

313325
private close(message: IRequestMessage<string>): void {

src/github/pullRequestOverview.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
217217
}
218218

219219
protected override async updateItem(pullRequestModel: PullRequestModel): Promise<void> {
220+
if (this._isUpdating) {
221+
throw new Error('Already updating pull request webview');
222+
}
220223
this._isUpdating = true;
221224
try {
222225
const [

0 commit comments

Comments
 (0)