Avoid duplicate PR recovery refresh work

This commit is contained in:
Brennan Benson
2026-07-15 01:16:40 -07:00
parent fa2a8f5e91
commit 567112fc59
5 changed files with 110 additions and 1 deletions
@@ -2225,6 +2225,71 @@ describe('createGitHubSlice.fetchPRForBranch', () => {
expect(store.getState().prCache[`${repoId}::${branch}`]).toBeUndefined()
})
it('unlinks a stale open PR and re-resolves the current branch with one follow-up lookup', async () => {
const store = createTestStore()
const repoPath = '/repo'
const repoId = 'repo-1'
const branch = 'feature/current'
const worktreeId = 'wt-stale-open-pr'
const stalePR = makePR({
number: 12,
title: 'Stale linked PR',
headSha: 'old-head',
headRefName: 'feature/old'
})
const currentPR = makePR({
number: 13,
title: 'Current branch PR',
headSha: 'current-head',
headRefName: branch
})
const updateWorktreeMeta = installLinkedPRClearStub(store, {
repoId,
repoPath,
branch,
worktree: makePRRefreshWorktree({
id: worktreeId,
repoId,
branch,
head: 'current-head',
linkedPR: 12
})
})
mockApi.gh.refreshPRNow
.mockResolvedValueOnce({ kind: 'found', pr: stalePR, fetchedAt: 2 })
.mockResolvedValueOnce({ kind: 'found', pr: currentPR, fetchedAt: 3 })
await expect(
store.getState().fetchPRForBranch(repoPath, branch, {
force: true,
repoId,
worktreeId,
linkedPRNumber: 12
})
).resolves.toEqual(stalePR)
await vi.waitFor(() => {
expect(mockApi.gh.refreshPRNow).toHaveBeenCalledTimes(2)
expect(store.getState().prCache[`${repoId}::${branch}`]?.data).toEqual(currentPR)
})
expect(updateWorktreeMeta).toHaveBeenCalledWith(
worktreeId,
{ linkedPR: null },
{
suppressHostedReviewRefresh: true,
shouldApply: expect.any(Function)
}
)
expect(mockApi.gh.refreshPRNow.mock.calls[1]?.[0]).toMatchObject({
candidate: expect.objectContaining({
branch,
linkedPRNumber: null,
worktreeId
})
})
expect(store.getState().worktreesByRepo[repoId]?.[0]?.linkedPR).toBeNull()
})
it('clears a linked merged PR on a fresh cache hit that already carries a head-scoped divergence signal', async () => {
const store = createTestStore()
const repoPath = '/repo'
+3
View File
@@ -3281,6 +3281,9 @@ export const createGitHubSlice: StateCreator<AppState, [], [], GitHubSlice> = (s
options.worktreeId,
{ linkedPR: null },
{
// Why: the branch-scoped PR refetch below updates both GitHub
// caches; the generic metadata refresh would duplicate provider work.
suppressHostedReviewRefresh: true,
shouldApply: () =>
shouldApplyBranchMismatchedLinkedPRClear({
worktree:
@@ -42,6 +42,8 @@ export type WorktreeMetaUpdateGuard = (worktree: Worktree | DetectedWorktree | u
export type WorktreeMetaUpdateOptions = {
shouldApply?: WorktreeMetaUpdateGuard
/** Skip the automatic review refetch when the caller owns an equivalent refresh. */
suppressHostedReviewRefresh?: boolean
}
export type WorktreeRenameRequest = {
@@ -4474,6 +4474,40 @@ describe('worktree remote runtime mutations', () => {
expect(store.getState().worktreesByRepo.repo1[0]?.pushTarget).toBeUndefined()
})
it('skips duplicate hosted-review work when the unlinking caller owns the refresh', async () => {
const store = createTestStore()
const fetchHostedReviewForBranch = vi.fn().mockResolvedValue(null)
const wt = makeWorktree({
id: 'repo1::/path/wt1',
repoId: 'repo1',
path: '/path/wt1',
branch: 'refs/heads/review-branch',
linkedPR: 2548,
pushTarget: { remoteName: 'fork', branchName: 'owner/old-pr' }
})
store.setState({
repos: [
{ id: 'repo1', path: '/repo1', displayName: 'Repo 1', badgeColor: '#000', addedAt: 0 }
],
worktreesByRepo: { repo1: [wt] },
fetchHostedReviewForBranch
} as Partial<AppState>)
await store
.getState()
.updateWorktreeMeta(wt.id, { linkedPR: null }, { suppressHostedReviewRefresh: true })
expect(fetchHostedReviewForBranch).not.toHaveBeenCalled()
expect(mockApi.worktrees.updateMeta).toHaveBeenCalledWith({
worktreeId: wt.id,
updates: { linkedPR: null, pushTarget: undefined }
})
expect(store.getState().worktreesByRepo.repo1[0]).toMatchObject({
linkedPR: null,
pushTarget: undefined
})
})
it('clears an older GitHub link and target when replacing it with a GitLab MR', async () => {
const store = createTestStore()
const oldPushTarget = { remoteName: 'fork', branchName: 'owner/old-pr' }
+6 -1
View File
@@ -3967,7 +3967,12 @@ export const createWorktreeSlice: StateCreator<AppState, [], [], WorktreeSlice>
try {
await persistWorktreeMeta(settingsForWorktreeOwner(get(), worktreeId), worktreeId, enriched)
if (reviewRepo && reviewBranch && typeof get().fetchHostedReviewForBranch === 'function') {
if (
!options?.suppressHostedReviewRefresh &&
reviewRepo &&
reviewBranch &&
typeof get().fetchHostedReviewForBranch === 'function'
) {
// Why: the old cache entry may have been populated by the previous
// provider link. Refetch against the post-update links so stale lookups
// cannot keep showing the removed review.