Bring Branching 2.0 out of feature preview (#44279)

## Context

Have Branching 2.0 as the default behaviour + remove it from feature
preview
Behaviour should match staging / prod if branching 2.0 feature preview
is toggled on

## To test

- [ ] Test branching flow in general for any oddities

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Removed the Branching 2.0 preview and cleared its persisted preview
setting; branching UI and branch editing are now available without
opt‑in.
* Simplified branch management flows and empty states by removing
preview-dependent conditions and tooltips.
* Made GitHub branch sync optional in create/edit forms and simplified
validation and submit behavior.
* "Create merge request" and related branch actions now render
consistently across the UI.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-04-14 18:39:20 +08:00
1 parent 255d754eb4
commit be0d05fb08
12 files changed
+263 -396

No files matched your search

@@ -1,60 +0,0 @@
import Image from 'next/image'
import { InlineLink } from '@/components/ui/InlineLink'
import { BASE_PATH, DOCS_URL } from '@/lib/constants'
export const Branching2Preview = () => {
return (
<div>
<p className="text-sm text-foreground-light mb-4">
Create branches, review changes, and merge back into production all through the dashboard.
Read the below limitations and our{' '}
<InlineLink href={`${DOCS_URL}/guides/platform/branching`}>
branching documentation
</InlineLink>{' '}
before opting in.
</p>
<Image
src={`${BASE_PATH}/img/previews/branching-preview.png`}
width={1296}
height={900}
alt="api-docs-side-panel-preview"
className="rounded border mb-4"
/>
<div className="my-6">
<p className="text-sm text-foreground mb-2 font-medium">Limitations:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>Custom roles created through the dashboard are not captured on branch creation.</li>
<li>
Branches can only be merged to <code>main</code>; merging between preview branches is
not supported.
</li>
<li>
If your branch is out of date, you can pull in latest changes from <code>main</code>,
but keep in mind that all functions will be overwritten.
</li>
<li>
Deleting functions must be done manually on <code>main</code>.
</li>
<li>Migration conflicts must be manually resolved on the preview branch.</li>
<li>
If you have run migrations on <code>main</code>, new branches will be created from
existing migrations instead of a full schema dump.
</li>
</ul>
</div>
<div className="space-y-2 !mt-4">
<p className="text-sm">Enabling this preview will:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>
Allow you to create, manage, review, and merge database branches directly via the
branches page within the dashboard
</li>
</ul>
</div>
</div>
)
}
@@ -95,11 +95,6 @@ export const useUnifiedLogsPreview = () => {
return { isEnabled, enable, disable }
}
export const useIsBranching2Enabled = () => {
const { flags } = useFeaturePreviewContext()
return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_BRANCHING_2_0]
}
export const useIsPgDeltaDiffEnabled = () => {
const { flags } = useFeaturePreviewContext()
const pgDeltaDiffEnabled = useFlag('pgdeltaDiff')
@@ -23,7 +23,6 @@ import {
} from 'ui/src/components/shadcn/ui/select'
import { AdvisorRulesPreview } from './AdvisorRulesPreview'
import { Branching2Preview } from './Branching2Preview'
import { CLSPreview } from './CLSPreview'
import { useFeaturePreviewContext, useFeaturePreviewModal } from './FeaturePreviewContext'
import { FloatingMobileToolbarPreview } from './FloatingMobileToolbarPreview'
@@ -40,7 +39,6 @@ import { IS_PLATFORM } from '@/lib/constants'
const FEATURE_PREVIEW_KEY_TO_CONTENT: {
[key: string]: ReactNode
} = {
[LOCAL_STORAGE_KEYS.UI_PREVIEW_BRANCHING_2_0]: <Branching2Preview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_PG_DELTA_DIFF]: <PgDeltaDiffPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_ADVISOR_RULES]: <AdvisorRulesPreview />,
[LOCAL_STORAGE_KEYS.UI_PREVIEW_CLS]: <CLSPreview />,
@@ -31,15 +31,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => {
isPlatformOnly: true,
isDefaultOptIn: false,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_BRANCHING_2_0,
name: 'Branching via dashboard',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/branching-2-0',
enabled: true,
isNew: false,
isPlatformOnly: true,
isDefaultOptIn: false,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_ADVISOR_RULES,
name: 'Disable Advisor rules',
@@ -22,6 +22,7 @@ interface BranchSelectorProps {
disabled?: boolean
isUpdating?: boolean
type?: 'primary' | 'outline'
align?: 'end' | 'center'
}
export const BranchSelector = ({
@@ -30,14 +31,12 @@ export const BranchSelector = ({
disabled = false,
isUpdating = false,
type = 'primary',
align = 'end',
}: BranchSelectorProps) => {
const [open, setOpen] = useState(false)
const [selectedBranch, setSelectedBranch] = useState<Branch | null>(null)
// Filter out branches that are already ready for review or linked to a github branch
const availableBranches = branches.filter(
(branch) => !branch.is_default && !branch.review_requested_at && !branch.git_branch
)
const availableBranches = branches.filter((branch) => !branch.is_default)
const handleBranchSelect = (branch: Branch) => {
setSelectedBranch(branch)
@@ -51,16 +50,17 @@ export const BranchSelector = ({
<ButtonTooltip
icon={<GitMerge size={14} strokeWidth={1.5} />}
type={type}
disabled={disabled || availableBranches.length === 0 || isUpdating}
disabled={
disabled || isUpdating || branches.length === 0 || availableBranches.length === 0
}
tooltip={{
content: {
side: 'bottom',
align,
text:
branches.length === 0
branches.length === 0 || availableBranches.length === 0
? 'Create a branch first to start a merge request'
: availableBranches.length === 0
? 'All branches currently have merge requests'
: undefined,
: undefined,
},
}}
>
@@ -80,7 +80,7 @@ export const BranchSelector = ({
value={branch.name.replaceAll('"', '')}
className="cursor-pointer w-full flex items-center justify-between"
onSelect={() => handleBranchSelect(branch)}
disabled={isUpdating}
disabled={isUpdating || !!branch.git_branch || !!branch.review_requested_at}
>
<div className="flex items-center gap-2">
{branch.is_default && <Shield size={14} className="text-amber-900" />}
@@ -91,6 +91,8 @@ export const BranchSelector = ({
{selectedBranch?.id === branch.id && (
<Check size={14} strokeWidth={1.5} className="text-brand" />
)}
{branch.git_branch && <span>Synced to a Git branch</span>}
{branch.review_requested_at && <span>Merge request opened</span>}
</CommandItem_Shadcn_>
))}
</ScrollArea>
@@ -32,7 +32,7 @@ import {
TooltipTrigger,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { GenericSkeletonLoader, ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import {
@@ -40,7 +40,6 @@ import {
estimateDiskCost,
estimateRestoreTime,
} from './BranchManagement.utils'
import { useIsBranching2Enabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { BranchingPITRNotice } from '@/components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice'
import AlertError from '@/components/ui/AlertError'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
@@ -70,7 +69,6 @@ export const CreateBranchModal = () => {
const { data: selectedOrg } = useSelectedOrganizationQuery()
const { showCreateBranchModal, setShowCreateBranchModal } = useAppStateSnapshot()
const gitlessBranching = useIsBranching2Enabled()
const allowDataBranching = useFlag('allowDataBranching')
const [isGitBranchValid, setIsGitBranchValid] = useState(false)
@@ -101,12 +99,7 @@ export const CreateBranchModal = () => {
(val) => (branches ?? []).every((branch) => branch.name !== val),
'A branch with this name already exists'
),
gitBranchName: z
.string()
.refine(
(val) => gitlessBranching || !githubConnection || (val && val.length > 0),
'Git branch name is required when GitHub is connected'
),
gitBranchName: z.string().optional(),
withData: z.boolean().default(false).optional(),
})
@@ -137,11 +130,10 @@ export const CreateBranchModal = () => {
{ enabled: showCreateBranchModal }
)
const computeAddon = addons?.selected_addons.find((addon) => addon.type === 'compute_instance')
const computeSize = !!computeAddon
const computeSize = computeAddon
? (computeAddon.variant.identifier.split('ci_')[1] as DesiredInstanceSize)
: undefined
const hasPitrEnabled =
(addons?.selected_addons ?? []).find((addon) => addon.type === 'pitr') !== undefined
const hasPitrEnabled = (addons?.selected_addons ?? []).some((addon) => addon.type === 'pitr')
const {
data: disk,
@@ -174,15 +166,13 @@ export const CreateBranchModal = () => {
onSuccess: async (data) => {
toast.success(`Successfully created preview branch "${data.name}"`)
if (projectRef) {
await Promise.all([
queryClient.invalidateQueries({ queryKey: projectKeys.detail(projectRef) }),
])
await queryClient.invalidateQueries({ queryKey: projectKeys.detail(projectRef) })
}
sendEvent({
action: 'branch_create_button_clicked',
properties: {
branchType: data.persistent ? 'persistent' : 'preview',
gitlessBranching,
gitlessBranching: !data.git_branch,
},
groups: {
project: ref ?? 'Unknown',
@@ -208,18 +198,13 @@ export const CreateBranchModal = () => {
!isFormValid ||
!canCreateBranch ||
!isSuccessAddons ||
!isSuccessConnections ||
(!!gitBranchName && !isSuccessConnections) ||
isLoadingEntitlement ||
!hasAccessToBranching ||
(!gitlessBranching && !githubConnection) ||
isCreatingBranch ||
isCheckingGHBranchValidity
const tooltipText = promptPlanUpgrade
? 'Upgrade to unlock branching'
: !gitlessBranching && !githubConnection
? 'Set up a GitHub connection first to create branches'
: undefined
const tooltipText = promptPlanUpgrade ? 'Upgrade to unlock branching' : undefined
const validateGitBranchName = useCallback(
(branchName: string) => {
@@ -283,24 +268,16 @@ export const CreateBranchModal = () => {
}
useEffect(() => {
if (form && showCreateBranchModal) {
form.reset()
}
if (showCreateBranchModal) form.reset()
}, [form, showCreateBranchModal])
useEffect(() => {
if (!githubConnection || !debouncedGitBranchName) {
setIsGitBranchValid(gitlessBranching)
form.clearErrors('gitBranchName')
return
}
form.clearErrors('gitBranchName')
validateGitBranchName(debouncedGitBranchName)
}, [debouncedGitBranchName, validateGitBranchName, form, githubConnection, gitlessBranching])
if (githubConnection && debouncedGitBranchName) validateGitBranchName(debouncedGitBranchName)
}, [debouncedGitBranchName, validateGitBranchName, form, githubConnection])
return (
<Dialog open={showCreateBranchModal} onOpenChange={(open) => setShowCreateBranchModal(open)}>
<Dialog open={showCreateBranchModal} onOpenChange={setShowCreateBranchModal}>
<DialogContent
size="large"
hideClose
@@ -348,92 +325,91 @@ export const CreateBranchModal = () => {
)}
/>
{githubConnection && (
<FormField_Shadcn_
control={form.control}
name="gitBranchName"
render={({ field }) => (
<FormItemLayout
label={
<div className="flex items-center justify-between w-full gap-4">
<span className="flex-1">
Sync with Git branch {gitlessBranching ? '(optional)' : ''}
</span>
<div className="flex items-center gap-2 text-sm">
<Image
className={cn('dark:invert')}
src={`${BASE_PATH}/img/icons/github-icon.svg`}
width={16}
height={16}
alt={`GitHub icon`}
/>
<Link
href={`https://github.com/${repoOwner}/${repoName}`}
target="_blank"
rel="noreferrer"
className="text-foreground hover:underline"
>
{repoOwner}/{repoName}
</Link>
</div>
</div>
}
description="Automatically deploy changes on every commit"
>
<div className="relative w-full">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="e.g. main, feat/some-feature"
autoComplete="off"
onChange={(e) => {
field.onChange(e)
setIsGitBranchValid(false)
}}
/>
</FormControl_Shadcn_>
<div className="absolute top-2.5 right-3 flex items-center gap-2">
{field.value ? (
isCheckingGHBranchValidity ? (
<Loader2 size={14} className="animate-spin" />
) : isGitBranchValid ? (
<Check size={14} className="text-brand" strokeWidth={2} />
) : null
) : null}
</div>
</div>
</FormItemLayout>
)}
/>
{isLoadingConnections && (
<div className="flex flex-col gap-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isLoadingConnections && <GenericSkeletonLoader />}
{isErrorConnections && (
<AlertError
error={connectionsError}
subject="Failed to retrieve GitHub connection information"
/>
)}
{isSuccessConnections && (
<>
{!githubConnection && (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Label>Sync with a GitHub branch</Label>
{!gitlessBranching && <Badge variant="warning">Required</Badge>}
{isSuccessConnections &&
(githubConnection ? (
<FormField_Shadcn_
control={form.control}
name="gitBranchName"
render={({ field }) => (
<FormItemLayout
label={
<div className="flex items-center justify-between w-full gap-4">
<span className="flex-1">Sync with Git branch</span>
<div className="flex items-center gap-2 text-sm">
<Image
className={cn('dark:invert')}
src={`${BASE_PATH}/img/icons/github-icon.svg`}
width={16}
height={16}
alt={`GitHub icon`}
/>
<Link
href={`https://github.com/${repoOwner}/${repoName}`}
target="_blank"
rel="noreferrer"
className="text-foreground hover:underline"
>
{repoOwner}/{repoName}
</Link>
</div>
</div>
}
labelOptional="Optional"
description="Automatically deploy changes on every commit"
>
<div className="relative w-full">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="e.g. main, feat/some-feature"
autoComplete="off"
onChange={(e) => {
field.onChange(e)
setIsGitBranchValid(false)
}}
/>
</FormControl_Shadcn_>
<div className="absolute top-2.5 right-3 flex items-center gap-2">
{field.value ? (
isCheckingGHBranchValidity ? (
<Loader2 size={14} className="animate-spin" />
) : isGitBranchValid ? (
<Check size={14} className="text-brand" strokeWidth={2} />
) : null
) : null}
</div>
</div>
<p className="text-sm text-foreground-lighter">
Keep this preview branch in sync with a chosen GitHub branch
</p>
</div>
<Button type="default" icon={<Github />} onClick={handleGitHubClick}>
Configure
</Button>
</FormItemLayout>
)}
/>
) : (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<Label>Sync with a GitHub branch</Label>
<p className="text-sm text-foreground-lighter">
Keep this preview branch in sync with a chosen GitHub branch
</p>
</div>
)}
</>
)}
<Button type="default" icon={<Github />} onClick={handleGitHubClick}>
Configure
</Button>
</div>
))}
{allowDataBranching && (
<FormField_Shadcn_
control={form.control}
@@ -529,7 +505,7 @@ export const CreateBranchModal = () => {
</p>
</div>
<div className="flex items-center gap-x-2">
<p className="w-24">Targer disk size:</p>
<p className="w-24">Target disk size:</p>
<p className="w-16">{branchDiskAttributes.size_gb} GB</p>
<p>(${estimatedDiskCost.size.toFixed(2)})</p>
</div>
@@ -8,7 +8,6 @@ import { useCallback, useEffect, useState } from 'react'
import { useForm, useWatch } from 'react-hook-form'
import { toast } from 'sonner'
import {
Badge,
Button,
cn,
Dialog,
@@ -25,10 +24,9 @@ import {
Label_Shadcn_ as Label,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import { useIsBranching2Enabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { AlertError } from '@/components/ui/AlertError'
import { InlineLink } from '@/components/ui/InlineLink'
import { useBranchUpdateMutation } from '@/data/branches/branch-update-mutation'
@@ -50,9 +48,8 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
const router = useRouter()
const { data: projectDetails } = useSelectedProjectQuery()
const { data: selectedOrg } = useSelectedOrganizationQuery()
const gitlessBranching = useIsBranching2Enabled()
const [isGitBranchValid, setIsGitBranchValid] = useState(false)
const [isGitBranchValid, setIsGitBranchValid] = useState(true)
const isBranch = projectDetails?.parent_project_ref !== undefined
const projectRef =
@@ -101,12 +98,7 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
val === branch?.name || (branches ?? []).every((b) => b.name !== val),
'A branch with this name already exists'
),
gitBranchName: z
.string()
.refine(
(val) => gitlessBranching || !githubConnection || (val && val.length > 0),
'Git branch name is required when GitHub is connected'
),
gitBranchName: z.string().optional(),
})
const form = useForm<z.infer<typeof FormSchema>>({
@@ -200,24 +192,21 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
// Pre-fill form when the modal becomes visible and branch data is available
useEffect(() => {
if (visible && branch) {
setIsGitBranchValid(!!branch.git_branch || gitlessBranching) // Initial validity based on existing link or gitless branching
form.reset({
branchName: branch.name ?? '',
gitBranchName: branch.git_branch ?? '',
})
}
}, [branch, visible, form, gitlessBranching])
}, [branch, visible, form])
useEffect(() => {
if (!githubConnection || !debouncedGitBranchName) {
setIsGitBranchValid(gitlessBranching)
form.clearErrors('gitBranchName')
return
return form.clearErrors('gitBranchName')
}
form.clearErrors('gitBranchName')
validateGitBranchName(debouncedGitBranchName)
}, [debouncedGitBranchName, validateGitBranchName, form, githubConnection, gitlessBranching])
}, [debouncedGitBranchName, validateGitBranchName, form, githubConnection])
return (
<Dialog open={visible} onOpenChange={(open) => !open && onClose()}>
@@ -245,88 +234,88 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
)}
/>
{githubConnection && (
<FormField_Shadcn_
control={form.control}
name="gitBranchName"
render={({ field }) => (
<FormItemLayout
label={
<div className="flex items-center justify-between w-full gap-4">
<span className="flex-1">
Sync with Git branch {gitlessBranching ? '(optional)' : ''}
</span>
<div className="flex items-center gap-2 text-sm">
<Image
className={cn('dark:invert')}
src={`${BASE_PATH}/img/icons/github-icon.svg`}
width={16}
height={16}
alt={`GitHub icon`}
/>
<InlineLink href={`https://github.com/${repoOwner}/${repoName}`}>
{repoOwner}/{repoName}
</InlineLink>
</div>
</div>
}
description={
gitlessBranching
? 'Automatically deploy changes on every commit'
: 'If linked, migrations from this Git branch will be automatically deployed.'
}
>
<div className="relative">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="e.g. main, feat/some-feature"
autoComplete="off"
onChange={(e) => {
field.onChange(e)
setIsGitBranchValid(false)
}}
/>
</FormControl_Shadcn_>
<div className="absolute top-2.5 right-3 flex items-center gap-2">
{field.value ? (
isChecking ? (
<Loader2 size={14} className="animate-spin" />
) : isGitBranchValid ? (
<Check size={14} className="text-brand" strokeWidth={2} />
) : null
) : null}
</div>
</div>
</FormItemLayout>
)}
/>
{isLoadingConnections && (
<div className="flex flex-col gap-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isLoadingConnections && <GenericSkeletonLoader />}
{isErrorConnections && (
<AlertError
error={connectionsError}
subject="Failed to retrieve GitHub connection information"
/>
)}
{isSuccessConnections && !githubConnection && (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Label>Sync with a GitHub branch</Label>
{!gitlessBranching && <Badge variant="warning">Required</Badge>}
{isSuccessConnections &&
(githubConnection ? (
<FormField_Shadcn_
control={form.control}
name="gitBranchName"
render={({ field }) => (
<FormItemLayout
label={
<div className="flex items-center justify-between w-full gap-4">
<span className="flex-1">Sync with Git branch</span>
<div className="flex items-center gap-2 text-sm">
<Image
className={cn('dark:invert')}
src={`${BASE_PATH}/img/icons/github-icon.svg`}
width={16}
height={16}
alt={`GitHub icon`}
/>
<InlineLink href={`https://github.com/${repoOwner}/${repoName}`}>
{repoOwner}/{repoName}
</InlineLink>
</div>
</div>
}
labelOptional="Optional"
description="Automatically deploy changes on every commit"
>
<div className="relative">
<FormControl_Shadcn_>
<Input_Shadcn_
{...field}
placeholder="e.g. main, feat/some-feature"
autoComplete="off"
onChange={(e) => {
field.onChange(e)
setIsGitBranchValid(false)
}}
/>
</FormControl_Shadcn_>
<div className="absolute top-2.5 right-3 flex items-center gap-2">
{field.value ? (
isChecking ? (
<Loader2 size={14} className="animate-spin" />
) : isGitBranchValid ? (
<Check size={14} className="text-brand" strokeWidth={2} />
) : null
) : null}
</div>
</div>
</FormItemLayout>
)}
/>
) : (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Label>Sync with a GitHub branch</Label>
</div>
<p className="text-sm text-foreground-light">
Optionally connect to a GitHub repository to manage migrations automatically
for this branch.
</p>
</div>
<p className="text-sm text-foreground-light">
{gitlessBranching
? 'Optionally connect to a GitHub repository to manage migrations automatically for this branch.'
: 'Keep this preview branch in sync with a chosen GitHub branch'}
</p>
<Button type="default" icon={<Github />} onClick={openLinkerPanel}>
Connect to GitHub
</Button>
</div>
<Button type="default" icon={<Github />} onClick={openLinkerPanel}>
{gitlessBranching ? 'Connect to GitHub' : 'Configure'}
</Button>
</div>
)}
))}
</DialogSection>
<DialogFooter padding="medium">
@@ -336,11 +325,10 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
<Button
form={formId}
disabled={
!isSuccessConnections ||
(!!gitBranchName && !isSuccessConnections) ||
isUpdating ||
!canSubmit ||
isChecking ||
(!gitlessBranching && !githubConnection)
isChecking
}
loading={isUpdating}
type="primary"
@@ -15,16 +15,14 @@ export const PullRequestsEmptyState = ({
branches,
onBranchSelected,
isUpdating,
githubConnection,
gitlessBranching = false,
hasGithubConnection,
}: {
url: string
projectRef: string
branches: Branch[]
onBranchSelected: (branch: Branch) => void
isUpdating: boolean
githubConnection?: any
gitlessBranching: boolean
hasGithubConnection?: boolean
}) => {
return (
<div className={EMPTY_STATE_CONTAINER}>
@@ -33,7 +31,7 @@ export const PullRequestsEmptyState = ({
Create your first merge request to merge changes back to the main branch
</p>
<div className="flex items-center space-x-2 mt-4">
{githubConnection ? (
{hasGithubConnection ? (
<Button type="outline" asChild icon={<Github />}>
<a href={url} target="_blank" rel="noopener noreferrer">
Create pull request
@@ -44,14 +42,13 @@ export const PullRequestsEmptyState = ({
<Link href={`/project/${projectRef}/settings/integrations`}>Connect to GitHub</Link>
</Button>
)}
{gitlessBranching && (
<BranchSelector
type="outline"
branches={branches}
onBranchSelected={onBranchSelected}
isUpdating={isUpdating}
/>
)}
<BranchSelector
type="outline"
align="center"
branches={branches}
onBranchSelected={onBranchSelected}
isUpdating={isUpdating}
/>
</div>
</div>
)
@@ -28,7 +28,6 @@ import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal'
import { BranchLoader, BranchManagementSection, BranchRow, BranchRowLoader } from './BranchPanels'
import { EditBranchModal } from './EditBranchModal'
import { PreviewBranchesEmptyState } from './EmptyStates'
import { useIsBranching2Enabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper'
import { useBranchQuery } from '@/data/branches/branch-query'
@@ -236,7 +235,6 @@ const PreviewBranchActions = ({
onSelectDeleteBranch: () => void
generateCreatePullRequestURL: (branchName?: string) => string
}) => {
const gitlessBranching = useIsBranching2Enabled()
const queryClient = useQueryClient()
const { project_ref: branchRef, parent_project_ref: projectRef } = branch
@@ -321,33 +319,30 @@ const PreviewBranchActions = ({
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" side="bottom" align="end">
{/* Edit Branch (gitless) */}
{gitlessBranching && (
<DropdownMenuItemTooltip
className="gap-x-2"
disabled={!canUpdateBranches || !isBranchActiveHealthy || isUpdatingBranch}
onSelect={(e) => {
e.stopPropagation()
setShowEditBranchModal(true)
}}
onClick={(e) => {
e.stopPropagation()
setShowEditBranchModal(true)
}}
tooltip={{
content: {
side: 'left',
text: !canUpdateBranches
? 'You need additional permissions to edit branches'
: !isBranchActiveHealthy
? 'Branch is still initializing. Please wait for it to become healthy before editing.'
: undefined,
},
}}
>
<Pencil size={14} /> Edit branch
</DropdownMenuItemTooltip>
)}
<DropdownMenuItemTooltip
className="gap-x-2"
disabled={!canUpdateBranches || !isBranchActiveHealthy || isUpdatingBranch}
onSelect={(e) => {
e.stopPropagation()
setShowEditBranchModal(true)
}}
onClick={(e) => {
e.stopPropagation()
setShowEditBranchModal(true)
}}
tooltip={{
content: {
side: 'left',
text: !canUpdateBranches
? 'You need additional permissions to edit branches'
: !isBranchActiveHealthy
? 'Branch is still initializing. Please wait for it to become healthy before editing.'
: undefined,
},
}}
>
<Pencil size={14} /> Edit branch
</DropdownMenuItemTooltip>
{!branch.deletion_scheduled_at && (
<DropdownMenuItemTooltip
@@ -15,10 +15,7 @@ import { HeaderUpgradeButton } from './HeaderUpgradeButton'
import { HomeIcon } from './HomeIcon'
import { LocalVersionPopover } from './LocalVersionPopover'
import { MergeRequestButton } from './MergeRequestButton'
import {
useIsBranching2Enabled,
useIsFloatingMobileToolbarEnabled,
} from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useIsFloatingMobileToolbarEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { ConnectButton } from '@/components/interfaces/ConnectButton/ConnectButton'
import { ConnectSheet } from '@/components/interfaces/ConnectSheet/ConnectSheet'
import { LocalDropdown } from '@/components/interfaces/LocalDropdown'
@@ -73,7 +70,6 @@ export const LayoutHeader = ({
const { ref: projectRef, slug } = useParams()
const { data: selectedProject } = useSelectedProjectQuery()
const { data: selectedOrganization } = useSelectedOrganizationQuery()
const gitlessBranching = useIsBranching2Enabled()
const showFloatingMobileToolbar = useIsFloatingMobileToolbarEnabled()
const [commandMenuEnabled] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.HOTKEY_COMMAND_MENU, true)
@@ -217,7 +213,7 @@ export const LayoutHeader = ({
ease: 'easeOut',
}}
>
{IS_PLATFORM && gitlessBranching && <MergeRequestButton />}
{IS_PLATFORM && <MergeRequestButton />}
<ConnectButton buttonType={connectButtonType} />
</motion.div>
)}
@@ -15,7 +15,6 @@ import {
} from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { useIsBranching2Enabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import {
BranchManagementSection,
BranchRow,
@@ -44,7 +43,6 @@ const MergeRequestsPage: NextPageWithLayout = () => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: selectedOrg } = useSelectedOrganizationQuery()
const gitlessBranching = useIsBranching2Enabled()
const isBranch = project?.parent_project_ref !== undefined
const projectRef =
@@ -68,7 +66,6 @@ const MergeRequestsPage: NextPageWithLayout = () => {
error: branchesError,
isPending: isLoadingBranches,
isError: isErrorBranches,
isSuccess: isSuccessBranches,
} = useBranchesQuery({ projectRef })
const [[mainBranch], previewBranchesUnsorted] = partition(branches, (branch) => branch.is_default)
const previewBranches = previewBranchesUnsorted.sort((a, b) =>
@@ -190,30 +187,26 @@ const MergeRequestsPage: NextPageWithLayout = () => {
{!isError && (
<div className="space-y-4">
{gitlessBranching && (
<>
{isBranch && !isCurrentBranchReadyForReview && currentBranch && (
<div className="rounded border rounded-lg bg-background px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm text-foreground-light">
<GitMerge strokeWidth={1.5} size={16} className="text-brand" />
<span className="text-foreground">{currentBranch.name}</span>
last viewed
</div>
<Button
type="primary"
size="tiny"
loading={currentBranch && isUpdating}
onClick={() =>
currentBranch && handleMarkBranchForReview(currentBranch)
}
>
Create merge request
</Button>
</div>
{isBranch && !isCurrentBranchReadyForReview && currentBranch && (
<div className="rounded border rounded-lg bg-background px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm text-foreground-light">
<GitMerge strokeWidth={1.5} size={16} className="text-brand" />
<span className="text-foreground">{currentBranch.name}</span>
last viewed
</div>
)}
</>
<Button
type="primary"
size="tiny"
loading={currentBranch && isUpdating}
onClick={() =>
currentBranch && handleMarkBranchForReview(currentBranch)
}
>
Create merge request
</Button>
</div>
</div>
)}
<BranchManagementSection
header={`${mergeRequestBranches.length} merge requests`}
@@ -297,12 +290,11 @@ const MergeRequestsPage: NextPageWithLayout = () => {
) : (
<PullRequestsEmptyState
url={generateCreatePullRequestURL()}
gitlessBranching={gitlessBranching}
projectRef={projectRef ?? '_'}
branches={previewBranches}
onBranchSelected={handleMarkBranchForReview}
isUpdating={isUpdating}
githubConnection={githubConnection}
hasGithubConnection={!!githubConnection}
/>
)}
</BranchManagementSection>
@@ -322,7 +314,6 @@ const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: selectedOrg } = useSelectedOrganizationQuery()
const gitlessBranching = useIsBranching2Enabled()
const isBranch = project?.parent_project_ref !== undefined
const projectRef =
@@ -373,36 +364,36 @@ const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => {
)
}
const primaryActions = gitlessBranching ? (
<BranchSelector
branches={previewBranches}
onBranchSelected={handleMarkBranchForReview}
disabled={!projectRef}
isUpdating={isUpdating}
/>
) : null
const secondaryActions = (
<div className="flex items-center gap-x-2">
<Button asChild type="text" icon={<MessageCircle className="text-muted" strokeWidth={1} />}>
<a
target="_blank"
rel="noreferrer"
href="https://github.com/orgs/supabase/discussions/18937"
>
Branching feedback
</a>
</Button>
<DocsButton href={`${DOCS_URL}/guides/platform/branching`} />
</div>
)
return (
<PageLayout
title="Merge requests"
subtitle="Review and merge changes from one branch into another"
primaryActions={primaryActions}
secondaryActions={secondaryActions}
primaryActions={
<BranchSelector
branches={previewBranches}
onBranchSelected={handleMarkBranchForReview}
disabled={!projectRef}
isUpdating={isUpdating}
/>
}
secondaryActions={
<div className="flex items-center gap-x-2">
<Button
asChild
type="text"
icon={<MessageCircle className="text-muted" strokeWidth={1} />}
>
<a
target="_blank"
rel="noreferrer"
href="https://github.com/orgs/supabase/discussions/18937"
>
Branching feedback
</a>
</Button>
<DocsButton href={`${DOCS_URL}/guides/platform/branching`} />
</div>
}
>
{children}
</PageLayout>
@@ -19,7 +19,6 @@ export const LOCAL_STORAGE_KEYS = {
UI_PREVIEW_INLINE_EDITOR: 'supabase-ui-preview-inline-editor',
UI_PREVIEW_UNIFIED_LOGS: 'supabase-ui-preview-unified-logs',
UI_ONBOARDING_NEW_PAGE_SHOWN: 'supabase-ui-onboarding-new-page-shown',
UI_PREVIEW_BRANCHING_2_0: 'supabase-ui-branching-2-0',
UI_PREVIEW_ADVISOR_RULES: 'supabase-ui-advisor-rules',
UI_PREVIEW_QUEUE_OPERATIONS: 'supabase-ui-queue-operations',
UI_PREVIEW_TABLE_FILTER_BAR: 'supabase-ui-table-filter-bar',
@@ -163,7 +162,6 @@ const LOCAL_STORAGE_KEYS_ALLOWLIST = [
LOCAL_STORAGE_KEYS.HIDE_PROMO_TOAST,
LOCAL_STORAGE_KEYS.BLOG_VIEW,
LOCAL_STORAGE_KEYS.AI_ASSISTANT_MCP_OPT_IN,
LOCAL_STORAGE_KEYS.UI_PREVIEW_BRANCHING_2_0,
LOCAL_STORAGE_KEYS.LINTER_SHOW_FOOTER,
LOCAL_STORAGE_KEYS.SIDEBAR_BEHAVIOR,
]