feat(review): auto-continue merge and poll provider after push

Coordinate local conflict resolutions with automated push and provider-
side merge status checks. When a local resolution completes the app
advances the merge workflow, pushes the updated branch, and attempts to
continue the merge. The UI shows a checking state and disables relevant
actions while the provider rechecks to prevent duplicate operations.
This streamlines finishing conflict resolution and keeps PR status in
sync with remote providers.

- Automatically push and continue merge when local resolutions finish
- Poll provider for updated mergeability and refresh request details
- Add guards to disable UI actions while waiting for remote status
This commit is contained in:
2026-09-07 18:54:06 +02:00
parent c46a875a91
commit 13660c801e
2 changed files with 70 additions and 6 deletions
+23 -2
View File
@@ -2700,7 +2700,6 @@
const sourceBranch = localSource?.name ?? remoteSource?.name;
if (!sourceBranch) throw new Error(appLanguage === "de" ? `Der Quellbranch „${request.sourceBranch}“ wurde lokal und auf ${match.remote.name} nicht gefunden.` : `The source branch “${request.sourceBranch}” was not found locally or on ${match.remote.name}.`);
if (status?.current_branch !== request.sourceBranch) applyStatus(await checkoutBranch(match.path, sourceBranch));
if ((status?.ahead ?? 0) > 0) throw new Error(appLanguage === "de" ? `Der lokale Branch „${request.sourceBranch}“ enthält noch nicht gepushte Commits. Pushe oder sichere sie zuerst.` : `The local branch “${request.sourceBranch}” contains unpushed commits. Push or preserve them first.`);
if (remoteSource) applyStatus(await mergeBranch(match.path, remoteSource.name, "ff-only"));
const targetBranch = availableBranches.some((branch) => branch.remote && branch.name === `${match.remote.name}/${request.targetBranch}`)
? `${match.remote.name}/${request.targetBranch}`
@@ -2717,6 +2716,8 @@
} else {
reviewConflictPhase = "ready-to-push";
reviewConflictMessage = appLanguage === "de" ? "Der Zielbranch wurde ohne Dateikonflikte übernommen. Der Quellbranch kann jetzt gepusht werden." : "The target branch was merged without file conflicts. The source branch is ready to push.";
await tick();
await pushReviewConflictResolution();
}
trackEvent("review_conflict_resolution_started");
} catch (error) {
@@ -2731,12 +2732,19 @@
}
async function continueReviewConflictMerge() {
if (reviewConflictPhase !== "ready-to-continue" || hasConflicts || !mergeInProgress) return;
if (!reviewConflictRequestId || hasConflicts || !mergeInProgress || isBusy) return;
reviewConflictPhase = "ready-to-continue";
reviewConflictMessage = appLanguage === "de" ? "Der Merge-Commit wird erstellt …" : "Creating the merge commit …";
await continueMerge();
activeView = "review-center";
await tick();
if (!errorMessage && !mergeInProgress) {
reviewConflictPhase = "ready-to-push";
reviewConflictMessage = appLanguage === "de" ? "Der Merge ist abgeschlossen. Pushe den Quellbranch, damit der PR aktualisiert wird." : "The merge is complete. Push the source branch to update the pull request.";
await tick();
await pushReviewConflictResolution();
} else if (errorMessage) {
reviewConflictMessage = appLanguage === "de" ? `Der Merge-Commit konnte nicht erstellt werden: ${errorMessage}` : `The merge commit could not be created: ${errorMessage}`;
}
}
@@ -5273,6 +5281,7 @@
async function applyPreparedResolutions() {
if (!activeRepoPath || isBusy || Object.keys(preparedResolutions).length === 0) return;
const entries = Object.entries(preparedResolutions);
let completeReviewMerge = false;
await runOperation(`Resolving ${entries.length} ${entries.length === 1 ? "file" : "files"}`, async () => {
let nextStatus: GitStatus | null = null;
for (const [file, prepared] of entries) {
@@ -5291,6 +5300,12 @@
resolveDialogOpen = false;
conflict = null;
conflictTarget = "";
completeReviewMerge = Boolean(
reviewConflictRequestId
&& reviewConflictRepoPath
&& sameRepoPath(activeRepoPath, reviewConflictRepoPath)
&& nextStatus?.merge_in_progress,
);
} else {
await loadConflict(remaining[0].path);
}
@@ -5299,6 +5314,12 @@
remaining: remaining.length,
});
});
if (completeReviewMerge && !errorMessage) {
reviewConflictPhase = "ready-to-continue";
activeView = "review-center";
await tick();
await continueReviewConflictMerge();
}
}
// ── Event handlers ─────────────────────────────────────────────────────────