mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 16:02:42 +00:00
fix: address pr-bug-scan findings from #1444
Threaded linkedPRNumber through all ChecksPanel force-refresh paths; gated useComposerState's PR-URL-from-name recovery on selected repo slug match; wrapped main-process linkedPR fallback gh pr view i
This commit is contained in:
@@ -941,8 +941,16 @@ export async function getPRForBranch(
|
||||
'--json',
|
||||
'number,title,state,url,statusCheckRollup,updatedAt,isDraft,mergeable,baseRefName,headRefName,baseRefOid,headRefOid'
|
||||
]
|
||||
const { stdout } = await ghExecFileAsync(args, { cwd: repoPath })
|
||||
data = JSON.parse(stdout)
|
||||
try {
|
||||
const { stdout } = await ghExecFileAsync(args, { cwd: repoPath })
|
||||
data = JSON.parse(stdout)
|
||||
} catch {
|
||||
// Why: a stale linkedPRNumber (PR deleted, wrong repo, …) makes
|
||||
// `gh pr view <number>` reject. Treat that as the no-PR case so
|
||||
// callers see the historical `null` semantics instead of a thrown
|
||||
// error every poll cycle.
|
||||
data = null
|
||||
}
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
|
||||
@@ -105,8 +105,8 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
// them so we don't keep rendering cached branch summaries or empty file
|
||||
// lists from an older payload.
|
||||
conflictSummaryRefreshKeyRef.current = refreshKey
|
||||
void fetchPRForBranch(repo.path, branch, { force: true })
|
||||
}, [repo, isFolder, branch, pr, fetchPRForBranch])
|
||||
void fetchPRForBranch(repo.path, branch, { force: true, linkedPRNumber: linkedPR })
|
||||
}, [repo, isFolder, branch, pr, linkedPR, fetchPRForBranch])
|
||||
|
||||
// Fetch checks via cached store method
|
||||
const fetchChecks = useCallback(
|
||||
@@ -235,7 +235,10 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
}
|
||||
setIsRefreshing(true)
|
||||
try {
|
||||
const refreshedPR = await fetchPRForBranch(repo.path, branch, { force: true })
|
||||
const refreshedPR = await fetchPRForBranch(repo.path, branch, {
|
||||
force: true,
|
||||
linkedPRNumber: linkedPR
|
||||
})
|
||||
if (refreshedPR) {
|
||||
await Promise.all([
|
||||
fetchChecks({ force: true, prNumberOverride: refreshedPR.number }),
|
||||
@@ -248,7 +251,7 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
} finally {
|
||||
setIsRefreshing(false)
|
||||
}
|
||||
}, [repo, branch, fetchPRForBranch, fetchChecks, fetchComments])
|
||||
}, [repo, branch, linkedPR, fetchPRForBranch, fetchChecks, fetchComments])
|
||||
|
||||
const handleStartEdit = useCallback(() => {
|
||||
if (!pr) {
|
||||
@@ -278,13 +281,13 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
})
|
||||
if (ok) {
|
||||
// Re-fetch PR to get updated title
|
||||
await fetchPRForBranch(repo.path, branch, { force: true })
|
||||
await fetchPRForBranch(repo.path, branch, { force: true, linkedPRNumber: linkedPR })
|
||||
}
|
||||
} finally {
|
||||
setTitleSaving(false)
|
||||
setEditingTitle(false)
|
||||
}
|
||||
}, [repo, pr, titleDraft, branch, fetchPRForBranch])
|
||||
}, [repo, pr, titleDraft, branch, linkedPR, fetchPRForBranch])
|
||||
|
||||
const handleTitleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
@@ -318,9 +321,9 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
// Refresh PR (passed to PRActions)
|
||||
const handleRefreshPR = useCallback(async () => {
|
||||
if (repo && branch) {
|
||||
await fetchPRForBranch(repo.path, branch, { force: true })
|
||||
await fetchPRForBranch(repo.path, branch, { force: true, linkedPRNumber: linkedPR })
|
||||
}
|
||||
}, [repo, branch, fetchPRForBranch])
|
||||
}, [repo, branch, linkedPR, fetchPRForBranch])
|
||||
|
||||
// Open PR in browser
|
||||
const handleOpenPR = useCallback(() => {
|
||||
|
||||
@@ -358,6 +358,40 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
agentPromptRef.current = agentPrompt
|
||||
|
||||
const selectedRepo = eligibleRepos.find((repo) => repo.id === repoId)
|
||||
|
||||
// Why: resolves the selected repo's owner/repo slug so a PR URL pasted
|
||||
// into the workspace name field can be matched against the current repo.
|
||||
// Pasting a PR URL from a different repo would otherwise recover only the
|
||||
// PR number, mislinking the worktree to an unrelated PR with the same
|
||||
// number in the selected repo.
|
||||
const [selectedRepoSlug, setSelectedRepoSlug] = useState<{ owner: string; repo: string } | null>(
|
||||
null
|
||||
)
|
||||
useEffect(() => {
|
||||
if (!selectedRepo) {
|
||||
setSelectedRepoSlug(null)
|
||||
return
|
||||
}
|
||||
let cancelled = false
|
||||
void (window.api.gh.repoSlug({ repoPath: selectedRepo.path }) as Promise<{
|
||||
owner: string
|
||||
repo: string
|
||||
} | null>)
|
||||
.then((result) => {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
setSelectedRepoSlug(result)
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setSelectedRepoSlug(null)
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [selectedRepo?.id, selectedRepo?.path])
|
||||
const sparsePresetsForRepo = sparsePresetsByRepo[repoId]
|
||||
const sparsePresets = sparsePresetsForRepo ?? EMPTY_SPARSE_PRESETS
|
||||
const normalizedSparseDirectories = useMemo(
|
||||
@@ -412,10 +446,21 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
}
|
||||
const fromName = parseGitHubIssueOrPRLink(name)
|
||||
if (fromName && fromName.type === 'pr') {
|
||||
return fromName.number
|
||||
// Why: only adopt a number when the URL's owner/repo matches the
|
||||
// selected repo. Pasting `github.com/other/repo/pull/1234` must not
|
||||
// mislink the worktree to an unrelated PR #1234 in the current repo.
|
||||
// If the slug hasn't resolved yet, suppress recovery rather than
|
||||
// risking a cross-repo mislink.
|
||||
if (
|
||||
selectedRepoSlug &&
|
||||
fromName.slug.owner.toLowerCase() === selectedRepoSlug.owner.toLowerCase() &&
|
||||
fromName.slug.repo.toLowerCase() === selectedRepoSlug.repo.toLowerCase()
|
||||
) {
|
||||
return fromName.number
|
||||
}
|
||||
}
|
||||
return null
|
||||
}, [linkedPR, name])
|
||||
}, [linkedPR, name, selectedRepoSlug])
|
||||
const setupConfig = useMemo(
|
||||
() => getSetupConfig(selectedRepo, yamlHooks),
|
||||
[selectedRepo, yamlHooks]
|
||||
|
||||
Reference in New Issue
Block a user