rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwood00e85d6303bc4ed7132b00acdc88f41e2ceaf695
{
"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": "5109cda98885b00c1d4f7165013e4d66f9b7512f",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": "lorenz"
},
"title": "node: Use winpipe for control socket on Windows",
"state": {
"status": "open",
"conflicts": []
},
"before": "a670b6e668761ea5893b7d343b4515b888712f14",
"after": "00e85d6303bc4ed7132b00acdc88f41e2ceaf695",
"commits": [
"00e85d6303bc4ed7132b00acdc88f41e2ceaf695"
],
"target": "a670b6e668761ea5893b7d343b4515b888712f14",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "5109cda98885b00c1d4f7165013e4d66f9b7512f",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": "lorenz"
},
"description": "As `std::os::unix` is obviously not available on Windows, resort to the\n`winpipe` crate, which implements a very similar API for named pipes.\n\nApart from simple changes to imports, we need to jump through a few\nhoops because the `std::io::{Read, Write}` impls are on `&mut` for\n`winpipe`, thus yielding different mutability requirements on Unix vs.\nWindows.\n\nWe resort to cloning, but as this fallible, swallow the added complexity\nof a platform-dependent implementation to not risk panics on Unix.",
"base": "a670b6e668761ea5893b7d343b4515b888712f14",
"oid": "55c073044d0515f4782f7823bac55ea68a8c3db6",
"timestamp": 1755855522
},
{
"id": "f2060aa6253e8d2ce9dd0a411f9d1be77880a516",
"author": {
"id": "did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz",
"alias": "lorenz"
},
"description": "Review",
"base": "a670b6e668761ea5893b7d343b4515b888712f14",
"oid": "00e85d6303bc4ed7132b00acdc88f41e2ceaf695",
"timestamp": 1755864816
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "64b9cec3-10d0-4268-819f-c4949dda5d54"
},
"info_url": "https://cci.rad.levitte.org//64b9cec3-10d0-4268-819f-c4949dda5d54.html"
}
Started at: 2025-08-22 14:13:39.586466+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/64b9cec3-10d0-4268-819f-c4949dda5d54/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 121 issues · 17 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 5109cda98885b00c1d4f7165013e4d66f9b7512f
✓ Switched to branch patch/5109cda at revision f2060aa
✓ Branch patch/5109cda setup to track rad/patches/5109cda98885b00c1d4f7165013e4d66f9b7512f
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout 00e85d6303bc4ed7132b00acdc88f41e2ceaf695
HEAD is now at 00e85d63 node: Use winpipe for control socket on Windows
Exit code: 0
$ git show 00e85d6303bc4ed7132b00acdc88f41e2ceaf695
commit 00e85d6303bc4ed7132b00acdc88f41e2ceaf695
Author: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Date: Thu Aug 21 18:16:03 2025 +0200
node: Use winpipe for control socket on Windows
As `std::os::unix` is obviously not available on Windows, resort to the
`winpipe` crate, which implements a very similar API for named pipes.
Apart from simple changes to imports, we need to jump through a few
hoops because the `std::io::{Read, Write}` impls are on `&mut` for
`winpipe`, thus yielding different mutability requirements on Unix vs.
Windows.
We resort to cloning, but as this fallible, swallow the added complexity
of a platform-dependent implementation to not risk panics on Unix.
diff --git a/Cargo.lock b/Cargo.lock
index 0cbb7436..c3059532 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2870,6 +2870,7 @@ dependencies = [
"tempfile",
"test-log",
"thiserror 1.0.69",
+ "winpipe",
]
[[package]]
diff --git a/crates/radicle-node/Cargo.toml b/crates/radicle-node/Cargo.toml
index e5fea061..667b084c 100644
--- a/crates/radicle-node/Cargo.toml
+++ b/crates/radicle-node/Cargo.toml
@@ -51,6 +51,9 @@ thiserror = { workspace = true }
[target.'cfg(target_os = "linux")'.dependencies]
radicle-systemd = { workspace = true, optional = true }
+[target.'cfg(windows)'.dependencies]
+winpipe = { workspace = true }
+
[dev-dependencies]
qcheck = { workspace = true }
qcheck-macros = { workspace = true }
diff --git a/crates/radicle-node/src/control.rs b/crates/radicle-node/src/control.rs
index 0f40b74d..3ec2a739 100644
--- a/crates/radicle-node/src/control.rs
+++ b/crates/radicle-node/src/control.rs
@@ -2,11 +2,14 @@
use std::io::prelude::*;
use std::io::BufReader;
use std::io::LineWriter;
-use std::os::unix::net::UnixListener;
-use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::{io, net, time};
+#[cfg(unix)]
+use std::os::unix::net::{UnixListener as Listener, UnixStream as Stream};
+#[cfg(windows)]
+use winpipe::{WinListener as Listener, WinStream as Stream};
+
use radicle::node::Handle;
use serde_json as json;
@@ -30,7 +33,7 @@ pub enum Error {
}
/// Listen for commands on the control socket, and process them.
-pub fn listen<E, H>(listener: UnixListener, handle: H) -> Result<(), Error>
+pub fn listen<E, H>(listener: Listener, handle: H) -> Result<(), Error>
where
H: Handle<Error = runtime::HandleError> + 'static,
H::Sessions: serde::Serialize,
@@ -42,11 +45,11 @@ where
for incoming in listener.incoming() {
match incoming {
- Ok(mut stream) => {
+ Ok(stream) => {
let handle = handle.clone();
thread::spawn(&nid, "control", move || {
- if let Err(e) = command(&stream, handle) {
+ if let Err((e, mut stream)) = command(stream, handle) {
log::error!(target: "control", "Command returned error: {e}");
CommandResult::error(e).to_writer(&mut stream).ok();
@@ -74,15 +77,56 @@ enum CommandError {
Io(#[from] io::Error),
}
-fn command<E, H>(stream: &UnixStream, mut handle: H) -> Result<(), CommandError>
+#[cfg(unix)]
+fn command<E, H>(stream: Stream, handle: H) -> Result<(), (CommandError, Stream)>
+where
+ H: Handle<Error = runtime::HandleError> + 'static,
+ H::Sessions: serde::Serialize,
+ CommandResult<E>: From<H::Event>,
+ E: serde::Serialize,
+{
+ let reader = BufReader::new(&stream);
+ let writer = LineWriter::new(&stream);
+
+ command_internal(reader, writer, handle).map_err(|e| (e, stream))
+}
+
+
+/// Due to different mutability requirements between Unix and Windows,
+/// we are forced to clone the stream on Windows.
+///
+/// # Errors
+///
+/// As of winpipe 0.1.1, [`WinStream::try_clone`] is actually infallible.
+#[cfg(windows)]
+fn command<E, H>(mut stream: Stream, handle: H) -> Result<(), (CommandError, Stream)>
+where
+ H: Handle<Error = runtime::HandleError> + 'static,
+ H::Sessions: serde::Serialize,
+ CommandResult<E>: From<H::Event>,
+ E: serde::Serialize,
+{
+ let mut clone = stream.try_clone().map_err(|e| (e.into(), stream))?;
+ let reader = BufReader::new(&mut clone);
+ let writer = LineWriter::new(&mut stream);
+
+ command_internal(reader, writer, handle).map_err(|e| (e, stream))
+}
+
+#[inline(always)]
+fn command_internal<E, H, R, W>(
+ mut reader: BufReader<R>,
+ mut writer: LineWriter<W>,
+ mut handle: H,
+) -> Result<(), CommandError>
where
H: Handle<Error = runtime::HandleError> + 'static,
H::Sessions: serde::Serialize,
CommandResult<E>: From<H::Event>,
E: serde::Serialize,
+ R: io::Read,
+ W: io::Write,
{
- let mut reader = BufReader::new(stream);
- let mut writer = LineWriter::new(stream);
let mut line = String::new();
reader.read_line(&mut line)?;
@@ -241,7 +285,6 @@ fn fetch<W: Write, H: Handle<Error = runtime::HandleError>>(
#[cfg(test)]
mod tests {
use std::io::prelude::*;
- use std::os::unix::net::UnixStream;
use std::thread;
use super::*;
@@ -257,7 +300,7 @@ mod tests {
let handle = test::handle::Handle::default();
let socket = tmp.path().join("alice.sock");
let rids = test::arbitrary::set::<RepoId>(1..3);
- let listener = UnixListener::bind(&socket).unwrap();
+ let listener = Listener::bind(&socket).unwrap();
thread::spawn({
let handle = handle.clone();
@@ -267,7 +310,7 @@ mod tests {
for rid in &rids {
let stream = loop {
- if let Ok(stream) = UnixStream::connect(&socket) {
+ if let Ok(stream) = Stream::connect(&socket) {
break stream;
}
};
@@ -305,7 +348,7 @@ mod tests {
let socket = tmp.path().join("node.sock");
let proj = test::arbitrary::gen::<RepoId>(1);
let peer = test::arbitrary::gen::<NodeId>(1);
- let listener = UnixListener::bind(&socket).unwrap();
+ let listener = Listener::bind(&socket).unwrap();
let mut handle = Node::new(&socket);
thread::spawn({
diff --git a/crates/radicle-node/src/runtime.rs b/crates/radicle-node/src/runtime.rs
index b0de2905..8639e93b 100644
--- a/crates/radicle-node/src/runtime.rs
+++ b/crates/radicle-node/src/runtime.rs
@@ -1,10 +1,14 @@
pub mod handle;
pub mod thread;
-use std::os::unix::net::UnixListener;
use std::path::PathBuf;
use std::{fs, io, net};
+#[cfg(unix)]
+use std::os::unix::net::UnixListener as Listener;
+#[cfg(windows)]
+use winpipe::WinListener as Listener;
+
use crossbeam_channel as chan;
use cyphernet::Ecdh;
use netservices::resource::NetAccept;
@@ -100,9 +104,9 @@ impl From<service::Error> for Error {
/// Wraps a [`UnixListener`] but tracks its origin.
pub enum ControlSocket {
/// The listener was created by binding to it.
- Bound(UnixListener, PathBuf),
+ Bound(Listener, PathBuf),
/// The listener was received via socket activation.
- Received(UnixListener),
+ Received(Listener),
}
/// Holds join handles to the client threads, as well as a client handle.
@@ -313,7 +317,7 @@ impl Runtime {
}
#[cfg(all(feature = "systemd", target_os = "linux"))]
- fn receive_listener() -> Option<UnixListener> {
+ fn receive_listener() -> Option<Listener> {
use std::os::fd::FromRawFd;
match radicle_systemd::listen::fd("control") {
Ok(Some(fd)) => {
@@ -327,7 +331,7 @@ impl Runtime {
Some(unsafe {
// SAFETY: We take ownership of this FD from systemd,
// which guarantees that it is open.
- UnixListener::from_raw_fd(fd)
+ Listener::from_raw_fd(fd)
})
}
Ok(None) => None,
@@ -348,7 +352,7 @@ impl Runtime {
}
log::info!(target: "node", "Binding control socket {}..", &path.display());
- match UnixListener::bind(&path) {
+ match Listener::bind(&path) {
Ok(sock) => Ok(ControlSocket::Bound(sock, path)),
Err(err) if err.kind() == io::ErrorKind::AddrInUse => Err(Error::AlreadyRunning(path)),
Err(err) => Err(err.into()),
diff --git a/crates/radicle-node/src/runtime/handle.rs b/crates/radicle-node/src/runtime/handle.rs
index f93f61cb..d071308f 100644
--- a/crates/radicle-node/src/runtime/handle.rs
+++ b/crates/radicle-node/src/runtime/handle.rs
@@ -1,9 +1,13 @@
use std::net;
-use std::os::unix::net::UnixStream;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{fmt, io, time};
+#[cfg(unix)]
+use std::os::unix::net::UnixStream as Stream;
+#[cfg(windows)]
+use winpipe::WinStream as Stream;
+
use crossbeam_channel as chan;
use radicle::node::events::{Event, Events};
use radicle::node::policy;
@@ -320,7 +324,7 @@ impl radicle::node::Handle for Handle {
// Send a shutdown request to our own control socket. This is the only way to kill the
// control thread gracefully. Since the control thread may have called this function,
// the control socket may already be disconnected. Ignore errors.
- UnixStream::connect(self.home.socket())
+ Stream::connect(self.home.socket())
.and_then(|sock| Command::Shutdown.to_writer(sock))
.ok();
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 64b9cec3-10d0-4268-819f-c4949dda5d54 -v /opt/radcis/ci.rad.levitte.org/cci/state/64b9cec3-10d0-4268-819f-c4949dda5d54/s:/64b9cec3-10d0-4268-819f-c4949dda5d54/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/64b9cec3-10d0-4268-819f-c4949dda5d54/w:/64b9cec3-10d0-4268-819f-c4949dda5d54/w -w /64b9cec3-10d0-4268-819f-c4949dda5d54/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:bookworm bash /64b9cec3-10d0-4268-819f-c4949dda5d54/s/script.sh
time="2025-08-22T14:13:41+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-08-22T14:13:41+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 /64b9cec3-10d0-4268-819f-c4949dda5d54/w/crates/radicle-node/src/control.rs:91:
command_internal(reader, writer, handle).map_err(|e| (e, stream))
}
-
/// Due to different mutability requirements between Unix and Windows,
/// we are forced to clone the stream on Windows.
///
Exit code: 1
{
"response": "finished",
"result": "failure"
}