Tags: an unsigned repository keeps the tag message by storing the attestation unsigned; unsigned attestations are not advertised to remotes c792033af344
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-09-27 16:02 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
c792033af3441e2bd3388ea1dc814fcd218c4df654a2738e2bb172b7a77ea54a- Depends on
- 132b884b9a8d 728da0f92161 982864068a59
- Depended on by
- 387335ff7f4d 75167672ebcd
- In bookmarks
- agents-in-readme bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
- In tags
- v0.1.0 v0.10.0 v0.11.0 v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
- Edits
- 6 atoms, 0 tree operations, 2 files
Browse the tree at this change
modified crates/oxy-core/src/repo/tags.rs +9 −2
| … | 50 unchanged lines |
||
| 51 | 51 | } |
|
| 52 | 52 | let state = self.store.put_state(&StateObject::new(heads.clone()))?; |
|
| 53 | 53 | let mut signer = None; |
|
| 54 | + | let attest = Attest { repo: self.id()?, hlc: Hlc::now(), body: AttestBody::Tag { name: name.into(), state, message: message.into() } }; |
|
| 54 | 55 | if self.secret_key()?.is_some() { |
|
| 55 | - | let sa = self.sign_attest(&Attest { repo: self.id()?, hlc: Hlc::now(), body: AttestBody::Tag { name: name.into(), state, message: message.into() } })?; |
|
| 56 | + | let sa = self.sign_attest(&attest)?; |
|
| 56 | 57 | let r = sa.attest.hash(); |
|
| 57 | 58 | self.set_tag_attest(name, &r)?; |
|
| 58 | 59 | signer = sa.sigs.first().and_then(|s| self.store.get_identity(&s.key).ok()).map(|i| i.name); |
|
| 60 | + | } else { |
|
| 61 | + | // No key: keep the attestation unsigned so the message survives; |
|
| 62 | + | // it is never sent to remotes (`tag_refs`). |
|
| 63 | + | let r = self.store.put_attest(&attest)?; |
|
| 64 | + | self.set_tag_attest(name, &r)?; |
|
| 59 | 65 | } |
|
| 60 | 66 | let mut view = op.view.clone(); |
|
| 61 | 67 | view.tags.insert(name.into(), state); |
|
| … | 34 unchanged lines |
||
| 96 | 102 | pub(crate) fn tag_refs(&self) -> Result<Vec<crate::protocol::TagRef>> { |
|
| 97 | 103 | let mut out = Vec::new(); |
|
| 98 | 104 | for t in self.tags()? { |
|
| 99 | - | out.push(crate::protocol::TagRef { name: t.name.clone(), state: t.state, heads: t.heads, attestation: self.tag_attest(&t.name)?.map(|(_, a)| a) }); |
|
| 105 | + | let attestation = self.tag_attest(&t.name)?.map(|(_, a)| a).filter(|a| !a.sigs.is_empty()); |
|
| 106 | + | out.push(crate::protocol::TagRef { name: t.name.clone(), state: t.state, heads: t.heads, attestation }); |
|
| 100 | 107 | } |
|
| 101 | 108 | Ok(out) |
|
| 102 | 109 | } |
|
| … | 43 unchanged lines |
modified crates/oxy-core/src/repo/tests.rs +15 −0
| … | 2532 unchanged lines |
||
| 2533 | 2533 | assert!(err.contains("empty") || err.contains("already"), "{err}"); |
|
| 2534 | 2534 | } |
|
| 2535 | 2535 | ||
| 2536 | + | #[test] |
|
| 2537 | + | fn unsigned_tags_keep_their_message() { |
|
| 2538 | + | hermetic(); |
|
| 2539 | + | let dir = tempfile::tempdir().unwrap(); |
|
| 2540 | + | let root = dir.path(); |
|
| 2541 | + | let repo = Repo::init(root).unwrap().with_home(root.join("home")); |
|
| 2542 | + | write(root, "a.txt", b"a\n"); |
|
| 2543 | + | repo.commit("a", &[]).unwrap(); |
|
| 2544 | + | assert!(repo.secret_key().unwrap().is_none()); |
|
| 2545 | + | repo.tag_create("v1", &repo.bookmark_heads().unwrap(), "first release").unwrap(); |
|
| 2546 | + | let tags = repo.tags().unwrap(); |
|
| 2547 | + | assert_eq!((tags[0].name.as_str(), tags[0].message.as_str(), tags[0].signer.as_deref()), ("v1", "first release", None)); |
|
| 2548 | + | assert!(repo.tag_refs().unwrap()[0].attestation.is_none(), "an unsigned attestation is not advertised"); |
|
| 2549 | + | } |
|
| 2550 | + |