OXYGENHUB

Diffusion: a bump pins the upstream's default bookmark, not the old pin's (closes #332) 3e868a1f94e1

Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key ce8a0aaaaeaa
When
· 2026-10-26 17:29 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
3e868a1f94e111eb69563ebe52a76d3c20bfe1857f6cc65986a189b6119dd75f
Depends on
1d634f698ecd 34388e612963 62cdf34daec1 7a14bdaeb857 a7e6e8e6094c b5668108487f e71583501b1d f677d6270298
Depended on by
4b49c41599f3
In bookmarks
diffusion-pin-bookmark main
In tags
v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.7 v0.9.8 v0.9.9
Edits
6 atoms, 0 tree operations, 2 files

Browse the tree at this change

modified CHANGELOG.md +1 −0
…
3 unchanged lines
4 4
5 5
## Unreleased
6 6
7 +
- **A Diffusion bump pins the upstream's default bookmark (#332).** A bump kept the bookmark name of the pin it replaced, so a subrepo first pinned at an upstream proposal's bookmark still named it after the proposal landed and Diffusion moved the pin to the default bookmark's state; a checkout then counted the subrepo as on another bookmark. A bump now names the upstream's default bookmark, unless it keeps pinned changes that bookmark lacks.
7 8
- **The API refuses a field an edit does not take (#330).** `PATCH` on an issue, proposal, project, milestone, release or drop answered 200 to a field it does not know and changed nothing (`{"ready": true}` on a draft proposal, say), which looked like success. It now answers 422 naming the field and the ones it takes.
8 9
- **Mail reads `[OxygenHub]`, in a wider message (#334).** A notification's subject was tagged with the site's name (`[Oxygenhub on Earth]`); mail now goes by the From address's display name (`OxygenHub <noreply@…>`) in the subject and the footer, falling back to the site's name, so the site keeps its own name (which its apt repository is labelled with). The message card is 720px wide rather than 560.
9 10
- **An agent follows its owner's newest identity (oxygen#82).** A keyless certificate's answer now carries `author_chain`: the grant owner's newest linked identity and its predecessors back to the grant's key, so an agent home with `author.for` authors as the owner's current identity, not one several rotations old.
…
349 unchanged lines
modified crates/hub/src/diffusion.rs +36 −1
…
719 unchanged lines
720 720
    if repo != upstream_id {
721 721
        return Ok(Bumped::NoPin);
722 722
    }
723 -
    let new_heads = match plan(app, &uname, old.clone(), new_heads.clone()).await? {
723 +
    let planned = match plan(app, &uname, old.clone(), new_heads.clone()).await? {
724 724
        Plan::To(heads, _) => heads,
725 725
        Plan::UpToDate => return Ok(Bumped::UpToDate),
726 726
        Plan::Unknown => return Ok(Bumped::Unknown),
727 727
        Plan::Conflicts => return Ok(Bumped::Conflicts),
728 728
    };
729 +
    let bookmark = pin_bookmark(&planned, &new_heads, &upstream.default_bookmark, bookmark);
730 +
    let new_heads = planned;
729 731
    if let Some(Pin::Oxy { heads: b, .. }) = &bumped {
730 732
        let (mut b, mut n) = (b.clone(), new_heads.clone());
731 733
        b.sort();
…
40 unchanged lines
772 774
        .await
773 775
}
774 776
777 +
/// The bookmark a bump's pin names (#332): the upstream's default
778 +
/// bookmark, which `new` (its heads) are from, unless the move keeps
779 +
/// pinned changes that bookmark lacks (an upstream proposal not landed
780 +
/// yet), which are only on the bookmark the pin named, `old`. Keeping
781 +
/// `old` always left a pin naming a proposal's bookmark after the
782 +
/// proposal had landed, and a checkout then counted the subrepo as on
783 +
/// another bookmark.
784 +
fn pin_bookmark(planned: &[ChangeHash], new: &[ChangeHash], default: &str, old: String) -> String {
785 +
    if heads_key(planned) == heads_key(new) {
786 +
        default.to_string()
787 +
    } else {
788 +
        old
789 +
    }
790 +
}
791 +
775 792
/// The account Diffusion's proposals are opened as.
776 793
pub const ACCOUNT: &str = "diffusion";
777 794
…
783 unchanged lines
1561 1578
        assert_eq!(title, "Diffusion: move rstar-api to doomsdayone/rstar-api 00a6c9d4746a (+19 heads)");
1562 1579
        assert_eq!(body, format!("Pins doomsdayone/rstar-api at {}", heads.join("+")));
1563 1580
        assert_eq!(bump_message("api", "andy/api", &heads[..1]), "Diffusion: move api to andy/api 00a6c9d4746a");
1581 +
    }
1582 +
}
1583 +
1584 +
#[cfg(test)]
1585 +
mod pin_tests {
1586 +
    use super::pin_bookmark;
1587 +
    use oxy_core::hash::ObjectRef;
1588 +
1589 +
    #[test]
1590 +
    fn a_bump_pins_the_upstreams_default_bookmark_unless_it_keeps_other_changes() {
1591 +
        let (a, b) = (ObjectRef([1; 32]), ObjectRef([2; 32]));
1592 +
        // R*'s proposal had landed on main: the move is to main's heads,
1593 +
        // so the pin names main, not the proposal's bookmark (#332).
1594 +
        assert_eq!(pin_bookmark(&[a], &[a], "main", "x16-tilemap-double".into()), "main");
1595 +
        assert_eq!(pin_bookmark(&[b, a], &[a, b], "main", "feature".into()), "main", "in any order");
1596 +
        // The pin's own changes are kept beside main's: still the bookmark
1597 +
        // that has them.
1598 +
        assert_eq!(pin_bookmark(&[a, b], &[a], "main", "feature".into()), "feature");
1564 1599
    }
1565 1600
}