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:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "1da708aed81e99e9151deec7012134dfa5f221fe",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"title": "radicle: Split `WriteRepository::set_head` up",
"state": {
"status": "archived",
"conflicts": []
},
"before": "646d4360e7d905f0a9ec87d32c6768c5390eabf3",
"after": "f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7",
"commits": [
"f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7",
"ecab3f6d1969b9ff5dd9a72f6feed8b896d979c7"
],
"target": "3fc92f83fe089263c470b328414d923227cf0e4a",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "1da708aed81e99e9151deec7012134dfa5f221fe",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": null
},
"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": null
},
"description": "Work in Matthias' suggestion and rebase.",
"base": "646d4360e7d905f0a9ec87d32c6768c5390eabf3",
"oid": "f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7",
"timestamp": 1757534266
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "fde2982e-2f4f-47e1-8e59-ad329619e1a7"
},
"info_url": "https://cci.rad.levitte.org//fde2982e-2f4f-47e1-8e59-ad329619e1a7.html"
}
Started at: 2026-09-08 09:06:01.815879+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ 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/fde2982e-2f4f-47e1-8e59-ad329619e1a7/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 38 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
$ rad patch show 1da708aed81e99e9151deec7012134dfa5f221fe -p
╭───────────────────────────────────────────────────────────────────────────────────╮
│ Title radicle: Split `WriteRepository::set_head` up │
│ Patch 1da708aed81e99e9151deec7012134dfa5f221fe │
│ Author lorenz z6MkkPv…WX5sTEz │
│ Head f00a5ed4df3fabc982bae6f4b452cbcb61c3d4c7 │
│ Base 646d4360e7d905f0a9ec87d32c6768c5390eabf3 │
│ Target master │
│ Branches patch/1da708a │
│ Commits ahead 2, behind 838 │
│ Status archived │
│ │
│ 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` │
├───────────────────────────────────────────────────────────────────────────────────┤
│ f00a5ed radicle: Split `WriteRepository::set_head` up │
│ ecab3f6 radicle: More convenience methods to get default branch │
├───────────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 1da708a @ f00d1d6..9cd98d7 by lorenz z6MkkPv…WX5sTEz 1 year ago │
│ └─ ⋄ reviewed by matthiasbeyer z6MkwcU…RDWq1kL 1 year ago │
│ ↑ Revision a50f237 @ 646d436..f00a5ed by lorenz z6MkkPv…WX5sTEz 11 months ago │
╰───────────────────────────────────────────────────────────────────────────────────╯
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 b53020609..9202cb4b7 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 1f472c513..f79e7cfbd 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 63a931f52..9f2f82e9d 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 5264a2b1d..91bba1e43 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 0316b795c..21983bb45 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 9a9e8eec4..7a2195317 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 a2ea5db63..b8e44c714 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!()
}
commit ecab3f6d1969b9ff5dd9a72f6feed8b896d979c7
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Sun Sep 7 14:01:27 2025 +0200
radicle: More convenience methods to get default branch
diff --git a/crates/radicle-remote-helper/src/push.rs b/crates/radicle-remote-helper/src/push.rs
index c41770a89..1f472c513 100644
--- a/crates/radicle-remote-helper/src/push.rs
+++ b/crates/radicle-remote-helper/src/push.rs
@@ -282,8 +282,7 @@ pub fn run(
}
let delegates = stored.delegates()?;
let identity = stored.identity()?;
- let project = identity.project()?;
- let canonical_ref = git::refs::branch(project.default_branch());
+ let canonical_ref = identity.default_branch()?;
let mut set_canonical_refs: Vec<(git::Qualified, git::canonical::Object)> =
Vec::with_capacity(specs.len());
let working = git::raw::Repository::open(working)?;
diff --git a/crates/radicle/src/identity/doc.rs b/crates/radicle/src/identity/doc.rs
index db27c47d6..80c0b692c 100644
--- a/crates/radicle/src/identity/doc.rs
+++ b/crates/radicle/src/identity/doc.rs
@@ -742,12 +742,16 @@ impl Doc {
Ok(proj)
}
+ /// Gets the qualified reference name of the default branch,
+ /// according to the project payload in this document.
+ pub fn default_branch(&self) -> Result<git::Qualified, PayloadError> {
+ Ok(git::refs::branch(self.project()?.default_branch()))
+ }
+
pub fn default_branch_rule(
&self,
) -> Result<(rules::Pattern, rules::ValidRule), DefaultBranchRuleError> {
- let proj = self.project()?;
- let refname = proj.default_branch();
- let pattern = rules::Pattern::try_from(git::refs::branch(refname).to_owned())?;
+ let pattern = rules::Pattern::try_from(self.default_branch()?.to_owned())?;
let rule = rules::Rule::new(
rules::ResolvedDelegates::Delegates(self.delegates.clone()),
self.threshold,
diff --git a/crates/radicle/src/storage.rs b/crates/radicle/src/storage.rs
index 92892959e..5264a2b1d 100644
--- a/crates/radicle/src/storage.rs
+++ b/crates/radicle/src/storage.rs
@@ -522,6 +522,12 @@ pub trait ReadRepository: Sized + ValidateRepository {
/// Returns the [`Oid`] as well as the qualified reference name.
fn head(&self) -> Result<(Qualified, Oid), RepositoryError>;
+ /// Gets the qualified reference name of the default branch of self,
+ /// according to the project payload in the identity document.
+ fn default_branch(&self) -> Result<Qualified, RepositoryError> {
+ Ok(self.identity_doc()?.default_branch()?.to_owned())
+ }
+
/// Compute the canonical head of this repository.
///
/// Ignores any existing `HEAD` reference.
diff --git a/crates/radicle/src/storage/git.rs b/crates/radicle/src/storage/git.rs
index 4bc313d18..0316b795c 100644
--- a/crates/radicle/src/storage/git.rs
+++ b/crates/radicle/src/storage/git.rs
@@ -786,7 +786,7 @@ impl ReadRepository for Repository {
fn canonical_head(&self) -> Result<(Qualified, Oid), RepositoryError> {
let doc = self.identity_doc()?;
- let refname = git::refs::branch(doc.project()?.default_branch());
+ let refname = doc.default_branch()?.to_owned();
let crefs = match doc.canonical_refs()? {
Some(crefs) => crefs,
// Fallback to constructing the default branch via the project
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 fde2982e-2f4f-47e1-8e59-ad329619e1a7 -v /opt/radcis/ci.rad.levitte.org/cci/state/fde2982e-2f4f-47e1-8e59-ad329619e1a7/s:/fde2982e-2f4f-47e1-8e59-ad329619e1a7/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/fde2982e-2f4f-47e1-8e59-ad329619e1a7/w:/fde2982e-2f4f-47e1-8e59-ad329619e1a7/w -w /fde2982e-2f4f-47e1-8e59-ad329619e1a7/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /fde2982e-2f4f-47e1-8e59-ad329619e1a7/s/script.sh
+ 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 /fde2982e-2f4f-47e1-8e59-ad329619e1a7/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"
}