mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 16:03:26 +00:00
Block mobile PR creation on unresolved conflicts and refresh status
Prevent creating a hosted review on mobile when there are unresolved merge conflicts. Also, return the latest git status on failures and reload it in the UI to keep the source control screen in sync.
This commit is contained in:
Generated
+26
@@ -1721,48 +1721,56 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-arm64-musl@0.52.0':
|
||||
resolution: {integrity: sha512-wZg6bLjDvh2KibyI3QFUYo8GTXneIFsd0JvehtvJiUmQ8WRPERgxd/VM4ctWb86U5FT1FkqgS8/wZKVB+AZScg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.52.0':
|
||||
resolution: {integrity: sha512-IngE8uxhNvxcMrLjZNDo9xNLY7rEK33AKnaMd2B46he1e/mz2CfcW6If/U1wUjdRZddm1QzQaciqZkuMkdh1FA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.52.0':
|
||||
resolution: {integrity: sha512-H3+DdFMv/efN3Efmhsv18jDrpiWWqKG7wsfAlQBqAt6z/E2Bx+TwEj2Nowe51CPOWB8/mFBC2dAMSgVFLvvowA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.52.0':
|
||||
resolution: {integrity: sha512-zji+1kb7lJKohSDjzC1IsS+K/cKRs1hdVf0ZH0VbdbiakmtLvN9twBoXo/k8VdjFax7kfo+DyPxS7vv52br1aw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.52.0':
|
||||
resolution: {integrity: sha512-hcLBYedpCy7ToUvvBidWk7+11Yhg1oAZ4+6hKPic/mQI6NaqXJSXMps5nFlwUuX2ewhtLZZDPg63TI042qGKBg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-gnu@0.52.0':
|
||||
resolution: {integrity: sha512-IDO2loXK2OtTOhSPchU9MW25mWL2QCDGdJbjN8MXKZVS80qXe5gMTwQWu/gMJ3juoBHbkuUZNB2N1LHzNT7DoA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-musl@0.52.0':
|
||||
resolution: {integrity: sha512-mAV2Hjn0SatJ+KoAzKUC3eJhdJ8wv+3m1KyuS0dTsbF0c5weq+QrCt/DRZZM+uj/XiKzCDEUKYsBF30e2qkcyw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-openharmony-arm64@0.52.0':
|
||||
resolution: {integrity: sha512-vd4npaUIwChxp7XzkqmepBWTT9YMcSe/NBApVGPC30/lLyOVaV3dvma1SKo03t8O73BPRAG7EyJzGlN5cJM5hQ==}
|
||||
@@ -1835,48 +1843,56 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-arm64-musl@1.67.0':
|
||||
resolution: {integrity: sha512-zB/Tf6sUjmmvvbva9Gj3JTJ8rJ9t4I8/U0o6vSRtd0DRIsIuyegBwJAzhSUFQHdMijIRJkW0exs/yBhpw2S20w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.67.0':
|
||||
resolution: {integrity: sha512-kgU40Gt74CK0TCsF51KZymkIwN9U0BajKsMijB52zPqOeZU9NAHkA/NSQkZDHEaCakx42DxhXkODiAqf2b4Gug==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.67.0':
|
||||
resolution: {integrity: sha512-tOYhkk/iaG9aD3FvGpBFd1Lrw0x0RaVoJBxjUkfNzS50rC5NS5BteNCwgr8A2zCdADrIIoze6D7u6U5Ic++/iQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.67.0':
|
||||
resolution: {integrity: sha512-sEtywrPb+0b+tHYl1SDCrw903fiC4eyKoNqzP3v+f2JT3Xcv4NEYG+P8rj+eEnX7IWhqV/xj8/JmcmVj21CXaA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.67.0':
|
||||
resolution: {integrity: sha512-BvR8Moa0zCLxroOx4vZaZN9nUfwAUpSTwjZdxZyKy4bv3PrzrXrxKR/ZQ0L9wNSvlPhnMJeZfa3q5w6ZCTuN6Q==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.67.0':
|
||||
resolution: {integrity: sha512-mm2cxM6fksOpq6l0uFws8BUGKAR4dNa/cZCn37Npq7PFbhD5HDJqWfnoIvTaeRKMy5XdS2tO0MA0qbHDrnXAAA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-musl@1.67.0':
|
||||
resolution: {integrity: sha512-WmbMuLapKyDlobMkXAaAL0Y+Uczh4LETfIfQsUpbId4Ip8Ai82/jqeYTOoUCkuuhBFapgqP253+d83tLKOksJg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-openharmony-arm64@1.67.0':
|
||||
resolution: {integrity: sha512-9g/PqxYJelzzTAOR5Y+RiRqdeydhEuXv2KxNeFcAKQ7UsvnWSY1OP4MsuPMbTO2Pf70tz7mFhl1j13H3fyh+8g==}
|
||||
@@ -2338,36 +2354,42 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0':
|
||||
resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-linux-ppc64-gnu@1.0.0':
|
||||
resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-s390x-gnu@1.0.0':
|
||||
resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0':
|
||||
resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0':
|
||||
resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0':
|
||||
resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==}
|
||||
@@ -4629,24 +4651,28 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
|
||||
@@ -25,6 +25,10 @@ function entry(area: 'unstaged' | 'untracked' | 'staged') {
|
||||
return { path: 'a.ts', status: 'modified', area }
|
||||
}
|
||||
|
||||
function unresolvedEntry(area: 'unstaged' | 'staged') {
|
||||
return { path: 'conflicted.ts', status: 'modified', area, conflictStatus: 'unresolved' }
|
||||
}
|
||||
|
||||
function eligibility(overrides: Record<string, unknown>) {
|
||||
return {
|
||||
provider: 'github',
|
||||
@@ -139,7 +143,11 @@ describe('prepareMobileHostedReviewCreateIntent', () => {
|
||||
})
|
||||
).resolves.toEqual({
|
||||
ok: false,
|
||||
error: 'Could not generate a commit message. Add one in Source Control, then retry.'
|
||||
error: 'Could not generate a commit message. Add one in Source Control, then retry.',
|
||||
committed: false,
|
||||
status: expect.objectContaining({
|
||||
entries: [expect.objectContaining({ area: 'staged' })]
|
||||
})
|
||||
})
|
||||
|
||||
expect(client.calls.map((call) => call.method)).toEqual([
|
||||
@@ -147,4 +155,26 @@ describe('prepareMobileHostedReviewCreateIntent', () => {
|
||||
'git.generateCommitMessage'
|
||||
])
|
||||
})
|
||||
|
||||
it('blocks unresolved conflicts before attempting a commit', async () => {
|
||||
const client = clientWith([ok(status([entry('staged'), unresolvedEntry('unstaged')]))])
|
||||
|
||||
await expect(
|
||||
prepareMobileHostedReviewCreateIntent(client, 'repo-1::/tmp/wt', {
|
||||
branch: 'feature/x',
|
||||
title: 'feature/x',
|
||||
status: null,
|
||||
commitMessage: 'Use my message'
|
||||
})
|
||||
).resolves.toEqual({
|
||||
ok: false,
|
||||
error: 'Resolve conflicts before creating a pull request.',
|
||||
committed: false,
|
||||
status: expect.objectContaining({
|
||||
entries: expect.arrayContaining([expect.objectContaining({ path: 'conflicted.ts' })])
|
||||
})
|
||||
})
|
||||
|
||||
expect(client.calls.map((call) => call.method)).toEqual(['git.status'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -72,6 +72,10 @@ function branchStillMatches(inputBranch: string, status: MobileGitStatusResult |
|
||||
)
|
||||
}
|
||||
|
||||
function hasUnresolvedConflicts(status: MobileGitStatusResult | null): boolean {
|
||||
return status?.entries.some((entry) => entry.conflictStatus === 'unresolved') === true
|
||||
}
|
||||
|
||||
async function sendGitMutation(
|
||||
client: Pick<RpcClient, 'sendRequest'>,
|
||||
method: string,
|
||||
@@ -138,6 +142,14 @@ async function ensureLocalChangesCommitted(
|
||||
if ((currentStatus?.entries.length ?? 0) === 0) {
|
||||
return { ok: true, status: currentStatus, committed: false }
|
||||
}
|
||||
if (hasUnresolvedConflicts(currentStatus)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Resolve conflicts before creating a pull request.',
|
||||
committed: false,
|
||||
status: currentStatus
|
||||
}
|
||||
}
|
||||
|
||||
const stagePaths = getStageablePaths(currentStatus?.entries ?? [])
|
||||
if (stagePaths.length > 0) {
|
||||
@@ -153,13 +165,23 @@ async function ensureLocalChangesCommitted(
|
||||
}
|
||||
currentStatus = await readStatus(client, worktreeId)
|
||||
if (!branchStillMatches(input.branch, currentStatus)) {
|
||||
return { ok: false, error: 'Branch changed while preparing the pull request.' }
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Branch changed while preparing the pull request.',
|
||||
committed: false,
|
||||
status: currentStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasStagedChanges = currentStatus?.entries.some((entry) => entry.area === 'staged') === true
|
||||
if (!hasStagedChanges) {
|
||||
return { ok: false, error: 'Resolve or stage changes before creating a pull request.' }
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Resolve or stage changes before creating a pull request.',
|
||||
committed: false,
|
||||
status: currentStatus
|
||||
}
|
||||
}
|
||||
|
||||
let message = input.commitMessage?.trim() ?? ''
|
||||
@@ -169,7 +191,9 @@ async function ensureLocalChangesCommitted(
|
||||
if (!generated.success) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Could not generate a commit message. Add one in Source Control, then retry.'
|
||||
error: 'Could not generate a commit message. Add one in Source Control, then retry.',
|
||||
committed: false,
|
||||
status: currentStatus
|
||||
}
|
||||
}
|
||||
message = generated.message
|
||||
@@ -178,7 +202,7 @@ async function ensureLocalChangesCommitted(
|
||||
input.onProgress?.('committing')
|
||||
const committed = await commitStagedChanges(client, worktreeId, message)
|
||||
if (!committed.ok) {
|
||||
return committed
|
||||
return { ...committed, committed: false, status: currentStatus }
|
||||
}
|
||||
currentStatus = await readStatus(client, worktreeId)
|
||||
if (!branchStillMatches(input.branch, currentStatus)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, type MutableRefObject } from 'react'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { triggerError } from '../platform/haptics'
|
||||
import type { MobileGitStatusResult } from './mobile-git-status'
|
||||
import type { LoadStatusOptions } from './mobile-source-control-screen-state'
|
||||
import {
|
||||
mobileHostedReviewCreateIntentProgressMessage,
|
||||
type MobileHostedReviewCreateIntentProgress
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from './mobile-hosted-review-create-intent-runner'
|
||||
|
||||
type RunGitWorkflow = (actionId: string, runner: () => Promise<void>) => Promise<boolean>
|
||||
type LoadStatus = (options?: LoadStatusOptions) => Promise<boolean>
|
||||
|
||||
type Params = {
|
||||
client: RpcClient | null
|
||||
@@ -21,6 +23,7 @@ type Params = {
|
||||
commitMessage: string
|
||||
mountedRef: MutableRefObject<boolean>
|
||||
runGitWorkflow: RunGitWorkflow
|
||||
loadStatus: LoadStatus
|
||||
setActionError: (next: string | null) => void
|
||||
setCommitMessage: (next: string) => void
|
||||
setShowActionSheet: (next: boolean) => void
|
||||
@@ -36,6 +39,7 @@ export function useMobileCreatePrRunner({
|
||||
commitMessage,
|
||||
mountedRef,
|
||||
runGitWorkflow,
|
||||
loadStatus,
|
||||
setActionError,
|
||||
setCommitMessage,
|
||||
setShowActionSheet,
|
||||
@@ -71,6 +75,13 @@ export function useMobileCreatePrRunner({
|
||||
if (outcome?.committed && mountedRef.current) {
|
||||
setCommitMessage('')
|
||||
}
|
||||
if (!ran && outcome?.status !== undefined && mountedRef.current) {
|
||||
await loadStatus({
|
||||
preserveReadyOnFailure: true,
|
||||
clearActionErrorOnSuccess: false,
|
||||
force: true
|
||||
})
|
||||
}
|
||||
if (!ran || !mountedRef.current || !outcome || !outcome.ok) {
|
||||
return
|
||||
}
|
||||
@@ -82,6 +93,7 @@ export function useMobileCreatePrRunner({
|
||||
branchLabel,
|
||||
client,
|
||||
commitMessage,
|
||||
loadStatus,
|
||||
mountedRef,
|
||||
runGitWorkflow,
|
||||
setActionError,
|
||||
|
||||
@@ -191,6 +191,7 @@ export function useMobileSourceControlRunners(params: Params) {
|
||||
commitMessage,
|
||||
mountedRef,
|
||||
runGitWorkflow,
|
||||
loadStatus,
|
||||
setActionError,
|
||||
setCommitMessage,
|
||||
setShowActionSheet,
|
||||
|
||||
Reference in New Issue
Block a user