rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwoodf00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Updated",
"patch": {
"id": "1da708aed81e99e9151deec7012134dfa5f221fe",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": "lorenz"
},
"title": "radicle: Split `WriteRepository::set_head` up",
"state": {
"status": "open",
"conflicts": []
},
"before": "646d4360e7d905f0a9ec87d32c6768c5390eabf3",
"after": "f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7",
"commits": [
"f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7",
"ecab3f6d1969b9ff5dd9a72f6feed8b896d979c7"
],
"target": "646d4360e7d905f0a9ec87d32c6768c5390eabf3",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "1da708aed81e99e9151deec7012134dfa5f221fe",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": "lorenz"
},
"description": "The method `set_head` did two things:\n 1. Compute the canonical head and set the default branch to target.\n 2. Set the symbolic reference `HEAD` to target the default branch.\n\nSplit these two concerns into:\n 1. `set_default_branch_to_canonical_head`\n 2. `set_head_to_default_branch`",
"base": "f00d1d67432882bef11fc940601f071efe55c88d",
"oid": "9cd98d7b26ebfdc5e58709693965011e13bbcab8",
"timestamp": 1757248276
},
{
"id": "a50f237c33833d731d3cad8be0e62ca5165bbcc1",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": "lorenz"
},
"description": "Work in Matthias' suggestion and rebase.",
"base": "646d4360e7d905f0a9ec87d32c6768c5390eabf3",
"oid": "f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7",
"timestamp": 1757534266
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "d30db7a2-e65a-40f4-bafa-1b4f16557d02"
},
"info_url": "https://cci.rad.levitte.org//d30db7a2-e65a-40f4-bafa-1b4f16557d02.html"
}
Started at: 2025-09-10 22:00:12.526504+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote cloudhead@z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW added
✓ Remote-tracking branch cloudhead@z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW/master created for z6MktaNvN1KVFMkSRAiN4qK5yvX1zuEEaseeX5sffhzPZRZW
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote erikli@z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz added
✓ Remote-tracking branch erikli@z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz/master created for z6MkgFq6z5fkF2hioLLSNu1zP2qEL1aHXHZzGH1FLFGAnBGz
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/d30db7a2-e65a-40f4-bafa-1b4f16557d02/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 112 issues · 17 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 1da708aed81e99e9151deec7012134dfa5f221fe
✓ Switched to branch patch/1da708a at revision a50f237
✓ Branch patch/1da708a setup to track rad/patches/1da708aed81e99e9151deec7012134dfa5f221fe
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7
HEAD is now at f00a5ed4 radicle: Split `WriteRepository::set_head` up
Exit code: 0
$ git show f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7
commit f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Sun Sep 7 14:01:27 2025 +0200
radicle: Split `WriteRepository::set_head` up
The method `set_head` did two things:
1. Compute the canonical head and set the default branch to target.
2. Set the symbolic reference `HEAD` to target the default branch.
Split these two concerns into:
1. `set_default_branch_to_canonical_head`
2. `set_head_to_default_branch`
diff --git a/crates/radicle-node/src/worker/fetch.rs b/crates/radicle-node/src/worker/fetch.rs
index b5302060..9202cb4b 100644
--- a/crates/radicle-node/src/worker/fetch.rs
+++ b/crates/radicle-node/src/worker/fetch.rs
@@ -119,11 +119,9 @@ impl Handle {
// points to a repository that is temporary and gets moved by [`mv`].
let repo = storage.repository(rid)?;
repo.set_identity_head()?;
- match repo.set_head() {
- Ok(head) => {
- if head.is_updated() {
- log::trace!(target: "worker", "Set HEAD to {}", head.new);
- }
+ match repo.set_head_to_default_branch() {
+ Ok(()) => {
+ log::trace!(target: "worker", "Set HEAD successfully");
}
Err(RepositoryError::Quorum(e)) => {
log::warn!(target: "worker", "Fetch could not set HEAD: {e}")
@@ -369,9 +367,6 @@ fn set_canonical_refs(
applied: &Applied,
) -> Result<Option<UpdatedCanonicalRefs>, error::Canonical> {
let identity = repo.identity()?;
- // TODO(finto): it's unfortunate that we may end up computing the default
- // branch again after `set_head` is called after the fetch. This is due to
- // the storage capabilities being leaked to this part of the code base.
let rules = identity
.canonical_refs_or_default(|| {
let rule = identity.doc().default_branch_rule()?;
diff --git a/crates/radicle-remote-helper/src/push.rs b/crates/radicle-remote-helper/src/push.rs
index 1f472c51..f79e7cfb 100644
--- a/crates/radicle-remote-helper/src/push.rs
+++ b/crates/radicle-remote-helper/src/push.rs
@@ -400,14 +400,8 @@ pub fn run(
// N.b. special case for handling the canonical ref, since it
// creates a symlink to HEAD
- if *refname == canonical_ref
- && stored
- .set_head()
- .map(|head| head.is_updated())
- .unwrap_or(false)
- {
- print_update();
- continue;
+ if *refname == canonical_ref {
+ stored.set_head_to_default_branch()?;
}
match stored.backend.refname_to_id(refname.as_str()) {
diff --git a/crates/radicle/src/rad.rs b/crates/radicle/src/rad.rs
index 63a931f5..9f2f82e9 100644
--- a/crates/radicle/src/rad.rs
+++ b/crates/radicle/src/rad.rs
@@ -147,7 +147,8 @@ where
)?;
stored.set_remote_identity_root_to(pk, identity)?;
stored.set_identity_head_to(identity)?;
- stored.set_head()?;
+ stored.set_head_to_default_branch()?;
+ stored.set_default_branch_to_canonical_head()?;
let signed = stored.sign_refs(signer)?;
diff --git a/crates/radicle/src/storage.rs b/crates/radicle/src/storage.rs
index 5264a2b1..91bba1e4 100644
--- a/crates/radicle/src/storage.rs
+++ b/crates/radicle/src/storage.rs
@@ -670,9 +670,16 @@ where
/// Allows read-write access to a repository.
pub trait WriteRepository: ReadRepository + SignRepository {
- /// Set the repository head to the canonical branch.
- /// This computes the head based on the delegate set.
- fn set_head(&self) -> Result<SetHead, RepositoryError>;
+ /// Sets the symbolic reference `HEAD` to target the default branch.
+ /// This only depends on the value for the default branch in the identity
+ /// document, and does not require the canonical reference behind the
+ /// default branch to be computed, or even exist.
+ fn set_head_to_default_branch(&self) -> Result<(), RepositoryError>;
+
+ /// Computes the head of the default branch based on the delegate set,
+ /// and sets it.
+ fn set_default_branch_to_canonical_head(&self) -> Result<SetHead, RepositoryError>;
+
/// Set the repository 'rad/id' to the canonical commit, agreed by quorum.
fn set_identity_head(&self) -> Result<Oid, RepositoryError> {
let head = self.canonical_identity_head()?;
diff --git a/crates/radicle/src/storage/git.rs b/crates/radicle/src/storage/git.rs
index 0316b795..21983bb4 100644
--- a/crates/radicle/src/storage/git.rs
+++ b/crates/radicle/src/storage/git.rs
@@ -878,16 +878,39 @@ impl ReadRepository for Repository {
}
impl WriteRepository for Repository {
- fn set_head(&self) -> Result<SetHead, RepositoryError> {
+ fn set_head_to_default_branch(&self) -> Result<(), RepositoryError> {
let head_ref = refname!("HEAD");
+ let branch_ref = self.default_branch()?;
+
+ match self.raw().find_reference(head_ref.as_str()) {
+ Ok(mut head_ref) => {
+ if head_ref.symbolic_target().is_some_and(|t| t != branch_ref.as_str()) {
+ head_ref.symbolic_set_target(branch_ref.as_str(), "set-head (radicle)")?;
+ }
+ Ok(())
+ }
+ Err(err) if git::ext::is_not_found_err(&err) => {
+ self.raw().reference_symbolic(
+ head_ref.as_str(),
+ branch_ref.as_str(),
+ true,
+ "set-head (radicle)",
+ )?;
+ Ok(())
+ }
+ Err(err) => Err(err.into()),
+ }
+ }
+
+ fn set_default_branch_to_canonical_head(&self) -> Result<SetHead, RepositoryError> {
+ let (branch_ref, new) = self.canonical_head()?;
+
let old = self
.raw()
- .refname_to_id(&head_ref)
+ .refname_to_id(&branch_ref)
.ok()
.map(|oid| oid.into());
- let (branch_ref, new) = self.canonical_head()?;
-
if old == Some(new) {
return Ok(SetHead { old, new });
}
@@ -895,10 +918,6 @@ impl WriteRepository for Repository {
self.raw()
.reference(&branch_ref, *new, true, "set-local-branch (radicle)")?;
- log::debug!(target: "storage", "Setting ref: {head_ref} -> {branch_ref}");
- self.raw()
- .reference_symbolic(&head_ref, &branch_ref, true, "set-head (radicle)")?;
-
Ok(SetHead { old, new })
}
diff --git a/crates/radicle/src/test.rs b/crates/radicle/src/test.rs
index 9a9e8eec..7a219531 100644
--- a/crates/radicle/src/test.rs
+++ b/crates/radicle/src/test.rs
@@ -58,7 +58,8 @@ pub fn fetch<W: WriteRepository>(
drop(opts);
repo.set_identity_head()?;
- repo.set_head()?;
+ repo.set_head_to_default_branch()?;
+ repo.set_default_branch_to_canonical_head()?;
let validations = repo.validate()?;
if !validations.is_empty() {
diff --git a/crates/radicle/src/test/storage.rs b/crates/radicle/src/test/storage.rs
index a2ea5db6..b8e44c71 100644
--- a/crates/radicle/src/test/storage.rs
+++ b/crates/radicle/src/test/storage.rs
@@ -326,7 +326,11 @@ impl WriteRepository for MockRepository {
todo!()
}
- fn set_head(&self) -> Result<SetHead, RepositoryError> {
+ fn set_head_to_default_branch(&self) -> Result<(), RepositoryError> {
+ todo!()
+ }
+
+ fn set_default_branch_to_canonical_head(&self) -> Result<SetHead, RepositoryError> {
todo!()
}
Exit code: 0
shell: 'cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny warnings cargo build --all-targets --workspace cargo doc --workspace --no-deps cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name d30db7a2-e65a-40f4-bafa-1b4f16557d02 -v /opt/radcis/ci.rad.levitte.org/cci/state/d30db7a2-e65a-40f4-bafa-1b4f16557d02/s:/d30db7a2-e65a-40f4-bafa-1b4f16557d02/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/d30db7a2-e65a-40f4-bafa-1b4f16557d02/w:/d30db7a2-e65a-40f4-bafa-1b4f16557d02/w -w /d30db7a2-e65a-40f4-bafa-1b4f16557d02/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:bookworm bash /d30db7a2-e65a-40f4-bafa-1b4f16557d02/s/script.sh
time="2025-09-10T22:00:14+02:00" level=error msg="User-selected graph driver \"overlay\" overwritten by graph driver \"vfs\" from database - delete libpod local files (\"/opt/radcis/ci.rad.levitte.org/.local/share/containers/storage\") to resolve. May prevent use of images created by other tools"
time="2025-09-10T22:00:14+02:00" level=error msg="User-selected graph driver \"overlay\" overwritten by graph driver \"vfs\" from database - delete libpod local files (\"/opt/radcis/ci.rad.levitte.org/.local/share/containers/storage\") to resolve. May prevent use of images created by other tools"
+ cargo --version
info: syncing channel updates for '1.88-x86_64-unknown-linux-gnu'
info: latest update on 2025-06-26, rust version 1.88.0 (6b00bc388 2025-06-23)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-src'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-src'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
cargo 1.88.0 (873a06493 2025-05-10)
+ rustc --version
rustc 1.88.0 (6b00bc388 2025-06-23)
+ cargo fmt --check
Diff in /d30db7a2-e65a-40f4-bafa-1b4f16557d02/w/crates/radicle/src/storage/git.rs:884:
match self.raw().find_reference(head_ref.as_str()) {
Ok(mut head_ref) => {
- if head_ref.symbolic_target().is_some_and(|t| t != branch_ref.as_str()) {
+ if head_ref
+ .symbolic_target()
+ .is_some_and(|t| t != branch_ref.as_str())
+ {
head_ref.symbolic_set_target(branch_ref.as_str(), "set-head (radicle)")?;
}
Ok(())
Exit code: 1
{
"response": "finished",
"result": "failure"
}