Use worktree path to resolve HEAD OID for merged PR lookups (#8349)
- Pass the active `worktreePath` through IPC, RPC, and the GitHub client to ensure we fetch the correct HEAD OID when resolving merged PRs. - Validate incoming worktree paths against known repository worktrees in the main process to prevent forged path usage. - Escalate Checks Panel PR refresh requests from 'swr' to 'active' when a cached "no PR" miss predates when the panel became visible.
This commit is contained in:
parent
4f9fbecb9e
commit
ff98936c4e
|
|
@ -124,6 +124,7 @@ import {
|
|||
shouldPollChecksPanelRuntimeSshStatus,
|
||||
type ChecksPanelGitStatusSnapshot
|
||||
} from './checks-panel-git-status-snapshot'
|
||||
import { resolveChecksPanelPRRefreshRequest } from './checks-panel-pr-refresh-request'
|
||||
import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
|
||||
|
|
@ -471,6 +472,7 @@ export default function ChecksPanel(): React.JSX.Element {
|
|||
const confirm = useConfirmationDialog()
|
||||
const prevChecksRef = useRef<string>('')
|
||||
const conflictSummaryRefreshKeyRef = useRef<string | null>(null)
|
||||
const panelVisibleSinceRef = useRef<number | null>(null)
|
||||
commentsRef.current = comments
|
||||
const prGenerationRecords = useAppStore((s) => s.pullRequestGenerationRecords)
|
||||
const allocatePullRequestGenerationRequestId = useAppStore(
|
||||
|
|
@ -652,6 +654,7 @@ export default function ChecksPanel(): React.JSX.Element {
|
|||
// the entry for the active repo and branch.
|
||||
const prCacheEntry = useAppStore((s) => selectReviewCacheEntry(s.prCache, prCacheKey || null))
|
||||
const pr: PRInfo | null = prCacheEntry?.data ?? null
|
||||
const prCachedHasPR = prCacheEntry ? prCacheEntry.data !== null : null
|
||||
const hostedReview = useAppStore((s) =>
|
||||
hostedReviewCacheKey ? (s.hostedReviewCache[hostedReviewCacheKey]?.data ?? null) : null
|
||||
)
|
||||
|
|
@ -736,6 +739,14 @@ export default function ChecksPanel(): React.JSX.Element {
|
|||
repo?.id
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPanelVisible) {
|
||||
panelVisibleSinceRef.current = null
|
||||
return
|
||||
}
|
||||
panelVisibleSinceRef.current = Date.now()
|
||||
}, [isPanelVisible, panelContextKey])
|
||||
|
||||
// Why: select only timestamps (not whole cache records) so the entry-refresh
|
||||
// effect doesn't re-run on every cache mutation. See
|
||||
// docs/refresh-on-checks-tab.md.
|
||||
|
|
@ -1221,7 +1232,12 @@ export default function ChecksPanel(): React.JSX.Element {
|
|||
staleWhileRevalidate: true
|
||||
})
|
||||
if (activeWorktreeId && !isGitLabReviewContext) {
|
||||
enqueueGitHubPRRefresh(activeWorktreeId, 'swr', 30)
|
||||
const refreshRequest = resolveChecksPanelPRRefreshRequest({
|
||||
cachedHasPR: prCachedHasPR,
|
||||
cachedFetchedAt: prFetchedAt ?? null,
|
||||
panelVisibleSince: panelVisibleSinceRef.current
|
||||
})
|
||||
enqueueGitHubPRRefresh(activeWorktreeId, refreshRequest.reason, refreshRequest.priority)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
|
@ -1239,6 +1255,8 @@ export default function ChecksPanel(): React.JSX.Element {
|
|||
linkedGiteaPR,
|
||||
linkedGitLabMR,
|
||||
linkedPR,
|
||||
prCachedHasPR,
|
||||
prFetchedAt,
|
||||
repo
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveChecksPanelPRRefreshRequest } from './checks-panel-pr-refresh-request'
|
||||
|
||||
describe('resolveChecksPanelPRRefreshRequest', () => {
|
||||
it('uses an active refresh for a cached miss from before the checks panel became visible', () => {
|
||||
expect(
|
||||
resolveChecksPanelPRRefreshRequest({
|
||||
cachedHasPR: false,
|
||||
cachedFetchedAt: 100,
|
||||
panelVisibleSince: 200
|
||||
})
|
||||
).toEqual({ reason: 'active', priority: 80 })
|
||||
})
|
||||
|
||||
it('keeps fresh empty lookups on the background path', () => {
|
||||
expect(
|
||||
resolveChecksPanelPRRefreshRequest({
|
||||
cachedHasPR: false,
|
||||
cachedFetchedAt: 200,
|
||||
panelVisibleSince: 100
|
||||
})
|
||||
).toEqual({ reason: 'swr', priority: 30 })
|
||||
})
|
||||
|
||||
it('keeps populated or unknown cache entries on the background path', () => {
|
||||
expect(
|
||||
resolveChecksPanelPRRefreshRequest({
|
||||
cachedHasPR: true,
|
||||
cachedFetchedAt: 100,
|
||||
panelVisibleSince: 200
|
||||
})
|
||||
).toEqual({ reason: 'swr', priority: 30 })
|
||||
|
||||
expect(
|
||||
resolveChecksPanelPRRefreshRequest({
|
||||
cachedHasPR: null,
|
||||
cachedFetchedAt: null,
|
||||
panelVisibleSince: 200
|
||||
})
|
||||
).toEqual({ reason: 'swr', priority: 30 })
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import type { GitHubPRRefreshReason } from '../../../../shared/types'
|
||||
|
||||
type ChecksPanelPRRefreshRequestInput = {
|
||||
cachedHasPR: boolean | null
|
||||
cachedFetchedAt: number | null
|
||||
panelVisibleSince: number | null
|
||||
}
|
||||
|
||||
type ChecksPanelPRRefreshRequest = {
|
||||
reason: GitHubPRRefreshReason
|
||||
priority: number
|
||||
}
|
||||
|
||||
export function resolveChecksPanelPRRefreshRequest(
|
||||
input: ChecksPanelPRRefreshRequestInput
|
||||
): ChecksPanelPRRefreshRequest {
|
||||
const cachedMissPredatesVisiblePanel =
|
||||
input.cachedHasPR === false &&
|
||||
input.cachedFetchedAt !== null &&
|
||||
input.panelVisibleSince !== null &&
|
||||
input.cachedFetchedAt < input.panelVisibleSince
|
||||
|
||||
if (cachedMissPredatesVisiblePanel) {
|
||||
// Why: external agents can create/merge a PR after Orca cached "none";
|
||||
// visible empty-state checks need one foreground lookup to recover.
|
||||
return { reason: 'active', priority: 80 }
|
||||
}
|
||||
|
||||
return { reason: 'swr', priority: 30 }
|
||||
}
|
||||
Loading…
Reference in New Issue