OXYGENHUB

Diffusion: a bump's pipeline runs f01cb865fc60

A hub-made bump does not come through the push path, so its pipeline did
not start and the proposal went untested. The hub now starts the
consumer's pipeline for the bump bookmark as a push of it would.

refs #148
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-13 02:07 UTC
Signature
✔ signed by Andy McCall
Hash
f01cb865fc60230b8370579a98b7eac42de01abe2ac040d85bbd0acf03f9e80d
Depends on
1d634f698ecd 3776b70d9b17 42fa95540468 a7e6e8e6094c f677d6270298
Depended on by
f4d6ef418a96 c4e432da4bc9 9aaa88b2d9c8
In bookmarks
api-unknown-fields cancel-landed-runs close-proposal-140 diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
8 atoms, 0 tree operations, 2 files

Browse the tree at this change

modified crates/hub/src/diffusion.rs +19 −3
…
175 unchanged lines
176 176
#[derive(Clone, Debug, PartialEq, Eq)]
177 177
pub enum Bumped {
178 178
    /// The bump bookmark now holds the target plus one change moving the pin.
179 -
    Moved { bookmark: String, change: ChangeHash, heads: Vec<ChangeHash>, from: Vec<ChangeHash> },
179 +
    Moved { bookmark: String, change: ChangeHash, heads: Vec<ChangeHash>, from: Vec<ChangeHash>, state: String },
180 180
    /// The pin already holds the upstream state.
181 181
    UpToDate,
182 182
    /// The bump bookmark already moves the pin to that state.
…
106 unchanged lines
289 289
            // target each time, so the bump it replaces may go.
290 290
            let mut update = r.host_ref_update(&bookmark, &heads, Some((&key, &secret)))?;
291 291
            update.force = true;
292 +
            let state = update.new_state.to_hex();
292 293
            let report = r.apply_hosted(Some((change, vec![sig])), &[identity], update)?;
293 294
            if let Some(refused) = report.results.iter().find(|x| !x.ok) {
294 295
                return Err(crate::repos::RepoError::Oxy(oxy_core::repo::RepoError::Msg(format!(
…
1 unchanged lines
296 297
                    refused.reason
297 298
                ))));
298 299
            }
299 -
            Ok(Bumped::Moved { bookmark, change: hash, heads, from: old })
300 +
            Ok(Bumped::Moved { bookmark, change: hash, heads, from: old, state })
300 301
        })
301 302
        .await
302 303
}
…
109 unchanged lines
412 413
    Some(number)
413 414
}
414 415
416 +
/// Start a bump's pipeline, as a push of it would. Boxed as `Send`, which
417 +
/// breaks the cycle between a bump starting runs and a passing run
418 +
/// starting bumps that the compiler cannot see through.
419 +
fn start_runs(
420 +
    app: Shared,
421 +
    row: RepoRow,
422 +
    bookmark: String,
423 +
    state: String,
424 +
    heads: Vec<ChangeHash>,
425 +
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>> {
426 +
    Box::pin(async move { crate::flow::on_push(&app, &row, &bookmark, &state, &heads, ACCOUNT).await })
427 +
}
428 +
415 429
/// The text of `.oxymodules` on `bookmark` of `name`, if any.
416 430
async fn oxymodules(app: &Shared, name: &RepoName, bookmark: &str) -> Option<String> {
417 431
    let b = bookmark.to_string();
…
52 unchanged lines
470 484
            continue;
471 485
        }
472 486
        match bump(app, &consumer, &target, &path, upstream, heads.clone()).await {
473 -
            Ok(Bumped::Moved { from, .. }) => {
487 +
            Ok(Bumped::Moved { from, bookmark, heads: bumped, state, .. }) => {
488 +
                // Its pipeline runs as for a push, so the proposal is tested.
489 +
                tokio::spawn(start_runs(app.clone(), consumer.clone(), bookmark, state, bumped));
474 490
                let n = propose(app, &consumer, &target, &path, upstream, from, heads.clone()).await;
475 491
                tracing::info!(
476 492
                    "diffusion: {}/{} {path} bumped to {}/{} (proposal {n:?})",
…
204 unchanged lines
modified crates/hub/tests/flow.rs +13 −0
…
3277 unchanged lines
3278 3278
    let game = client(&format!("{}/andy/game", h.base), game_work.path());
3279 3279
    game.submodule_add(&format!("{}/andy/api", h.base), "deps/api", Some("main")).unwrap();
3280 3280
    write(game_work.path(), "game.c", "int main;\n");
3281 +
    write(game_work.path(), ".flow/pipeline.kdl", &pipeline(""));
3282 +
    write(game_work.path(), "test.sh", "echo ok\n");
3281 3283
    game.snapshot("t").unwrap();
3282 3284
    game.commit("game on api v1", &[]).unwrap();
3283 3285
    assert!(game.push("origin", false).unwrap().iter().all(|r| r.ok));
…
24 unchanged lines
3308 3310
    assert_eq!(put(r#"{"follow":"any"}"#).await.0, StatusCode::OK);
3309 3311
    let first = bump().await.expect("caught up when turned on");
3310 3312
    assert!(first.title.contains("1 change") && first.body.contains("api v2: faster sprites"), "{:?}", first.title);
3313 +
    // The bump's pipeline runs, as a push of it would, so the proposal is tested.
3314 +
    let mut ran = false;
3315 +
    for _ in 0..100 {
3316 +
        if runs_of(&h.app, "andy", "game").await.iter().any(|r| r.bookmark == "diffusion/deps/api") {
3317 +
            ran = true;
3318 +
            break;
3319 +
        }
3320 +
        tokio::time::sleep(std::time::Duration::from_millis(50)).await;
3321 +
    }
3322 +
    assert!(ran, "a run of the bump");
3323 +
    pass_all().await;
3311 3324
    write(api_work.path(), "a3.c", "v3\n");
3312 3325
    api.snapshot("t").unwrap();
3313 3326
    api.commit("api v3: save slots past ten", &[]).unwrap();
…
92 unchanged lines