diff --git a/mobile/src/source-control/mobile-source-control-primary-action.test.ts b/mobile/src/source-control/mobile-source-control-primary-action.test.ts index 39229161525..0b127d9bde3 100644 --- a/mobile/src/source-control/mobile-source-control-primary-action.test.ts +++ b/mobile/src/source-control/mobile-source-control-primary-action.test.ts @@ -245,6 +245,23 @@ describe('mobile source-control primary action decision parity', () => { inFlightRemoteOpKind: 'pull', upstreamStatus: { hasUpstream: true, ahead: 2, behind: 0 } }) + }, + { + name: 'in-flight force push mirrors force push', + input: decisionInputs({ + isRemoteOperationActive: true, + inFlightRemoteOpKind: 'force_push', + upstreamStatus: { hasUpstream: true, ahead: 2, behind: 0 } + }) + }, + { + name: 'in-flight push blocks committable candidate', + input: decisionInputs({ + isRemoteOperationActive: true, + inFlightRemoteOpKind: 'push', + stagedCount: 1, + hasMessage: true + }) } ])('matches the shared commit-area decision for $name', ({ input }) => { expect(resolveMobileSourceControlCommitAreaPrimaryActionDecision(input)).toEqual( diff --git a/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-state.ts b/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-state.ts index 2a8236dc686..b40976642e1 100644 --- a/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-state.ts +++ b/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-state.ts @@ -5,6 +5,8 @@ import { type CreateReviewIntentKind } from '../../../../shared/source-control-create-review-intent' +// Why: renderer APIs keep PR terminology for compatibility, while shared logic +// uses provider-neutral review terminology for PR/MR hosts. export type CreatePrIntentKind = CreateReviewIntentKind export type CreatePrIntentEligibility = CreateReviewIntentEligibility diff --git a/src/renderer/src/components/right-sidebar/source-control-primary-action.ts b/src/renderer/src/components/right-sidebar/source-control-primary-action.ts index 1eee270ee09..d2a134019d1 100644 --- a/src/renderer/src/components/right-sidebar/source-control-primary-action.ts +++ b/src/renderer/src/components/right-sidebar/source-control-primary-action.ts @@ -152,9 +152,15 @@ function resolvePrimaryActionTitle( { value0: resolvePrimaryActionLabel(decision, inputs) } ) case 'remote_operation_in_progress': - return 'Remote operation in progress…' + return translate( + 'auto.components.right.sidebar.source.control.primary.action.6f7a8b9c0d', + 'Remote operation in progress…' + ) case 'remote_operation_blocks_commit': - return 'Remote operation in progress — try again once it finishes' + return translate( + 'auto.components.right.sidebar.source.control.primary.action.7f8a9b0c1d', + 'Remote operation in progress — try again once it finishes' + ) case 'resolve_conflicts_before_commit': return translate( 'auto.components.right.sidebar.source.control.primary.action.a6457b46a7', @@ -237,7 +243,10 @@ function resolvePrimaryActionTitle( { value0: copy.reviewLabel } ) case 'nothing_to_commit_up_to_date': - return 'Nothing to commit. Branch is up to date.' + return translate( + 'auto.components.right.sidebar.source.control.primary.action.8f9a0b1c2d', + 'Nothing to commit. Branch is up to date.' + ) case 'checking_review_creation': return translate( 'auto.components.right.sidebar.source.control.primary.action.h3i4j5k607', diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 7e004df6698..62deabac88c 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -9061,6 +9061,8 @@ "2d8f185fbc": "Stage all changes before committing partially staged files", "a6457b46a7": "Resolve conflicts before committing", "484f45c439": "{{value0}} in progress…", + "6f7a8b9c0d": "Remote operation in progress…", + "7f8a9b0c1d": "Remote operation in progress — try again once it finishes", "74fc171e99": "Force Push in progress…", "16aee3a5c1": "Commit in progress…", "e61b0d7a3c": "Check out a branch before publishing commits.", @@ -9080,6 +9082,7 @@ "d8a4c0e369": "Authenticate before creating a {{value0}}.", "e9b5d1f470": "Check out a branch before creating a {{value0}}.", "f0c6e2a581": "This branch is not ready for a {{value0}} yet.", + "8f9a0b1c2d": "Nothing to commit. Branch is up to date.", "h3i4j5k607": "Checking whether this branch can create a {{value0}}…" } } diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index d01aecb6416..2a27e147585 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -9061,6 +9061,8 @@ "2d8f185fbc": "Prepare todos los cambios antes de enviar archivos parcialmente preparados", "a6457b46a7": "Resolver conflictos antes de comprometerse", "484f45c439": "{{value0}} en progreso…", + "6f7a8b9c0d": "Remote operation in progress…", + "7f8a9b0c1d": "Remote operation in progress — try again once it finishes", "74fc171e99": "Empuje forzado en progreso...", "16aee3a5c1": "Compromiso en progreso...", "e61b0d7a3c": "Cambia a una rama antes de publicar commits.", @@ -9080,6 +9082,7 @@ "d8a4c0e369": "Authenticate before creating a {{value0}}.", "e9b5d1f470": "Check out a branch before creating a {{value0}}.", "f0c6e2a581": "This branch is not ready for a {{value0}} yet.", + "8f9a0b1c2d": "Nothing to commit. Branch is up to date.", "h3i4j5k607": "Checking whether this branch can create a {{value0}}…" } } diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 138e10813fd..c2f6da8e9ac 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -9061,6 +9061,8 @@ "2d8f185fbc": "部分的にステージングされたファイルを commit する前に、すべての変更をステージングします。", "a6457b46a7": "commit する前に競合を解決する", "484f45c439": "{{value0}} が進行中です…", + "6f7a8b9c0d": "Remote operation in progress…", + "7f8a9b0c1d": "Remote operation in progress — try again once it finishes", "74fc171e99": "強制プッシュ中です…", "16aee3a5c1": "Commit 中です…", "e61b0d7a3c": "commits を公開する前にブランチをチェックアウトしてください。", @@ -9080,6 +9082,7 @@ "d8a4c0e369": "Authenticate before creating a {{value0}}.", "e9b5d1f470": "Check out a branch before creating a {{value0}}.", "f0c6e2a581": "This branch is not ready for a {{value0}} yet.", + "8f9a0b1c2d": "Nothing to commit. Branch is up to date.", "h3i4j5k607": "Checking whether this branch can create a {{value0}}…" } } diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 64bf5cd45da..cfae0b73f10 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -9061,6 +9061,8 @@ "2d8f185fbc": "부분적으로 준비된 파일을 commit 하기 전에 모든 변경 사항을 준비합니다.", "a6457b46a7": "commit 하기 전에 충돌을 해결하세요.", "484f45c439": "{{value0}} 진행 중…", + "6f7a8b9c0d": "Remote operation in progress…", + "7f8a9b0c1d": "Remote operation in progress — try again once it finishes", "74fc171e99": "강제 푸시 진행 중…", "16aee3a5c1": "Commit 진행 중…", "e61b0d7a3c": "commits을 게시하기 전에 브랜치를 체크아웃하세요.", @@ -9080,6 +9082,7 @@ "d8a4c0e369": "{{value0}}을(를) 만들기 전에 인증하세요.", "e9b5d1f470": "{{value0}}을(를) 만들기 전에 브랜치를 체크아웃하세요.", "f0c6e2a581": "이 브랜치는 아직 {{value0}}을(를) 만들 준비가 되지 않았습니다.", + "8f9a0b1c2d": "Nothing to commit. Branch is up to date.", "h3i4j5k607": "이 브랜치에서 {{value0}}을(를) 만들 수 있는지 확인 중…" } } diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 1e46ef2fb90..3c6d01d5eae 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -9061,6 +9061,8 @@ "2d8f185fbc": "在提交部分暂存的文件之前暂存所有更改", "a6457b46a7": "提交前解决冲突", "484f45c439": "{{value0}} 正在进行中...", + "6f7a8b9c0d": "Remote operation in progress…", + "7f8a9b0c1d": "Remote operation in progress — try again once it finishes", "74fc171e99": "强制推送正在进行中...", "16aee3a5c1": "正在进行中……", "e61b0d7a3c": "请先检出分支再发布 commits。", @@ -9080,6 +9082,7 @@ "d8a4c0e369": "Authenticate before creating a {{value0}}.", "e9b5d1f470": "Check out a branch before creating a {{value0}}.", "f0c6e2a581": "This branch is not ready for a {{value0}} yet.", + "8f9a0b1c2d": "Nothing to commit. Branch is up to date.", "h3i4j5k607": "Checking whether this branch can create a {{value0}}…" } } diff --git a/src/shared/source-control-primary-action-decision.test.ts b/src/shared/source-control-primary-action-decision.test.ts index dad1ee40e3f..1cdcc1193b5 100644 --- a/src/shared/source-control-primary-action-decision.test.ts +++ b/src/shared/source-control-primary-action-decision.test.ts @@ -157,4 +157,25 @@ describe('source-control primary action decision', () => { expect(resolveSourceControlPrimaryActionDecision(input).kind).toBe('create_pr') expect(resolveSourceControlCommitAreaPrimaryActionDecision(input).kind).toBe('commit') }) + + it('returns disabled create review while hosted-review creation eligibility is loading', () => { + const result = resolveSourceControlPrimaryActionDecision( + inputs({ + upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 }, + hostedReviewCreation: { + provider: 'gitlab', + review: null, + canCreate: false, + blockedReason: null, + nextAction: null + }, + isHostedReviewCreationLoading: true + }) + ) + expect(result).toMatchObject({ + kind: 'create_pr', + titleIntent: 'checking_review_creation', + disabled: true + }) + }) }) diff --git a/src/shared/source-control-primary-action-decision.ts b/src/shared/source-control-primary-action-decision.ts index e51cbdd2e05..acdaf27cbf8 100644 --- a/src/shared/source-control-primary-action-decision.ts +++ b/src/shared/source-control-primary-action-decision.ts @@ -1,4 +1,5 @@ import { shouldForcePushWithLeaseForUpstream } from './git-upstream-status' +import { supportsHostedReviewCreation } from './hosted-review-creation-providers' import { resolveCreateReviewIntentEligibility } from './source-control-create-review-intent' import { resolveSourceControlPrimaryActionDuringRemoteOp } from './source-control-primary-action-in-flight' import type { @@ -32,7 +33,8 @@ export function resolveSourceControlPrimaryActionDecision( branchCommitsAhead, hasCurrentBranch = true, canPushLinkedReviewWithoutUpstream = false, - isPrIntentInFlight = false + isPrIntentInFlight = false, + isHostedReviewCreationLoading = false } = inputs if (isPrIntentInFlight) { @@ -69,6 +71,19 @@ export function resolveSourceControlPrimaryActionDecision( } } + if ( + isHostedReviewCreationLoading && + hostedReviewCreation && + shouldOfferCreateReviewLoadingAction(hostedReviewCreation) + ) { + return { + kind: 'create_pr', + labelIntent: 'create_pr', + titleIntent: 'checking_review_creation', + disabled: true + } + } + const createPrIntent = resolveCreatePrIntentDecision(inputs) if (createPrIntent) { return createPrIntent @@ -192,6 +207,18 @@ export function resolveSourceControlPrimaryActionDecision( } } +function shouldOfferCreateReviewLoadingAction( + hostedReviewCreation: SourceControlPrimaryActionDecisionInputs['hostedReviewCreation'] +): boolean { + if (!supportsHostedReviewCreation(hostedReviewCreation?.provider)) { + return false + } + return ( + hostedReviewCreation.blockedReason !== 'existing_review' && + hostedReviewCreation.blockedReason !== 'unsupported_provider' + ) +} + export function resolveSourceControlCommitAreaPrimaryActionDecision( inputs: SourceControlPrimaryActionDecisionInputs ): SourceControlPrimaryActionDecision {