rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwood59d1548e5afd285fe0a051b3ab9ed127937df77d
{
"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": "Created",
"patch": {
"id": "875cc60381632048bfe6bee8ffa8b49f63921fec",
"author": {
"id": "did:key:z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5",
"alias": "sebastinez"
},
"title": "httpd: Fetch projects after starting seeding it",
"state": {
"status": "archived",
"conflicts": []
},
"before": "0908c65f504ec56bcb368b3a020ddee5f251eb56",
"after": "59d1548e5afd285fe0a051b3ab9ed127937df77d",
"commits": [
"59d1548e5afd285fe0a051b3ab9ed127937df77d",
"936c69c9df5bfd5e1354cf8acd7fb72af54a95ba"
],
"target": "6cfed884bf37cba1e0d8e97fa8b0e94df4a04b1f",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "875cc60381632048bfe6bee8ffa8b49f63921fec",
"author": {
"id": "did:key:z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5",
"alias": "sebastinez"
},
"description": "Implements the fetching behavior from `radicle_cli::commands::rad_sync::fetch`\ninto the `PUT /node/policies/repos/:rid` endpoint.\n\nThis allows users to send a request for adding a repo to their seeding\ntable and the repo will be fetched from where ever possible, allowing\nusers to interact with it quickly and contribute to its seeding.\n\nAlso moving `RepoSync` from `radicle-cli` to `radicle::node`\n\nThis to allow `radicle-httpd` using `RepoSync` without importing it from\n`radicle-cli`.",
"base": "0908c65f504ec56bcb368b3a020ddee5f251eb56",
"oid": "6d6618a907ae372fb6edb7ca333bc045f9521f02",
"timestamp": 1708623235
},
{
"id": "7cba2d27cdc2a92699c7ae86c96f9bd7961711a3",
"author": {
"id": "did:key:z6MkkfM3tPXNPrPevKr3uSiQtHPuwnNhu2yUVjgd2jXVsVz5",
"alias": "sebastinez"
},
"description": "Revert references change to `rad_sync::fetch`",
"base": "0908c65f504ec56bcb368b3a020ddee5f251eb56",
"oid": "59d1548e5afd285fe0a051b3ab9ed127937df77d",
"timestamp": 1708685180
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "680676be-ed06-4fa1-8021-4a8119a36913"
},
"info_url": "https://cci.rad.levitte.org//680676be-ed06-4fa1-8021-4a8119a36913.html"
}
Started at: 2025-10-21 18:32:55.028136+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/680676be-ed06-4fa1-8021-4a8119a36913/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 125 issues · 15 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 875cc60381632048bfe6bee8ffa8b49f63921fec
✓ Switched to branch patch/875cc60 at revision 7cba2d2
✓ Branch patch/875cc60 setup to track rad/patches/875cc60381632048bfe6bee8ffa8b49f63921fec
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout 59d1548e5afd285fe0a051b3ab9ed127937df77d
HEAD is now at 59d1548e httpd: Fetch projects after starting seeding it
Exit code: 0
$ git show 59d1548e5afd285fe0a051b3ab9ed127937df77d
commit 59d1548e5afd285fe0a051b3ab9ed127937df77d
Author: Sebastian Martinez <me@sebastinez.dev>
Date: Thu Feb 22 14:32:50 2024 -0300
httpd: Fetch projects after starting seeding it
Implements the fetching behavior from `radicle_cli::commands::rad_sync::fetch`
into the `PUT /node/policies/repos/:rid` endpoint.
This allows users to send a request for adding a repo to their seeding
table and the repo will be fetched from where ever possible, allowing
users to interact with it quickly and contribute to its seeding.
diff --git a/radicle-cli/src/commands/sync.rs b/radicle-cli/src/commands/sync.rs
index b9567c48..32372ae6 100644
--- a/radicle-cli/src/commands/sync.rs
+++ b/radicle-cli/src/commands/sync.rs
@@ -500,14 +500,14 @@ pub fn fetch(
// Fetch from connected seeds.
let connected = connected
- .iter()
+ .into_iter()
.filter(|c| !results.contains(&c.nid))
- .map(|c| &c.nid)
+ .map(|c| c.nid)
.take(replicas)
.collect::<Vec<_>>();
for nid in connected {
- let result = fetch_from(rid, nid, timeout, node)?;
- results.push(*nid, result);
+ let result = fetch_from(rid, &nid, timeout, node)?;
+ results.push(nid, result);
}
// Try to connect to disconnected seeds and fetch from them.
diff --git a/radicle-httpd/src/api.rs b/radicle-httpd/src/api.rs
index 75fb6218..62466383 100644
--- a/radicle-httpd/src/api.rs
+++ b/radicle-httpd/src/api.rs
@@ -19,7 +19,7 @@ use radicle::cob::patch;
use radicle::identity::{DocAt, RepoId};
use radicle::node::policy::Scope;
use radicle::node::routing::Store;
-use radicle::node::{Handle, NodeId};
+use radicle::node::Handle;
use radicle::storage::{ReadRepository, ReadStorage};
use radicle::{Node, Profile};
@@ -156,8 +156,6 @@ pub struct CobsQuery<T> {
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PoliciesQuery {
- /// The NID from which to fetch from after tracking a repo.
- pub from: Option<NodeId>,
pub scope: Option<Scope>,
}
diff --git a/radicle-httpd/src/api/v1/node.rs b/radicle-httpd/src/api/v1/node.rs
index 0181868d..fd976be2 100644
--- a/radicle-httpd/src/api/v1/node.rs
+++ b/radicle-httpd/src/api/v1/node.rs
@@ -8,7 +8,7 @@ use serde_json::json;
use radicle::identity::RepoId;
use radicle::node::routing::Store;
-use radicle::node::{policy, AliasStore, Handle, NodeId, DEFAULT_TIMEOUT};
+use radicle::node::{self, policy, AliasStore, FetchResults, Handle, NodeId, DEFAULT_TIMEOUT};
use radicle::Node;
use crate::api::error::Error;
@@ -109,17 +109,79 @@ async fn node_policies_seed_handler(
Query(qs): Query<PoliciesQuery>,
) -> impl IntoResponse {
api::auth::validate(&ctx, &token).await?;
- let mut node = Node::new(ctx.profile.socket());
- node.seed(project, qs.scope.unwrap_or_default())?;
-
- if let Some(from) = qs.from {
- let results = node.fetch(project, from, DEFAULT_TIMEOUT)?;
+ let profile = &ctx.profile;
+ let mut node = Node::new(profile.socket());
+ let updated = node.seed(project, qs.scope.unwrap_or_default())?;
+ let outcome = if updated { "updated" } else { "exists" };
+
+ let local = ctx.profile.id();
+ let seeds = node.seeds(project)?;
+ let settings = node::seed::RepoSync::default().with_profile(profile);
+ let replicas = settings
+ .replicas
+ .min(seeds.iter().filter(|s| s.nid != *local).count());
+ let sessions = node.sessions()?;
+ let mut results = FetchResults::default();
+ let (connected, mut disconnected) = seeds.partition();
+
+ // Fetch from specified seeds, plus our preferred seeds.
+ for nid in &settings.seeds {
+ if !sessions.iter().any(|s| s.nid == *nid) {
+ // Node isn't connected, skipping it.
+ continue;
+ }
+ let result = node.fetch(project, *nid, DEFAULT_TIMEOUT)?;
+ results.push(*nid, result);
+ }
+ if results.success().count() >= replicas {
return Ok::<_, Error>((
StatusCode::OK,
- Json(json!({ "success": true, "results": results })),
+ Json(json!({ "success": true, "outcome": outcome, "results": results })),
));
}
- Ok::<_, Error>((StatusCode::OK, Json(json!({ "success": true }))))
+
+ // Fetch from connected seeds.
+ let connected = connected
+ .into_iter()
+ .filter(|c| !results.contains(&c.nid))
+ .map(|c| c.nid)
+ .take(replicas)
+ .collect::<Vec<_>>();
+ for nid in connected {
+ let result = node.fetch(project, nid, DEFAULT_TIMEOUT)?;
+ results.push(nid, result);
+ }
+
+ // Try to connect to disconnected seeds and fetch from them.
+ while results.success().count() < replicas {
+ let Some(seed) = disconnected.pop() else {
+ break;
+ };
+ if seed.nid == *local {
+ // Skip our own node.
+ continue;
+ }
+ for addr in seed.addrs.into_iter().map(|ka| ka.addr) {
+ if let node::ConnectResult::Connected = node.connect(
+ seed.nid,
+ addr,
+ node::ConnectOptions {
+ persistent: false,
+ timeout: DEFAULT_TIMEOUT,
+ },
+ )? {
+ let result = node.fetch(project, seed.nid, DEFAULT_TIMEOUT)?;
+ results.push(seed.nid, result);
+ } else {
+ continue;
+ }
+ }
+ }
+
+ Ok::<_, Error>((
+ StatusCode::OK,
+ Json(json!({ "success": true, "outcome": outcome, "results": results })),
+ ))
}
/// Unseed a repo.
diff --git a/radicle/src/node.rs b/radicle/src/node.rs
index 08e6442e..d57ca7ed 100644
--- a/radicle/src/node.rs
+++ b/radicle/src/node.rs
@@ -694,7 +694,7 @@ impl<S: ToString> From<Result<(Vec<RefUpdate>, HashSet<NodeId>), S>> for FetchRe
}
/// Holds multiple fetch results.
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Serialize)]
pub struct FetchResults(Vec<(NodeId, FetchResult)>);
impl FetchResults {
Exit code: 0
shell: 'cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny clippy::all cargo build --all-targets --workspace cargo doc --workspace cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name 680676be-ed06-4fa1-8021-4a8119a36913 -v /opt/radcis/ci.rad.levitte.org/cci/state/680676be-ed06-4fa1-8021-4a8119a36913/s:/680676be-ed06-4fa1-8021-4a8119a36913/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/680676be-ed06-4fa1-8021-4a8119a36913/w:/680676be-ed06-4fa1-8021-4a8119a36913/w -w /680676be-ed06-4fa1-8021-4a8119a36913/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:bookworm bash /680676be-ed06-4fa1-8021-4a8119a36913/s/script.sh
+ cargo --version
info: syncing channel updates for '1.74-x86_64-unknown-linux-gnu'
info: latest update on 2023-12-07, rust version 1.74.1 (a28077b28 2023-12-04)
info: downloading component 'cargo'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: installing component 'cargo'
info: installing component 'rust-std'
info: installing component 'rustc'
cargo 1.74.1 (ecb9851af 2023-10-18)
+ rustc --version
rustc 1.74.1 (a28077b28 2023-12-04)
+ cargo fmt --check
error: 'cargo-fmt' is not installed for the toolchain '1.74-x86_64-unknown-linux-gnu'.
To install, run `rustup component add --toolchain 1.74-x86_64-unknown-linux-gnu rustfmt`
Exit code: 1
{
"response": "finished",
"result": "failure"
}