useBranchMergeDiff.ts243 lines · main
| 1 | import { useMemo } from 'react' |
| 2 | |
| 3 | import { useEdgeFunctionsDiff, type EdgeFunctionsDiffResult } from './useEdgeFunctionsDiff' |
| 4 | import { useIsPgDeltaDiffEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' |
| 5 | import { useBranchDiffQuery } from '@/data/branches/branch-diff-query' |
| 6 | import { useMigrationsQuery } from '@/data/database/migrations-query' |
| 7 | |
| 8 | interface UseBranchMergeDiffProps { |
| 9 | currentBranchRef?: string |
| 10 | parentProjectRef?: string |
| 11 | currentBranchConnectionString?: string |
| 12 | parentBranchConnectionString?: string |
| 13 | currentBranchCreatedAt?: string |
| 14 | } |
| 15 | |
| 16 | export interface BranchMergeDiffResult { |
| 17 | // Database diff |
| 18 | diffContent: string | undefined |
| 19 | isDatabaseDiffLoading: boolean |
| 20 | isDatabaseDiffRefetching: boolean |
| 21 | databaseDiffError: any |
| 22 | refetchDatabaseDiff: () => void |
| 23 | |
| 24 | // Edge functions diff |
| 25 | edgeFunctionsDiff: EdgeFunctionsDiffResult |
| 26 | |
| 27 | // Migrations |
| 28 | currentBranchMigrations: any[] | undefined |
| 29 | mainBranchMigrations: any[] | undefined |
| 30 | refetchCurrentBranchMigrations: () => void |
| 31 | refetchMainBranchMigrations: () => void |
| 32 | |
| 33 | // Branch state |
| 34 | isBranchOutOfDateMigrations: boolean |
| 35 | hasEdgeFunctionModifications: boolean |
| 36 | missingFunctionsCount: number |
| 37 | hasMissingFunctions: boolean |
| 38 | outOfDateFunctionsCount: number |
| 39 | hasOutOfDateFunctions: boolean |
| 40 | isBranchOutOfDateOverall: boolean |
| 41 | missingMigrationsCount: number |
| 42 | modifiedFunctionsCount: number |
| 43 | |
| 44 | // Combined states |
| 45 | isLoading: boolean |
| 46 | hasChanges: boolean |
| 47 | } |
| 48 | |
| 49 | export const useBranchMergeDiff = ({ |
| 50 | currentBranchRef, |
| 51 | parentProjectRef, |
| 52 | currentBranchConnectionString, |
| 53 | parentBranchConnectionString, |
| 54 | currentBranchCreatedAt, |
| 55 | }: UseBranchMergeDiffProps): BranchMergeDiffResult => { |
| 56 | const pgDeltaDiffEnabled = useIsPgDeltaDiffEnabled() |
| 57 | |
| 58 | // Get database diff |
| 59 | const { |
| 60 | data: diffContent, |
| 61 | isPending: isDatabaseDiffLoading, |
| 62 | isRefetching: isDatabaseDiffRefetching, |
| 63 | error: databaseDiffError, |
| 64 | refetch: refetchDatabaseDiff, |
| 65 | } = useBranchDiffQuery( |
| 66 | { |
| 67 | branchRef: currentBranchRef || '', |
| 68 | projectRef: parentProjectRef || '', |
| 69 | pgdelta: pgDeltaDiffEnabled, |
| 70 | }, |
| 71 | { |
| 72 | enabled: !!currentBranchRef && !!parentProjectRef, |
| 73 | refetchOnMount: 'always', |
| 74 | refetchOnWindowFocus: false, |
| 75 | staleTime: 0, |
| 76 | } |
| 77 | ) |
| 78 | |
| 79 | // Get migrations for both current branch and main branch |
| 80 | const { data: currentBranchMigrations, refetch: refetchCurrentBranchMigrations } = |
| 81 | useMigrationsQuery( |
| 82 | { |
| 83 | projectRef: currentBranchRef, |
| 84 | connectionString: currentBranchConnectionString, |
| 85 | }, |
| 86 | { |
| 87 | enabled: !!currentBranchRef, |
| 88 | staleTime: 3000, |
| 89 | } |
| 90 | ) |
| 91 | |
| 92 | const { data: mainBranchMigrations, refetch: refetchMainBranchMigrations } = useMigrationsQuery( |
| 93 | { |
| 94 | projectRef: parentProjectRef, |
| 95 | connectionString: parentBranchConnectionString, |
| 96 | }, |
| 97 | { |
| 98 | enabled: !!parentProjectRef, |
| 99 | staleTime: 3000, |
| 100 | } |
| 101 | ) |
| 102 | |
| 103 | // Get edge functions diff |
| 104 | const edgeFunctionsDiff = useEdgeFunctionsDiff({ |
| 105 | currentBranchRef, |
| 106 | mainBranchRef: parentProjectRef, |
| 107 | }) |
| 108 | |
| 109 | // Check if current branch is out of date with main branch (migrations) |
| 110 | const isBranchOutOfDateMigrations = useMemo(() => { |
| 111 | if (!currentBranchMigrations || !mainBranchMigrations) return false |
| 112 | |
| 113 | // Get the latest migration version from main branch |
| 114 | const latestMainMigration = mainBranchMigrations[0] // migrations are ordered by version desc |
| 115 | if (!latestMainMigration) return false |
| 116 | |
| 117 | // Check if current branch has this latest migration |
| 118 | const hasLatestMigration = currentBranchMigrations.some( |
| 119 | (migration) => migration.version === latestMainMigration.version |
| 120 | ) |
| 121 | |
| 122 | return !hasLatestMigration |
| 123 | }, [currentBranchMigrations, mainBranchMigrations]) |
| 124 | |
| 125 | // Check if main branch has functions that are newer than the current branch versions |
| 126 | const outOfDateFunctionsCount = useMemo(() => { |
| 127 | if (!edgeFunctionsDiff.modifiedSlugs.length) return 0 |
| 128 | |
| 129 | return edgeFunctionsDiff.modifiedSlugs.filter((slug) => { |
| 130 | // Get both main and current branch function data |
| 131 | const mainFunction = edgeFunctionsDiff.mainBranchFunctions?.find((func) => func.slug === slug) |
| 132 | const currentFunction = edgeFunctionsDiff.currentBranchFunctions?.find( |
| 133 | (func) => func.slug === slug |
| 134 | ) |
| 135 | |
| 136 | if (!mainFunction || !currentFunction) return false |
| 137 | |
| 138 | // Compare updated_at timestamps - if main branch function is newer, it was modified after current branch |
| 139 | const mainUpdatedAt = mainFunction.updated_at * 1000 // Convert to milliseconds |
| 140 | const currentUpdatedAt = currentFunction.updated_at * 1000 // Convert to milliseconds |
| 141 | |
| 142 | return mainUpdatedAt > currentUpdatedAt |
| 143 | }).length |
| 144 | }, [ |
| 145 | edgeFunctionsDiff.modifiedSlugs, |
| 146 | edgeFunctionsDiff.mainBranchFunctions, |
| 147 | edgeFunctionsDiff.currentBranchFunctions, |
| 148 | ]) |
| 149 | |
| 150 | // Check if main branch has functions that are newer than the current branch versions |
| 151 | const hasOutOfDateFunctions = outOfDateFunctionsCount > 0 |
| 152 | |
| 153 | // Check if current branch has any edge function modifications (not additions/removals) |
| 154 | // This only includes functions where the current branch version is newer than the main branch version |
| 155 | const hasEdgeFunctionModifications = |
| 156 | edgeFunctionsDiff.modifiedSlugs.length > outOfDateFunctionsCount |
| 157 | |
| 158 | // Count of removed functions that were updated on main branch after this branch was created |
| 159 | // Note: For removed functions, we use branch creation date since there's no current branch version to compare to |
| 160 | const missingFunctionsCount = useMemo(() => { |
| 161 | if (!currentBranchCreatedAt || !edgeFunctionsDiff.removedSlugs.length) return 0 |
| 162 | |
| 163 | const branchCreatedAt = new Date(currentBranchCreatedAt).getTime() |
| 164 | |
| 165 | return edgeFunctionsDiff.removedSlugs.filter((slug) => { |
| 166 | // Access main branch function data from the original functions list |
| 167 | const mainFunction = edgeFunctionsDiff.mainBranchFunctions?.find((func) => func.slug === slug) |
| 168 | if (!mainFunction) return false |
| 169 | |
| 170 | // Check if function was updated after branch creation |
| 171 | const functionUpdatedAt = mainFunction.updated_at * 1000 // Convert to milliseconds |
| 172 | return functionUpdatedAt > branchCreatedAt |
| 173 | }).length |
| 174 | }, [ |
| 175 | currentBranchCreatedAt, |
| 176 | edgeFunctionsDiff.removedSlugs, |
| 177 | edgeFunctionsDiff.mainBranchFunctions, |
| 178 | ]) |
| 179 | |
| 180 | // Check if main branch has functions removed from current branch that were updated after branch creation |
| 181 | const hasMissingFunctions = missingFunctionsCount > 0 |
| 182 | |
| 183 | // Update overall out-of-date check to include newer removed functions and newer modified functions |
| 184 | const isBranchOutOfDateOverall = |
| 185 | isBranchOutOfDateMigrations || hasMissingFunctions || hasOutOfDateFunctions |
| 186 | |
| 187 | // Get the count of migrations that the branch is missing |
| 188 | const missingMigrationsCount = useMemo(() => { |
| 189 | if (!currentBranchMigrations || !mainBranchMigrations || !isBranchOutOfDateMigrations) return 0 |
| 190 | |
| 191 | const currentVersions = new Set(currentBranchMigrations.map((m) => m.version)) |
| 192 | return mainBranchMigrations.filter((m) => !currentVersions.has(m.version)).length |
| 193 | }, [currentBranchMigrations, mainBranchMigrations, isBranchOutOfDateMigrations]) |
| 194 | |
| 195 | // Get count of modified functions |
| 196 | const modifiedFunctionsCount = edgeFunctionsDiff.modifiedSlugs.length |
| 197 | |
| 198 | // Check if there are any changes (database or edge functions) |
| 199 | const hasChanges = useMemo(() => { |
| 200 | // Check database changes |
| 201 | const hasDatabaseChanges = diffContent && diffContent.trim() !== '' |
| 202 | |
| 203 | // Check edge function changes |
| 204 | const hasEdgeFunctionChanges = edgeFunctionsDiff.hasChanges |
| 205 | |
| 206 | return hasDatabaseChanges || hasEdgeFunctionChanges |
| 207 | }, [diffContent, edgeFunctionsDiff.hasChanges]) |
| 208 | |
| 209 | const isLoading = isDatabaseDiffLoading || edgeFunctionsDiff.isLoading |
| 210 | |
| 211 | return { |
| 212 | // Database diff |
| 213 | diffContent, |
| 214 | isDatabaseDiffLoading, |
| 215 | isDatabaseDiffRefetching, |
| 216 | databaseDiffError, |
| 217 | refetchDatabaseDiff, |
| 218 | |
| 219 | // Edge functions diff |
| 220 | edgeFunctionsDiff, |
| 221 | |
| 222 | // Migrations |
| 223 | currentBranchMigrations, |
| 224 | mainBranchMigrations, |
| 225 | refetchCurrentBranchMigrations, |
| 226 | refetchMainBranchMigrations, |
| 227 | |
| 228 | // Branch state |
| 229 | isBranchOutOfDateMigrations, |
| 230 | hasEdgeFunctionModifications, |
| 231 | missingFunctionsCount, |
| 232 | hasMissingFunctions, |
| 233 | outOfDateFunctionsCount, |
| 234 | hasOutOfDateFunctions, |
| 235 | isBranchOutOfDateOverall, |
| 236 | missingMigrationsCount, |
| 237 | modifiedFunctionsCount, |
| 238 | |
| 239 | // Combined states |
| 240 | isLoading, |
| 241 | hasChanges, |
| 242 | } |
| 243 | } |