useBranchMergeDiff.ts243 lines · main
1import { useMemo } from 'react'
2
3import { useEdgeFunctionsDiff, type EdgeFunctionsDiffResult } from './useEdgeFunctionsDiff'
4import { useIsPgDeltaDiffEnabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
5import { useBranchDiffQuery } from '@/data/branches/branch-diff-query'
6import { useMigrationsQuery } from '@/data/database/migrations-query'
7
8interface UseBranchMergeDiffProps {
9 currentBranchRef?: string
10 parentProjectRef?: string
11 currentBranchConnectionString?: string
12 parentBranchConnectionString?: string
13 currentBranchCreatedAt?: string
14}
15
16export 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
49export 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}