@@ -109,6 +109,20 @@ pub fn parse_bridge_json(json_str: &str) -> Result<Vec<RawMigrationUnit>, LoadEr
109109 Ok ( units)
110110}
111111
112+ /// Resolve relative `source_file` paths in migration units against a base directory.
113+ ///
114+ /// The bridge JAR and `update-sql` strategies return `source_file` paths relative to the
115+ /// changelog's parent directory. This function joins those relative paths with `base_dir`
116+ /// so that downstream code (suppression reading, output) can find the actual files.
117+ /// Absolute paths are left unchanged.
118+ pub fn resolve_source_paths ( units : & mut [ RawMigrationUnit ] , base_dir : & Path ) {
119+ for unit in units {
120+ if unit. source_file . is_relative ( ) {
121+ unit. source_file = base_dir. join ( & unit. source_file ) ;
122+ }
123+ }
124+ }
125+
112126/// Load Liquibase migrations using the configured strategy.
113127///
114128/// Strategy selection:
@@ -176,7 +190,9 @@ fn load_with_bridge(
176190 let mut all_units = Vec :: new ( ) ;
177191
178192 for path in paths {
179- let units = loader. load ( path) ?;
193+ let mut units = loader. load ( path) ?;
194+ let base_dir = path. parent ( ) . unwrap_or_else ( || Path :: new ( "." ) ) ;
195+ resolve_source_paths ( & mut units, base_dir) ;
180196 all_units. extend ( units) ;
181197 }
182198
@@ -202,7 +218,9 @@ fn load_with_updatesql(
202218 let mut all_units = Vec :: new ( ) ;
203219
204220 for path in paths {
205- let units = loader. load ( path) ?;
221+ let mut units = loader. load ( path) ?;
222+ let base_dir = path. parent ( ) . unwrap_or_else ( || Path :: new ( "." ) ) ;
223+ resolve_source_paths ( & mut units, base_dir) ;
206224 all_units. extend ( units) ;
207225 }
208226
@@ -348,6 +366,64 @@ mod tests {
348366 }
349367 }
350368
369+ #[ test]
370+ fn test_resolve_relative_paths ( ) {
371+ let mut units = vec ! [
372+ RawMigrationUnit {
373+ id: "1" . into( ) ,
374+ sql: "SELECT 1;" . into( ) ,
375+ source_file: PathBuf :: from( "migrations/foo.xml" ) ,
376+ source_line_offset: 1 ,
377+ run_in_transaction: true ,
378+ is_down: false ,
379+ } ,
380+ RawMigrationUnit {
381+ id: "2" . into( ) ,
382+ sql: "SELECT 2;" . into( ) ,
383+ source_file: PathBuf :: from( "/absolute/bar.xml" ) ,
384+ source_line_offset: 1 ,
385+ run_in_transaction: true ,
386+ is_down: false ,
387+ } ,
388+ ] ;
389+ resolve_source_paths ( & mut units, Path :: new ( "db/changelog" ) ) ;
390+ assert_eq ! (
391+ units[ 0 ] . source_file,
392+ PathBuf :: from( "db/changelog/migrations/foo.xml" )
393+ ) ;
394+ // Absolute path should be unchanged
395+ assert_eq ! ( units[ 1 ] . source_file, PathBuf :: from( "/absolute/bar.xml" ) ) ;
396+ }
397+
398+ #[ test]
399+ fn test_resolve_paths_empty_base ( ) {
400+ let mut units = vec ! [ RawMigrationUnit {
401+ id: "1" . into( ) ,
402+ sql: "SELECT 1;" . into( ) ,
403+ source_file: PathBuf :: from( "foo.xml" ) ,
404+ source_line_offset: 1 ,
405+ run_in_transaction: true ,
406+ is_down: false ,
407+ } ] ;
408+ // Empty base dir (changelog at repo root) should leave path unchanged
409+ resolve_source_paths ( & mut units, Path :: new ( "" ) ) ;
410+ assert_eq ! ( units[ 0 ] . source_file, PathBuf :: from( "foo.xml" ) ) ;
411+ }
412+
413+ #[ test]
414+ fn test_resolve_paths_dot_base ( ) {
415+ let mut units = vec ! [ RawMigrationUnit {
416+ id: "1" . into( ) ,
417+ sql: "SELECT 1;" . into( ) ,
418+ source_file: PathBuf :: from( "foo.xml" ) ,
419+ source_line_offset: 1 ,
420+ run_in_transaction: true ,
421+ is_down: false ,
422+ } ] ;
423+ resolve_source_paths ( & mut units, Path :: new ( "." ) ) ;
424+ assert_eq ! ( units[ 0 ] . source_file, PathBuf :: from( "./foo.xml" ) ) ;
425+ }
426+
351427 #[ test]
352428 fn test_parse_json_multiple_sql_statements ( ) {
353429 let json = r#"[
0 commit comments