clone migrations on fork

This commit is contained in:
Diego Imbert
2026-07-03 14:10:30 +02:00
parent 7612d7b712
commit 154f31a054
5 changed files with 83 additions and 6 deletions
@@ -46,11 +46,11 @@
]
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true
]
@@ -15,7 +15,7 @@
]
},
"nullable": [
true
null
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO datatable_migrations (workspace_id, datatable, timestamp, name, code_up, code_down)\n SELECT $2, datatable, timestamp, name, code_up, code_down\n FROM datatable_migrations WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "aef927110ef4cff51d3faabb0c389730dd215f4e90d105f0e86025f0ac42f020"
}
@@ -1345,6 +1345,25 @@ pub(crate) async fn cascade_datatable_migration_renames_and_deletes(
Ok(())
}
/// Copy a workspace's migration definitions to another workspace, so a fork
/// inherits the same per-data-table migration history as its parent.
pub(crate) async fn clone_datatable_migrations(
tx: &mut Transaction<'_, Postgres>,
source_workspace_id: &str,
target_workspace_id: &str,
) -> Result<()> {
sqlx::query!(
"INSERT INTO datatable_migrations (workspace_id, datatable, timestamp, name, code_up, code_down)
SELECT $2, datatable, timestamp, name, code_up, code_down
FROM datatable_migrations WHERE workspace_id = $1",
source_workspace_id,
target_workspace_id,
)
.execute(&mut **tx)
.await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1492,4 +1511,38 @@ mod tests {
]
);
}
// A fork inherits its parent's migration definitions unchanged.
#[sqlx::test(migrations = "../migrations")]
async fn clone_copies_datatable_migrations_to_target(pool: DB) {
let src = format!("src{}", uuid::Uuid::new_v4().simple());
let dst = format!("dst{}", uuid::Uuid::new_v4().simple());
for w in [&src, &dst] {
sqlx::query(
"INSERT INTO workspace (id, name, owner) VALUES ($1, $1, 'test@windmill.dev')",
)
.bind(w)
.execute(&pool)
.await
.unwrap();
}
seed_migration(&pool, &src, "customers", 1, "create_customers").await;
seed_migration(&pool, &src, "customers", 2, "add_index").await;
seed_migration(&pool, &src, "orders", 3, "create_orders").await;
let mut tx = pool.begin().await.unwrap();
clone_datatable_migrations(&mut tx, &src, &dst)
.await
.unwrap();
tx.commit().await.unwrap();
// the target ends up with an identical set, and the source is untouched.
let expected = vec![
("customers".to_string(), "create_customers".to_string()),
("customers".to_string(), "add_index".to_string()),
("orders".to_string(), "create_orders".to_string()),
];
assert_eq!(migration_keys(&pool, &dst).await, expected);
assert_eq!(migration_keys(&pool, &src).await, expected);
}
}
@@ -3974,6 +3974,15 @@ async fn clone_workspace_data(
// Clone workspace settings (merge with existing basic settings)
update_workspace_settings(tx, source_workspace_id, target_workspace_id).await?;
// Clone data table migration definitions (the settings above carry the data
// table config; this carries their migration history).
crate::datatable_migrations::clone_datatable_migrations(
tx,
source_workspace_id,
target_workspace_id,
)
.await?;
// Clone workspace environment variables
clone_workspace_env(tx, source_workspace_id, target_workspace_id).await?;