Files
supabase/apps/studio/hooks/branches/useBranchMergeDiff.ts
T
ea38c6d153 Review and merge branch (#36795)
* allow creating branching without git

* update branching modals

* add account connections

* edit branch

* copy

* update copy

* enable branch modal changes

* add gitless branching flag

* update account connections

* merge page

* merge experiment

* update merge

* update pull requests empty state

* use diff query

* branch diffing

* diff query

* Clean up

* refinements to gitless branching

* branching merge and status

* link

* branch function diffing

* update styling

* refactor

* remove hook

* error handling

* move

* remove enable branching modal

* re-add github linker

* abstract away enable and disable

* toggle fixes

* update logic to lean on connection status

* update form logic

* sheet layout

* gitless flag

* style and workflow updates

* fix side panel size

* fix duplicate onerror

* copy changes

* refetch

* merge mutation copy

* remove import

* add cost

* allow connection details on create

* initial queries

* push button

* merge cleanup

* Fix TS issues

* Fix TS issues

* Couple of clean ups

* Revert hardcode in useFlag

* Fix TS

* layout issues and github check

* refactor

* refactor to use new field

* cleanup

* style

* failed merge

* error positioning

* refactoring merge

* workflow refactor

* hook move

* clarification with github integration

* replace branch dropdown button

* update repo picker

* updates

* remove modal

* fix small nits

* change defaults

* clean up

* disable if not gitless and no connection

* clean up

* always show workflow run id

* optimistic

* fix branch query

* fix issues

* fetch edge diff

* confirm merge

* update edge functions key

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
Co-authored-by: Kevin Grüneberg <k.grueneberg1994@gmail.com>
Co-authored-by: Alaister Young <a@alaisteryoung.com>
2025-07-04 14:57:59 +10:00

241 lines
8.1 KiB
TypeScript

import { useMemo } from 'react'
import { useBranchDiffQuery } from 'data/branches/branch-diff-query'
import { useMigrationsQuery } from 'data/database/migrations-query'
import { useEdgeFunctionsDiff, type EdgeFunctionsDiffResult } from './useEdgeFunctionsDiff'
interface UseBranchMergeDiffProps {
branchId?: string
currentBranchRef?: string
parentProjectRef?: string
currentBranchConnectionString?: string
parentBranchConnectionString?: string
currentBranchCreatedAt?: string
}
export interface BranchMergeDiffResult {
// Database diff
diffContent: string | undefined
isDatabaseDiffLoading: boolean
isDatabaseDiffRefetching: boolean
databaseDiffError: any
refetchDatabaseDiff: () => void
// Edge functions diff
edgeFunctionsDiff: EdgeFunctionsDiffResult
// Migrations
currentBranchMigrations: any[] | undefined
mainBranchMigrations: any[] | undefined
refetchCurrentBranchMigrations: () => void
refetchMainBranchMigrations: () => void
// Branch state
isBranchOutOfDateMigrations: boolean
hasEdgeFunctionModifications: boolean
missingFunctionsCount: number
hasMissingFunctions: boolean
outOfDateFunctionsCount: number
hasOutOfDateFunctions: boolean
isBranchOutOfDateOverall: boolean
missingMigrationsCount: number
modifiedFunctionsCount: number
// Combined states
isLoading: boolean
hasChanges: boolean
}
export const useBranchMergeDiff = ({
branchId,
currentBranchRef,
parentProjectRef,
currentBranchConnectionString,
parentBranchConnectionString,
currentBranchCreatedAt,
}: UseBranchMergeDiffProps): BranchMergeDiffResult => {
// Get database diff
const {
data: diffContent,
isLoading: isDatabaseDiffLoading,
isRefetching: isDatabaseDiffRefetching,
error: databaseDiffError,
refetch: refetchDatabaseDiff,
} = useBranchDiffQuery(
{
branchId: branchId || '',
projectRef: parentProjectRef || '',
},
{
enabled: !!branchId && !!parentProjectRef,
refetchOnMount: 'always',
refetchOnWindowFocus: false,
staleTime: 0,
}
)
// Get migrations for both current branch and main branch
const { data: currentBranchMigrations, refetch: refetchCurrentBranchMigrations } =
useMigrationsQuery(
{
projectRef: currentBranchRef,
connectionString: currentBranchConnectionString,
},
{
enabled: !!currentBranchRef,
staleTime: 3000,
}
)
const { data: mainBranchMigrations, refetch: refetchMainBranchMigrations } = useMigrationsQuery(
{
projectRef: parentProjectRef,
connectionString: parentBranchConnectionString,
},
{
enabled: !!parentProjectRef,
staleTime: 3000,
}
)
// Get edge functions diff
const edgeFunctionsDiff = useEdgeFunctionsDiff({
currentBranchRef,
mainBranchRef: parentProjectRef,
})
// Check if current branch is out of date with main branch (migrations)
const isBranchOutOfDateMigrations = useMemo(() => {
if (!currentBranchMigrations || !mainBranchMigrations) return false
// Get the latest migration version from main branch
const latestMainMigration = mainBranchMigrations[0] // migrations are ordered by version desc
if (!latestMainMigration) return false
// Check if current branch has this latest migration
const hasLatestMigration = currentBranchMigrations.some(
(migration) => migration.version === latestMainMigration.version
)
return !hasLatestMigration
}, [currentBranchMigrations, mainBranchMigrations])
// Check if main branch has functions that are newer than the current branch versions
const outOfDateFunctionsCount = useMemo(() => {
if (!edgeFunctionsDiff.modifiedSlugs.length) return 0
return edgeFunctionsDiff.modifiedSlugs.filter((slug) => {
// Get both main and current branch function data
const mainFunction = edgeFunctionsDiff.mainBranchFunctions?.find((func) => func.slug === slug)
const currentFunction = edgeFunctionsDiff.currentBranchFunctions?.find(
(func) => func.slug === slug
)
if (!mainFunction || !currentFunction) return false
// Compare updated_at timestamps - if main branch function is newer, it was modified after current branch
const mainUpdatedAt = mainFunction.updated_at * 1000 // Convert to milliseconds
const currentUpdatedAt = currentFunction.updated_at * 1000 // Convert to milliseconds
return mainUpdatedAt > currentUpdatedAt
}).length
}, [
edgeFunctionsDiff.modifiedSlugs,
edgeFunctionsDiff.mainBranchFunctions,
edgeFunctionsDiff.currentBranchFunctions,
])
// Check if main branch has functions that are newer than the current branch versions
const hasOutOfDateFunctions = outOfDateFunctionsCount > 0
// Check if current branch has any edge function modifications (not additions/removals)
// This only includes functions where the current branch version is newer than the main branch version
const hasEdgeFunctionModifications =
edgeFunctionsDiff.modifiedSlugs.length > outOfDateFunctionsCount
// Count of removed functions that were updated on main branch after this branch was created
// Note: For removed functions, we use branch creation date since there's no current branch version to compare to
const missingFunctionsCount = useMemo(() => {
if (!currentBranchCreatedAt || !edgeFunctionsDiff.removedSlugs.length) return 0
const branchCreatedAt = new Date(currentBranchCreatedAt).getTime()
return edgeFunctionsDiff.removedSlugs.filter((slug) => {
// Access main branch function data from the original functions list
const mainFunction = edgeFunctionsDiff.mainBranchFunctions?.find((func) => func.slug === slug)
if (!mainFunction) return false
// Check if function was updated after branch creation
const functionUpdatedAt = mainFunction.updated_at * 1000 // Convert to milliseconds
return functionUpdatedAt > branchCreatedAt
}).length
}, [
currentBranchCreatedAt,
edgeFunctionsDiff.removedSlugs,
edgeFunctionsDiff.mainBranchFunctions,
])
// Check if main branch has functions removed from current branch that were updated after branch creation
const hasMissingFunctions = missingFunctionsCount > 0
// Update overall out-of-date check to include newer removed functions and newer modified functions
const isBranchOutOfDateOverall =
isBranchOutOfDateMigrations || hasMissingFunctions || hasOutOfDateFunctions
// Get the count of migrations that the branch is missing
const missingMigrationsCount = useMemo(() => {
if (!currentBranchMigrations || !mainBranchMigrations || !isBranchOutOfDateMigrations) return 0
const currentVersions = new Set(currentBranchMigrations.map((m) => m.version))
return mainBranchMigrations.filter((m) => !currentVersions.has(m.version)).length
}, [currentBranchMigrations, mainBranchMigrations, isBranchOutOfDateMigrations])
// Get count of modified functions
const modifiedFunctionsCount = edgeFunctionsDiff.modifiedSlugs.length
// Check if there are any changes (database or edge functions)
const hasChanges = useMemo(() => {
// Check database changes
const hasDatabaseChanges = diffContent && diffContent.trim() !== ''
// Check edge function changes
const hasEdgeFunctionChanges = edgeFunctionsDiff.hasChanges
return hasDatabaseChanges || hasEdgeFunctionChanges
}, [diffContent, edgeFunctionsDiff.hasChanges])
const isLoading = isDatabaseDiffLoading || edgeFunctionsDiff.isLoading
return {
// Database diff
diffContent,
isDatabaseDiffLoading,
isDatabaseDiffRefetching,
databaseDiffError,
refetchDatabaseDiff,
// Edge functions diff
edgeFunctionsDiff,
// Migrations
currentBranchMigrations,
mainBranchMigrations,
refetchCurrentBranchMigrations,
refetchMainBranchMigrations,
// Branch state
isBranchOutOfDateMigrations,
hasEdgeFunctionModifications,
missingFunctionsCount,
hasMissingFunctions,
outOfDateFunctionsCount,
hasOutOfDateFunctions,
isBranchOutOfDateOverall,
missingMigrationsCount,
modifiedFunctionsCount,
// Combined states
isLoading,
hasChanges,
}
}