Preserve slash branch names from branch composer (#6748)
Co-authored-by: Orca <help@stably.ai> Co-authored-by: Neil <charlie-eng@stably.ai>
This commit is contained in:
parent
ca36072295
commit
f7a4100bc7
|
|
@ -51,6 +51,7 @@ import SparseCheckoutPresetSelect from '@/components/sparse/SparseCheckoutPreset
|
|||
import SmartWorkspaceNameField, {
|
||||
type SmartWorkspaceNameSelection
|
||||
} from '@/components/new-workspace/SmartWorkspaceNameField'
|
||||
import type { SmartNameMode } from '@/components/new-workspace/smart-workspace-source-results'
|
||||
import ProjectCombobox from '@/components/new-workspace/ProjectCombobox'
|
||||
import type { SetupConfig } from '@/lib/new-workspace'
|
||||
import type { NewWorkspaceProjectOption } from '@/lib/new-workspace-project-options'
|
||||
|
|
@ -107,6 +108,7 @@ type NewWorkspaceComposerCardProps = {
|
|||
onSmartGitHubItemSelect: (item: GitHubWorkItem) => void
|
||||
onSmartGitLabItemSelect: (item: GitLabWorkItem) => void
|
||||
onSmartBranchSelect: (refName: string, localBranchName: string) => void
|
||||
onSmartNameModeChange?: (mode: SmartNameMode) => void
|
||||
onSmartLinearIssueSelect: (issue: LinearIssue) => void
|
||||
smartNameSelection: SmartWorkspaceNameSelection | null
|
||||
onClearSmartNameSelection: () => void
|
||||
|
|
@ -571,6 +573,7 @@ export default function NewWorkspaceComposerCard({
|
|||
onSmartGitHubItemSelect,
|
||||
onSmartGitLabItemSelect,
|
||||
onSmartBranchSelect,
|
||||
onSmartNameModeChange,
|
||||
onSmartLinearIssueSelect,
|
||||
smartNameSelection,
|
||||
onClearSmartNameSelection,
|
||||
|
|
@ -956,6 +959,7 @@ export default function NewWorkspaceComposerCard({
|
|||
repoBackedSearchRepos={repoBackedSearchRepos}
|
||||
allowCrossRepoProjectAdd={allowSmartNameAddProject}
|
||||
crossRepoSwitchTarget={smartNameRepoSwitchTarget}
|
||||
onActiveSourceModeChange={onSmartNameModeChange}
|
||||
onPlainEnter={() => {
|
||||
// Why: Enter on the workspace name advances focus to the next
|
||||
// field (Agent combobox) rather than submitting, letting the user
|
||||
|
|
|
|||
|
|
@ -92,6 +92,58 @@ describe('resolveComposerBranchSelection', () => {
|
|||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
it('uses a slash-containing typed branch name as the create override in branch mode', () => {
|
||||
expect(
|
||||
resolveComposerBranchNameOverrideForCreate({
|
||||
branchNameOverride: undefined,
|
||||
branchAutoName: '',
|
||||
workspaceName: 'feature/user-profile',
|
||||
preserveWorkspaceNameEdits: false,
|
||||
createBranchFromWorkspaceName: true
|
||||
})
|
||||
).toBe('feature/user-profile')
|
||||
})
|
||||
|
||||
it('keeps plain typed branch names on the existing sanitized-name path', () => {
|
||||
expect(
|
||||
resolveComposerBranchNameOverrideForCreate({
|
||||
branchNameOverride: undefined,
|
||||
branchAutoName: '',
|
||||
workspaceName: 'feature-user-profile',
|
||||
preserveWorkspaceNameEdits: false,
|
||||
createBranchFromWorkspaceName: true
|
||||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not preserve a slash typed name outside branch mode (gate off)', () => {
|
||||
// Why: only branch mode opts into slash preservation; every other mode keeps
|
||||
// deriving the branch from the sanitized workspace name.
|
||||
expect(
|
||||
resolveComposerBranchNameOverrideForCreate({
|
||||
branchNameOverride: undefined,
|
||||
branchAutoName: '',
|
||||
workspaceName: 'feature/user-profile',
|
||||
preserveWorkspaceNameEdits: false,
|
||||
createBranchFromWorkspaceName: false
|
||||
})
|
||||
).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps a resolver-provided override even in branch mode with a slash name', () => {
|
||||
// Why: a picked branch (override set) wins over the typed slash name so the
|
||||
// branch-mode gate never hijacks an explicit branch selection.
|
||||
expect(
|
||||
resolveComposerBranchNameOverrideForCreate({
|
||||
branchNameOverride: 'feature/picked',
|
||||
branchAutoName: 'feature/picked',
|
||||
workspaceName: 'feature/user-profile',
|
||||
preserveWorkspaceNameEdits: true,
|
||||
createBranchFromWorkspaceName: true
|
||||
})
|
||||
).toBe('feature/picked')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveComposerManualBranchNameChange', () => {
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ import { queueNewWorkspaceTerminalFocus } from '@/lib/new-workspace-terminal-foc
|
|||
import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner'
|
||||
import { getSuggestedCreatureName } from '@/components/sidebar/worktree-name-suggestions'
|
||||
import type { SmartWorkspaceNameSelection } from '@/components/new-workspace/SmartWorkspaceNameField'
|
||||
import type { SmartNameMode } from '@/components/new-workspace/smart-workspace-source-results'
|
||||
import { getForkPushWarning } from './fork-push-warning'
|
||||
import { CONTEXTUAL_TOUR_ENABLE_AUTO_WORKSPACE_NAME_EVENT } from '@/components/contextual-tours/contextual-tour-composer-events'
|
||||
import { ensureHooksConfirmed } from '@/lib/ensure-hooks-confirmed'
|
||||
|
|
@ -261,6 +262,7 @@ export type ComposerCardProps = {
|
|||
onSmartGitHubItemSelect: (item: GitHubWorkItem) => void
|
||||
onSmartGitLabItemSelect: (item: GitLabWorkItem) => void
|
||||
onSmartBranchSelect: (refName: string, localBranchName: string) => void
|
||||
onSmartNameModeChange?: (mode: SmartNameMode) => void
|
||||
onSmartLinearIssueSelect: (issue: LinearIssue) => void
|
||||
smartNameGitHubSourceContext?: TaskSourceContext | null
|
||||
/** GitLab parallel of onBaseBranchPrSelect. */
|
||||
|
|
@ -1024,6 +1026,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
|||
const [branchNameOverride, setBranchNameOverride] = useState<string | undefined>(undefined)
|
||||
const [branchNameOverridePreservesNameEdits, setBranchNameOverridePreservesNameEdits] =
|
||||
useState(false)
|
||||
const [smartNameMode, setSmartNameMode] = useState<SmartNameMode>('smart')
|
||||
// Why (#5181): when the user picks an existing LOCAL branch, let them reuse it
|
||||
// (check it out) instead of creating a new branch from it. `reuseEligibleBranch`
|
||||
// is the local branch name eligible for reuse (null = not a reusable local
|
||||
|
|
@ -3476,7 +3479,9 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
|||
branchAutoName: branchAutoNameRef.current,
|
||||
workspaceName,
|
||||
preserveWorkspaceNameEdits:
|
||||
smartGitHubResolution.kind === 'pr-start-point' || branchNameOverridePreservesNameEdits
|
||||
smartGitHubResolution.kind === 'pr-start-point' || branchNameOverridePreservesNameEdits,
|
||||
createBranchFromWorkspaceName:
|
||||
smartGitHubResolution.kind === 'none' && smartNameMode === 'branches'
|
||||
})
|
||||
const createDisplayName =
|
||||
smartGitHubResolution.kind === 'none'
|
||||
|
|
@ -3678,6 +3683,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
|||
settings?.agentDefaultArgs,
|
||||
settings?.agentDefaultEnv,
|
||||
settings?.autoRenameBranchFromWork,
|
||||
smartNameMode,
|
||||
setSidebarOpen,
|
||||
setupDecision,
|
||||
sparseEnabled,
|
||||
|
|
@ -3875,7 +3881,9 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
|||
branchAutoName: branchAutoNameRef.current,
|
||||
workspaceName,
|
||||
preserveWorkspaceNameEdits:
|
||||
smartGitHubResolution.kind === 'pr-start-point' || branchNameOverridePreservesNameEdits
|
||||
smartGitHubResolution.kind === 'pr-start-point' || branchNameOverridePreservesNameEdits,
|
||||
createBranchFromWorkspaceName:
|
||||
smartGitHubResolution.kind === 'none' && smartNameMode === 'branches'
|
||||
})
|
||||
const submitBaseBranch = selectedRepoIsGit
|
||||
? await resolveWorktreeCreateBaseBranch({
|
||||
|
|
@ -4128,6 +4136,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
|||
settings?.agentDefaultArgs,
|
||||
settings?.agentDefaultEnv,
|
||||
settings?.autoRenameBranchFromWork,
|
||||
smartNameMode,
|
||||
disabledTuiAgents,
|
||||
setupDecision,
|
||||
sparseEnabled,
|
||||
|
|
@ -4191,6 +4200,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
|||
onSmartGitHubItemSelect: handleSmartGitHubItemSelect,
|
||||
onSmartGitLabItemSelect: handleSmartGitLabItemSelect,
|
||||
onSmartBranchSelect: isProjectGroupTarget ? () => {} : handleSmartBranchSelect,
|
||||
onSmartNameModeChange: setSmartNameMode,
|
||||
onSmartLinearIssueSelect: handleSmartLinearIssueSelect,
|
||||
smartNameGitHubSourceContext: selectedRepoGitHubSourceContext,
|
||||
smartNameSelection,
|
||||
|
|
|
|||
|
|
@ -99,14 +99,29 @@ export function resolveComposerReuseOverride(args: {
|
|||
return args.branchNameOverride
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch-name override to apply when creating a worktree from the composer.
|
||||
*
|
||||
* With no resolver-provided override, branch mode (#6721) keeps a
|
||||
* slash-containing typed name as the git branch — validated downstream by
|
||||
* `git check-ref-format` — while the worktree folder name is sanitized
|
||||
* separately; every other mode leaves the branch to be derived from the
|
||||
* sanitized name. With an override, keep it verbatim when the workspace name is
|
||||
* user-edited (`preserveWorkspaceNameEdits`) or still matches the auto-name.
|
||||
*/
|
||||
export function resolveComposerBranchNameOverrideForCreate(args: {
|
||||
branchNameOverride: string | undefined
|
||||
branchAutoName: string
|
||||
workspaceName: string
|
||||
preserveWorkspaceNameEdits: boolean
|
||||
createBranchFromWorkspaceName?: boolean
|
||||
}): string | undefined {
|
||||
if (!args.branchNameOverride) {
|
||||
return undefined
|
||||
// Why: branch mode keeps slash-containing git branch names while the
|
||||
// workspace folder name may still be sanitized separately.
|
||||
return args.createBranchFromWorkspaceName && args.workspaceName.includes('/')
|
||||
? args.workspaceName
|
||||
: undefined
|
||||
}
|
||||
if (args.preserveWorkspaceNameEdits) {
|
||||
return args.branchNameOverride
|
||||
|
|
|
|||
Loading…
Reference in New Issue