remote-helper/service: Introduce `NodeSession`

Prefactor to add a trait capturing behaviour of the node.
This commit is contained in:
Adrian Duke 2026-02-09 23:09:02 +00:00 committed by Lorenz Leutgeb
parent 56253b529e
commit 27493c2229
3 changed files with 100 additions and 61 deletions

View File

@ -243,6 +243,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
let mut opts = Options::default();
let mut expected_refs = Vec::new();
let git = service::RealGitService;
let mut node = service::RealNodeSession::new(&profile);
if let Err(e) = radicle::io::set_file_limit(4096) {
if debug {
@ -310,6 +311,7 @@ pub fn run(profile: radicle::Profile) -> Result<(), Error> {
opts,
&expected_refs,
&git,
&mut node,
)?;
for line in output {

View File

@ -4,7 +4,6 @@ mod canonical;
mod error;
use std::collections::HashMap;
use std::io::IsTerminal;
use std::process::ExitStatus;
use std::str::FromStr;
use std::{assert_eq, io};
@ -22,16 +21,16 @@ use radicle::crypto;
use radicle::explorer::ExplorerResource;
use radicle::identity::{CanonicalRefs, Did};
use radicle::node;
use radicle::node::{Handle, NodeId};
use radicle::node::NodeId;
use radicle::storage;
use radicle::storage::git::transport::local::Url;
use radicle::storage::{ReadRepository, SignRepository as _, WriteRepository};
use radicle::Profile;
use radicle::{git, rad};
use radicle_cli as cli;
use radicle_cli::terminal as term;
use crate::service::GitService;
use crate::service::NodeSession;
use crate::{hint, read_line, warn, Options, Verbosity};
#[derive(Debug, Error)]
@ -254,6 +253,7 @@ pub fn run(
opts: Options,
expected_refs: &[String],
git: &impl GitService,
node: &mut impl NodeSession,
) -> Result<Vec<String>, Error> {
// Don't allow push if either of these conditions is true:
//
@ -462,10 +462,10 @@ pub fn run(
// Connect to local node and announce refs to the network.
// If our node is not running, we simply skip this step, as the
// refs will be announced eventually, when the node restarts.
let node = radicle::Node::new(profile.socket());
if node.is_running() {
// Nb. allow this to fail. The push to local storage was still successful.
sync(stored, ok.into_values().flatten(), opts, node, profile).ok();
node.sync(stored, ok.into_values().flatten().collect(), opts, profile)
.ok();
} else if hints {
hint("offline push, your node is not running");
hint("to sync with the network, run `rad node start`");
@ -1038,59 +1038,3 @@ fn push_ref(
Ok(())
}
/// Sync with the network.
fn sync(
repo: &storage::git::Repository,
updated: impl Iterator<Item = ExplorerResource>,
opts: Options,
mut node: radicle::Node,
profile: &Profile,
) -> Result<(), cli::node::SyncError> {
let progress = if io::stderr().is_terminal() {
term::PaintTarget::Stderr
} else {
term::PaintTarget::Hidden
};
let result = cli::node::announce(
repo,
cli::node::SyncSettings::default().with_profile(profile),
cli::node::SyncReporting {
progress,
completion: term::PaintTarget::Stderr,
debug: opts.sync_debug,
},
&mut node,
profile,
)?;
let mut urls = Vec::new();
if let Some(result) = result {
for seed in profile.config.preferred_seeds.iter() {
if result.is_synced(&seed.id) {
for resource in updated {
let url = profile
.config
.public_explorer
.url(seed.addr.host.clone(), repo.id)
.resource(resource);
urls.push(url);
}
break;
}
}
}
// Print URLs to the updated resources.
if !urls.is_empty() {
eprintln!();
for url in urls {
eprintln!(" {}", term::format::dim(url));
}
eprintln!();
}
Ok(())
}

View File

@ -1,9 +1,15 @@
use std::io;
use std::io::IsTerminal;
use std::path::Path;
use std::process;
use radicle::explorer::ExplorerResource;
use radicle::git;
use radicle::node::Handle;
use radicle::storage;
use radicle::Profile;
use radicle_cli::node::{SyncError, SyncReporting, SyncSettings};
use radicle_cli::terminal as term;
/// Abstraction for Git subprocess calls.
pub(super) trait GitService {
@ -38,3 +44,90 @@ impl GitService for RealGitService {
git::run(working, args)
}
}
/// Abstraction for Node interaction.
pub(super) trait NodeSession {
fn is_running(&self) -> bool;
fn sync(
&mut self,
repo: &storage::git::Repository,
updated: Vec<ExplorerResource>,
opts: crate::Options,
profile: &Profile,
) -> Result<(), SyncError>;
}
pub(super) struct RealNodeSession {
node: radicle::Node,
}
impl RealNodeSession {
pub(super) fn new(profile: &Profile) -> Self {
Self {
node: radicle::Node::new(profile.socket()),
}
}
}
impl NodeSession for RealNodeSession {
fn is_running(&self) -> bool {
self.node.is_running()
}
fn sync(
&mut self,
repo: &storage::git::Repository,
updated: Vec<ExplorerResource>,
opts: crate::Options,
profile: &Profile,
) -> Result<(), SyncError> {
let progress = if io::stderr().is_terminal() {
term::PaintTarget::Stderr
} else {
term::PaintTarget::Hidden
};
let result = radicle_cli::node::announce(
repo,
SyncSettings::default().with_profile(profile),
SyncReporting {
progress,
completion: term::PaintTarget::Stderr,
debug: opts.sync_debug,
},
&mut self.node,
profile,
)?;
let mut urls = Vec::new();
if let Some(result) = result {
for seed in profile.config.preferred_seeds.iter() {
if result.is_synced(&seed.id) {
for resource in updated {
let url = profile
.config
.public_explorer
.url(seed.addr.host.clone(), repo.id)
.resource(resource);
urls.push(url);
}
break;
}
}
}
// Print URLs to the updated resources.
if !urls.is_empty() {
eprintln!();
for url in urls {
eprintln!(" {}", term::format::dim(url));
}
eprintln!();
}
Ok(())
}
}