mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 00:03:20 +00:00
Avoid duplicate PR recovery refresh work
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user