Commit Graph

787 Commits

Author SHA1 Message Date
Lorenz Leutgeb 88bf2a9648
term: Remove typing indication for passwords
`inquire::PasswordDisplayMode::Masked` causes inquire to
render characters of the password text input marked as
different characters (asterisks).

Users reported that they would prefer password text input to not be
rendered at all, i.e., have no indication of input. This is inquire's
default, so remove the lines that set the display mode.
2026-06-12 14:21:21 +02:00
Adrian Duke e991bd7476 cli/examples: Introduce a suite of merge and revert tests for patch.target 2026-06-09 11:37:26 +02:00
Adrian Duke 178bda36d8 radicle-cli/terminal: Add patch target ref to rad patch show 2026-06-09 11:37:26 +02:00
Adrian Duke eb2dded0ed remote-helper: Introduce magic push ref 'refs/for/'
Introduces support for Gerrit-style magic push references via
`refs/for/<branch>`. Pushing to this ref automatically extracts the
target branch and opens a patch against it, bypassing the need for the
push option `patch.target`. Example:

```
$ git push rad HEAD:refs/for/accepted
```

Will open a patch with its `patch.target` set to `refs/heads/accepted`.
2026-06-09 11:37:26 +02:00
Adrian Duke 06e201c927 remote-helper: Add 'patch.target' push option
The remote helper now supports the push option `patch.target`. This
allows users to explicitly specify a target canonical reference when
opening or updating a patch. For example, to open a patch that targets
the branch "backport", use:
```
git push -o patch.target=refs/heads/backport
```

Furthermore, strict merge and revert isolation is now enforced:
patches are only marked as merged or reverted if the commits are
pushed to the target branch of the patch explicitly.
2026-06-09 11:21:33 +02:00
Adrian Duke f0c6abc6ea radicle/cob/patch: Extend MergeTarget
Extend `enum MergeTarget` to include a new variant, `Branch`.
The intended use of this new variant is to allow a `Patch` to have a
target branch other than the default branch.

The `Branch` variant holds a `TargetBranch` which, in turn, is ensured
to be a `Qualified` reference that begins with `refs/heads`, i.e. a
Git branch.
2026-06-09 11:21:33 +02:00
Adrian Duke c7339615a7 cob: Replace 'git2::Oid' -> 'git::raw::Oid' 2026-06-09 11:21:33 +02:00
Adrian Duke db4ad27b4f radicle-cli: Fix broken test 'rad-cob-update'
The `rad_cob_update` test accidentally use the `rad-cob-log` file
instead of `rad-cob-log`.

This change fixes that, and updates the `rad-cob-update` example file
to the latest patch show format.
2026-06-09 11:00:32 +02:00
Adrian Duke d734d69269 radicle-cli: Add labels column to patch list 2026-06-09 11:00:32 +02:00
Adrian Duke fda165ffca radicle: Replace WAN routeable testing IPs with 198.18.0.0/15 private subnet 2026-06-08 11:24:55 +01:00
Adrian Duke 55b4b04002 radicle-node: Replace WAN routeable testing IPs with 198.18.0.0/15 private subnet 2026-06-08 11:23:47 +01:00
Adrian Duke a294e05730 radicle-node: Prevent infinite connection loop on instant dial failure
When a connection drops and the node attempts to reconnect within the
same second, the `last_attempt` timestamp written to the database is
identitcal to the `last_success` timestamp due to the Unix time (seconds)
precision loss.

The `maintain_connections` logic filters eligible peers using the
condition `last_success >= last_attempt`. Because the truncated
timestamps are equal, this evaluates to true, causing the node to
bypass the `CONNECTION_RETRY_DELTA` backoff.

If the OS instantly rejects the dial attempt (e.g. `EHOSTUNREACH`),
the node synchronously queues another connection attempt. This traps
the reactor in an infinite `while` loop.

Changing the condition to `last_success > last_attempt` ensures that a
failed attempt in the same second correctly evaluates to false, forcing
the node to respect the backoff timer and breaking the loop.
2026-06-05 18:27:16 +01:00
Fintan Halpenny e4a16dc40a cli-test: Disable normalizing paths
The normalization of paths was converting valid `\n` characters to `/n`.
This had not been noticed until codespell suggest the change of
`/ndefined` to be `/undefined`.

Use `Assert::normalize_paths(false)` to disable this behaviour.
2026-06-03 11:49:41 +02:00
Fintan Halpenny b397b1a249 workspace: Update snapbox to 1.2 2026-06-03 09:40:26 +02:00
Fintan Halpenny 09e6147ef9 radicle/node/config: Document RateLimit fields
The documentation for `RateLimit::fill_rate` and `RateLimit::capacity`
were missing.

Add the documentation to help users decided on values based on how the
rate limiting works.
2026-06-01 11:36:21 +01:00
Lorenz Leutgeb a65ac048cb cob/change_graph: Log Errors
In case of a signature verification failure or failure to apply an
operation, the error is swallowed, but not logged.

During debugging, it can be crucial to understand whether these errors
occur, so log them.

If trace logging is enabled, also log the tips that are used. This
gives a better overview where operations originate from.
2026-05-28 16:58:55 +01:00
Fintan Halpenny 9ff0e8b01f just: checking for ellipses
Add custom check for ellipses "...", asking for replacement of "…".

Git ranges and the CLI wildcard matches are ignored.

This change includes fixes to all sites that did not pass the check.
2026-05-28 16:52:02 +01:00
Arthur Mariano 0bbedb9650 cli: Fix panic on `rad patch review` without options
When invoked without any of `--patch`, `--delete`, `--accept`,
or `--reject`, the command panicked.

It will now correctly return an error to use `--accept`, `--reject`,
or to supply a message.
Note that `--patch` and `--delete` are removed as these commands are
now obsolete.

The new doc test also documents two previously-untested behaviors
of `rad patch review`: neutral reviews (verdict=None with summary)
and the COB layer's silent no-op when a duplicate review is submitted
(see `test_patch_review_duplicate` in radicle-cli/tests/commands/patch.rs).

Fixes 8ceff5d4b8fa94bcbe243f9f8ef52138f65e495d

Signed-off-by: Arthur Mariano <arthvm@proton.me>
Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2026-05-28 16:14:58 +01:00
Fabrice Bellamy 804fa3e44c cli/examples: rad-patch-merge-on-first-push
Exercise the edge case where Bob, as a delegate, has never pushed to the default branch.
He then merges Alice's patch, and it is correctly marked as merged.
2026-05-27 22:31:11 +01:00
Fabrice Bellamy cf28294473 remote-helper: Account for first push from delegate
An edge case was found where a delegate attempted to merge a patch,
but had not pushed to their default branch yet.
This resulted in the `old` not being defined and the check to revert
and/or merge not happening – so the patch continued to appear as open.

Since `old` only matters for the revert and merge check for the
default branch, resolving the `old` side does its best to find a
commit for the checks.
It does this by falling back to getting the canonical head before the
push, and using that as the old.
2026-05-27 22:31:11 +01:00
Lorenz Leutgeb cf108311dc node: Heap Profiling with DHAT 2026-05-27 12:07:52 +01:00
Fred Gobry 0a03f1c84e crypto: Handle default `SSH_AUTH_SOCK` on Windows
On Windows, SSH Agents commonly use a named pipe with a global default
name. This patch is to give users a more streamline experience as they
will not need to set the environment variable manually anymore.
2026-05-26 09:18:07 +02:00
Lorenz Leutgeb 998ff91e2c cli/id: Print Parent of Revision
When delegates collaborate on the repository identity, their voting
narrows the tree of active proposals to the chain of accepted
proposals. Thus, they crucially must understand which proposals are
siblings, i.e., which proposals share the same parent, because no
two sibling proposals should ever be accepted at the same time, as
this would mean a fork.

To ease collaboration of delegates and improve their overview, also
print the parent revision, if present. Note that the initial revision
is the only revision that has no parent.
2026-05-22 09:09:01 +01:00
Fintan Halpenny 704b6cdabb radicle/node/address: Skip unknown AddressType value
Since `tor` and `i2p` are features, dependents may compile the
`radicle` crate without these features and will not be able to parse
`AddressType`.

Allow parsing of the `AddressType` to fail, and skip if that is the
case.

Further, the same holds for `Address`, and skip if that fails to parse
as well.

Finally, `Address` may parse an address string successfully to
`HostName`, but it may have an `AddressType` that does not match the
`HostName` variant. Guard against this, again skipping if they are not
equal.
For example, `HostName::Dns` is a `String` underneath, so there is
higher chance that something like `deadbeef.onion` parses to this
variant.
2026-05-21 16:08:36 +01:00
Lorenz Leutgeb ff8660d872 radicle: Warn less aggressively on IPv6 addresses
Parsing of IPv6 addresses not enclosed in square brackets causes a
warning to be logged.

This is too strict, since the user can not influence IPv6 addresses
received over the network, but only those in their configuration.

Remove the warning from parsing, and instead carry a boolean to warn the
user later.
2026-05-21 14:52:35 +01:00
Lorenz Leutgeb 7ed9d50ab6
crypto: Update ssh-agent-lib from 0.5.2 to 0.6
This should fix issues handling SSH certificates.

See
<https://radicle.zulipchat.com/#narrow/channel/369873-Support/topic/1.2E9.2E0.3A.20rad.20auth.20fails.20with.20SSH.20certificate.20in.20agent/with/596358640>.
2026-05-21 13:10:35 +02:00
Lorenz Leutgeb 09b1f1f77c
node: Fix handling absence of systemd credential
In case `radicle_systemd::credential::path` returns `Ok(None)`, a
misleading warning is logged.

Handle this case separately and do not emit a warning.
2026-05-20 09:36:22 +02:00
Fintan Halpenny 1f40b32b6a protocol: provide more information for invalid timestamps
The invalid timestamp error will only tell the timestamp of the
offending node's message.

Currently, there are issues where that error is occurring but the
timestamp seems legitimate. This patch adds the running node's
timestamp to help debug the issue.
2026-05-18 18:06:26 +02:00
Fintan Halpenny ac5fc67428 fetch: Update gix packages
Several vulnerabilities were found in the gix packages[^1][^2][^3].

Update the packages to their latest versions which include the fixes.

The only required change is in `ls_refs`.
`RefPrefixes` was introduced and this fixes the issue with `ref-prefix`.
This should now improve the ls-refs stage and only return references
that the client is asking for, as opposed to all references.

[^1]: https://github.com/radicle-dev/heartwood/security/dependabot/38
[^2]: https://github.com/radicle-dev/heartwood/security/dependabot/39
[^3]: https://github.com/radicle-dev/heartwood/security/dependabot/36
2026-05-18 14:57:33 +02:00
stefan 1000402536 node: upgrade cyphernet to fix a panic in socks5-client when using artix
cyphernet 0.5.4 upgrades socks5-client to 0.4.3, which fixes a panic
that occurred when using tor's new artix implementation via socks5.

The upstream fix (authored by me):
https://github.com/cyphernet-labs/cyphernet.rs/pull/18

Diff:
https://github.com/cyphernet-labs/cyphernet.rs/compare/v0.5.3...v0.5.4
2026-05-15 09:06:46 +01:00
Adrian Duke caee776c38 log: New crate for logger implementations
Move logging-related module `radicle::logging` into its own crate.

While at it, remove the "logger" feature flag from `radicle`.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
2026-05-11 16:44:37 +01:00
Lorenz Leutgeb 420af3b710 workspace/rust: 1.90 → 1.95
The update to `flake.lock` is a simple

    nix flake update rust-overlay

to be able to reach 1.95.
2026-05-11 12:09:04 +01:00
Lorenz Leutgeb 9ea040ccd0 rust/msrv: 1.85.0 → 1.88.0
`cargo check` fails because `human-panic` and `sysinfo` require at least
1.88.0.

    +++ command cargo check --release --locked --all-targets
    error: rustc 1.85.0 is not supported by the following packages:
      human-panic@2.0.6 requires rustc 1.88
      sysinfo@0.37.2 requires rustc 1.88

Bump MSRV to fix this.

1.88.0 introduced [let chains], which in turn has `clippy` warn about
nested if statements. All of these sites are fixed in this change.

1.87.0 introduced [`is_multiple_of`], which is a more readable version
of `x % y == 0`.

[let chains]: https://blog.rust-lang.org/2025/06/26/Rust-1.88.0/#let-chains
[`is_multiple_of`]: https://doc.rust-lang.org/std/primitive.usize.html#method.is_multiple_of
2026-05-11 11:23:18 +01:00
Lorenz Leutgeb 0a0e70b1d1
node/systemd: Fix reading credentials
In `cb57e6560fe7767e79b87473a11261fbb82ce84c` the loading of systemd
credentials was refactored. The idea was to only change the accepted IDs
slightly after switching from radicle.xyz to radicle.dev. Unfortunately,
this was done in a sloppy way. The two cases for passphrase and secret
actually differ in ways that was overlooked.

Rectify these mistakes and restore the old behavior:
 1. The logic to read the passphrase from the file that contains the
    credential is moved out of `load_credential` since it only applies
    to the passphrase, not the secret.
 2. The warnings printed are now generic, and will not mention the
    passphrase in case reading the path for the secret fails.
2026-05-07 22:53:57 +02:00
Daniel Norman 6b460c4429 logger: Respect config file log level
- Fixes a bug where the log level set in the config file was
  ignored: `Logger` and `StderrLogger` captured the level in a
  `self.level` field at construction time and checked it in
  `Log::enabled`. After config was loaded, the global
  `log::set_max_level` was updated but `self.level` was not,
  so verbose messages were dropped by the per-instance filter
  even when the global filter allowed them.
- Make `log::set_max_level` the single source of truth: remove
  the `level` field and have `Log::enabled` defer to
  `log::max_level()` so the two filters can no longer drift.
- Update call sites to construct loggers without a level.
- Disable the structured logger's internal filter (set to "trace") so
  that it also falls back to `log::set_max_level`.
2026-05-07 16:23:28 +01:00
Lorenz Leutgeb 9177146794 radicle/web: Fix schema of `Config::description`
The JSON Schema for the field `radicle::web::Config::description` is specified
to be a URI, which is wrong.

Remove the additional attribute.
2026-05-07 15:23:31 +01:00
Lorenz Leutgeb ee9a9de36d radicle/web: Relax deserialization of `Config`
Allow omission of `pinned` and its members.
2026-05-07 15:23:31 +01:00
Fintan Halpenny 53db994146 schemars: Release 0.8.0 2026-05-06 21:15:54 +01:00
Fintan Halpenny ad068309c9 fetch: Release 0.20.0 2026-05-06 21:15:54 +01:00
Fintan Halpenny ab3621d5d4 remote-helper: Release 0.17.0 2026-05-06 21:15:54 +01:00
Fintan Halpenny f5c39dd603 cob: Release 0.20.0 2026-05-06 21:15:54 +01:00
Fintan Halpenny baf533b37b cli: Release 0.21.0 2026-05-06 21:15:54 +01:00
Fintan Halpenny 342b4d963a node: Release 0.20.0 2026-05-06 21:00:14 +01:00
Fintan Halpenny 622f3827b5 protocol: Release 0.8.0 2026-05-06 21:00:14 +01:00
Fintan Halpenny 2707f6b4b1 radicle: Release 0.24.0 2026-05-06 21:00:14 +01:00
Fintan Halpenny 3486c028ea term: Release 0.18.0 2026-05-06 21:00:14 +01:00
Fintan Halpenny 5991bdfa9a systemd: Release 0.13.0 2026-05-06 21:00:14 +01:00
Fintan Halpenny 2871417808 oid: Release 0.2.0 2026-05-06 21:00:14 +01:00
Fintan Halpenny 4ca2098cb7 core: Release 0.3.0 2026-05-06 21:00:14 +01:00
Fintan Halpenny 64a419d79c crypto: Release 0.17.0 2026-05-06 21:00:14 +01:00
Lorenz Leutgeb 3f81e83d36 radicle/crefs: Support Symbolic References
Canonical references can not be used to model symbolic references.

Relax this restriction by adding another member to the payload, named
"symbolic". Its key-value/name-target pairs then translate directly to
canonical symbolic references.

Care is taken to not allow circular references, and that there always
is a rule that could generate the target of a symbolic reference
(or a chain of symbolic references). Still, a symbolic reference may
dangle (for example when its target reference cannot be computed
because of divergence), but at least it can be prevented that a
symref *always* dangles, because there is no rule that would produce
its target.

Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2026-05-06 20:51:04 +02:00
Adrian Duke 080790d84e radicle/git/canonical: Add explicit test case for fast_glob pattern match syntax 2026-05-05 14:41:52 +01:00
Adrian Duke 80d30d3b59 e2e: Fix false-positive in canonical ref update test
Previously `test_fetch_emits_canonical_ref_update` was passing by
catching a leftover `CanonicalRefUpdated` event from the initial
repository fetch. Furthermore, the new commit was written to the
un-namespaced root. It can be proven to be the case by commenting out
the `alice.commit_to(...)` line and watching the test still pass.

Fix the test by committing to the namespaced default branch which will
trigger the `CanonicalRefUpdated` that is expected by Bob. Drains events
so we don't catch the wrong event, and explicitly announces.
2026-05-05 14:41:52 +01:00
Adrian Duke ed340f5724 e2e: Add partial glob match cref test 2026-05-05 14:41:52 +01:00
Fintan Halpenny e87af868db node/test: `NodeHandle::commit_to` should namespace
The `commit_to` method should namespace the reference, so that node is
only ever committing the namespace of that particular node.

This prevents the node from committing to another node's namespace or
the canonical namespace.
2026-05-05 14:41:52 +01:00
Adrian Duke 8724ebe780 radicle/git/canonical: Introduce property tests for glob matching behaviour 2026-05-05 14:41:52 +01:00
Adrian Duke fb40bdc0ac radicle/git/canonical: Fix glob rewrite rule for trailing asterisk
Previously a rule like `refs/heads/releases-*` would be rewritten
incorrectly to `refs/heads/releases-**/*` matching the 'trailing
asterisk' rule.

Simplify the rewrite rule so that a trailing `*` becomes a prefix
matching rule. Add tests.
2026-05-05 14:08:33 +01:00
Fintan Halpenny 455138f4a7 radicle/canonical/rules: Reorganise test suite
Reorganises the rules test suite into a `test` module and a `helper`
sub-module.

The former holds the original tests, removing the `test_` prefix.
The latter holds helper functions for test setup.
2026-05-05 09:53:58 +01:00
Daniel Norman 998f527bd4 node/db: delete unparseable ipv6 rows
- Drop `type='ipv6'` rows whose bracket-stripped inner part contains
  no `:`, since every valid IPv6 textual form has at least one
- Cover the offender (`[]:8776`), bracketed garbage (`[abc]:8776`),
  the unspecified (`::`), loopback, and full forms in tests
- Keep `dns` and `ipv4` rows untouched

Migration 8 retyped any `[..]:N` dns row to ipv6 by bracket pattern
alone, without checking the inner part is parseable
2026-05-01 17:42:44 +02:00
Daniel Norman a45a1078ab node: Skip unreadable address rows in `entries()`
- Decode each row independently so one corrupt entry no longer aborts
  the whole address-book iterator
- Log a warn with the raw `value` and continue past parse failures
- Add a regression test that mimics the post-migration-8 state where a
  legacy `[]:8776` row lands as `type='ipv6'`

A pre-`df8e4e6c` parser admitted `[]:8776` as `HostName::Dns("[]")`.
Migration 8 retyped any `[..]:N` dns row to `ipv6` without validating
the inner part, so on read `Address::from_str` rejects the row with
"invalid IPv6 address syntax", which bubbled up and caused
`available_peers()` to silently return empty, breaking peer discovery
for the affected node.
2026-05-01 17:42:44 +02:00
Josh Soref 5dae2a8b58 treewide: Spelling
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2026-04-30 15:50:29 -04:00
Josh Soref a101705012 term: Rename `Row::Divid{i,}er`
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2026-04-30 15:49:57 -04:00
Josh Soref 08dd1dfda7 node/test: Fix Spelling
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2026-04-30 15:49:56 -04:00
Josh Soref 8391599f61 oid: Fix spelling
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2026-04-30 15:48:33 -04:00
Fintan Halpenny b482845e71 sigrefs/git/properties: Restore property tests
These tests fell under the radar when refactoring.
Restore them to the latest code so that these properties are tested.
2026-04-29 14:04:54 +02:00
Fintan Halpenny b4707e5502 cli: Amend jj tests
Update the output of `rad_jj_colocated_patch` so that it passes with the current version of `jj` being used:
```
jj --version
jj 0.35.0
```

Mark `rad_jj_bare` as `ignore` since it currently cannot determine the
RID from the `rad` remote.
2026-04-29 13:51:11 +02:00
Lorenz Leutgeb 378365aa42 term: Deny `print_stdout`
To prevent regressions, `#![deny(clippy::print_stdout)]` is added to
`radicle-term`, similar to `radicle-cli`.
2026-04-29 10:57:24 +02:00
Lorenz Leutgeb cc70d7dad4 radicle/cob: Do not return `BrokenPipe` from external COB invocation
A handle for standard input can always be expected here.
2026-04-29 10:57:24 +02:00
Lorenz Leutgeb 553a3ebd0a cli/cob: Remove check of `BrokenPipe`
The swallowing of `BrokenPipe` is processed further down the stack,
so this case is no longer needed.
2026-04-29 10:57:24 +02:00
Fintan Halpenny 56625a42f7 term: Rename print_inline to print
The name `print_inline` was used because, previously, the `print`
function name was taken.

This has now changed, allowing the rename of this function to happen.
2026-04-29 10:57:24 +02:00
Fintan Halpenny 2262b8d9bd term: Rename print to println
The name `print` was used because, previously, the `println`
function name was taken.

This has now changed, allowing the rename of this function to happen.
2026-04-29 10:57:24 +02:00
Fintan Halpenny e7d519cf65 term: Rename println to println_prefixed
This function was mistakenly called `println`, despite it also wanting
a prefix.

Rename the function so that it better reflects its behaviour.
2026-04-29 10:57:24 +02:00
Fintan Halpenny 1a31a9f541 cli: Handle broken pipe (SIGPIPE) gracefully
Prevent `rad` from panicking when its stdout is a closed pipe, e.g.
when running `rad config | head`.

The broken pipes are handled at the output boundaries using three
layers of defence:

1. Inner: Route all stdout output through `term::print()` and
   `term::print_inline()`, which use explicit `writeln!`/`write!` to
   a locked stdout and silently ignore write errors. All `println!`
   calls in `radicle-cli` and `radicle-term` are converted.
2. Middle: Catch `BrokenPipe` errors that propagate up via `anyhow`
   from subcommands in `main::run()`, and exit cleanly with code 0.
3. Outer: Install a panic hook that intercepts `println!` panics
   containing "Broken pipe" (from dependencies like clap) and exits
   cleanly, chained in front of human_panic's hook.

To prevent regressions, `#![deny(clippy::print_stdout)]` is added to
`radicle-cli`, requiring all future stdout output to go through the
safe `term::` functions.

Note: `eprintln!` calls (stderr) are not handled, as broken stderr
pipes are extremely rare in practice.
2026-04-29 10:57:24 +02:00
Fintan Halpenny e1f16bee26 term: Catch EPIPE and swallow
Rust (since 1.62) ignores EPIPE by default (see [Rust #62569]),
causing writes to closed pipes to return `io::ErrorKind::BrokenPipe`
errors.  The `println!` macro panics on these errors, producing a
confusing backtrace instead of the silent exit expected of Unix CLI
tools.

To avoid the use of `print!` and `println!`, the `radicle-term`
helper functions use `write!` and `writeln!`. This lays the groundwork
for the `rad` CLI to transition to using only `radicle-term`
functions.

There was a first attempt made, which is documented here.  A
process-wide SIGPIPE reset (`SIG_DFL`) was ruled out because `rad`
manages child processes via libgit2 pipes and communicates with the
radicle node over Unix sockets. Resetting SIGPIPE globally caused
`rad` itself to be killed during internal pipe operations (e.g. during
`rad remote add --fetch`), producing flaky test failures.

[Rust #62569] https://github.com/rust-lang/rust/issues/62569
2026-04-29 10:57:24 +02:00
Fintan Halpenny 8f4b90db7b cli/test: Add broken pipe (SIGPIPE) tests
Add tests that verify the `rad` binary does not panic when its stdout
is a broken pipe (e.g., `rad config | head -1`).

Rust (since 1.62) ignores SIGPIPE by default (see [Rust #62569]),
causing `println!` to panic with "failed printing to stdout: Broken
pipe" instead of exiting silently. This is the standard Unix behaviour
expected of CLI tools.

These tests currently fail on `rad config` and `rad self`, and are set to `ignore`, to be enabled when fixed.

[Rust #62569]: https://github.com/rust-lang/rust/issues/62569
2026-04-29 10:57:24 +02:00
Lorenz Leutgeb 7b07e57b9c treewide/test: Use `impl Arbitrary for Oid`
Many usages of `Oid::from_sha1` are actually in the context of
generating arbitrary `Oid`s. In these cases, `impl Arbitrary for Oid`
may be used directly.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb 88a7252e33 oid: Fix `impl Arbitrary for Oid`
`fill` would cause the OID to consist of 20 repetitions of the same
byte every time, not an arbitrary OID.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb deabe6d2ce oid: Nicer panic message
The panic message that `unimplemented!` generates is more appropriate.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb 60f42bff53 protocol: Minimize scope of `radicle::git::raw`
A tiny refactoring to make it less inviting to use
`radicle::git::raw`.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb 8dba3ca9de protocol: Decode `radicle_oid::Oid` without `git2`
`impl Decode for Oid` uses `git2` via `radicle::git::raw`.
This is unnecessarily complex.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb 3d1b37fcbb oid: Make `Oid::SHA1_LEN` public
The length of a SHA-1 object identifier is defined in various locations.
Instead of this redundancy, make the definition in `radicle-oid` public, and
use it in `radicle{-{core,protocol},}`.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb f46624b81c oid: SHA1_DIGEST_LEN → Oid::LEN_SHA1
A small refactor to move this constant closer to the `enum` that it is
about. An associated constant constant fits more nicely.

Reason for changing the name from `SHA1_*_LEN` to `LEN_SHA1`: As we
think about the object format in the future becoming variable, we
would have that the length of an OID is parameterized by the object
format. So, we imagine a function `fn len(format: ObjectFormat)`, to
be called as `Oid::len(ObjectFormat::Sha1)`. This is the new order
we use.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb 4c605c0671 radicle: Avoid comparison with `ZERO_SHA1`
When the underlying hash function is not important, but only the fact
whether the OID is the zero sentinel, use `Oid::is_zero`. This is more
flexible, as it is forward compatible with the SHA-256 object format.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb ecca50a5f9 treewide: Avoid `git2::Oid::zero`
In multiple instances, `git2::Oid::zero` is used to obtain an OID that
is equivalent to `radicle_oid::Oid::SHA1_ZERO`. This is needlessly
complex.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb f65175397f oid: `const ZERO_SHA1` instead of `fn sha1_zero`
`fn sha1_zero` can be `const fn`, and since it does not take arguments,
it can just be a `const`. There is no good reason to not have a constant.

Justification for changing the order to first specify "zero" and then
"SHA1": In the future we will support multiple object formats. A
function that produces a zero OID would be parameterized by the object
format then. Imagine `fn zero(format: ObjectFormat)` which would be
called as `Oid::zero(ObjectForma::Sha1)`. In anticipation, we change the
ordering to match.
2026-04-28 14:35:38 +02:00
Lorenz Leutgeb aa177b0400
treewide: radicle.{xyz → dev,network}
We have moved to radicle.dev, so adjust the domain name in various
places.

Not changed:
 - COB type names
 - systemd secret identifiers
2026-04-27 18:34:30 +02:00
Lorenz Leutgeb cb57e6560f
node: Rename systemd Credentials
Because we changed our main domain from radicle.xyz to radicle.dev, we
should also change our systemd credential identifiers.

Handling is move into a new function, which also checks the old names
for backwards compatibility.
2026-04-27 18:21:26 +02:00
Lorenz Leutgeb 06ff36ebc4
cli: Update warning for new bootstrap node names 2026-04-27 18:20:25 +02:00
Lorenz Leutgeb 22287fd94a
radicle: Change names of bootstrap nodes 2026-04-27 18:19:47 +02:00
Lorenz Leutgeb 3bc8abdc29
radicle/node/config: Use `IndexSet`
With `connect: std::collections::HashSet`, we suffer unpredictable reordering
of values in our test cases.

Use `connect: indexmap::IndexSet` instead, which preserves insertion
order for iteration.
2026-04-27 17:36:20 +02:00
Fintan Halpenny 099722dd08 radicle: Propagate `radicle-oid/qcheck`
The `radicle-oid/qcheck` feature was not added for `radicle`'s
`qcheck` feature.
Add this so that it propagates for use in downstream crates.
2026-04-24 08:29:34 +01:00
Adrian Duke bdd991a5f1 e2e: Fix race condition in test_non_fastforward_identity_doc
Fixes an intermittent failure caused by a race condition with background
gossip messages.

Previously Eve's `RefsAnnounced` message could be processed by Bob
after the test updated the identity document to include Alice-Laptop
as a delegate. This caused Bob to unexpectedly fetch the issue in the
background, failing the `!has_issue` assertion.

We now explicitly wait for Bob to receive Eve's announcement before
updating the identity document.
2026-04-23 08:45:06 +01:00
Adrian Duke 802e4726e3 e2e: Replace the hardcoded 1-second sleep in `test_connection_crossing` with polling loop
Avoid relying on a thread sleep to wait for the nodes to connect.

Instead rely on the connection condition being established by using a polling loop.
The loop is controlled by a number of iterations, panicking if the
number of iterations is exceeded.
2026-04-23 08:42:32 +01:00
Adrian Duke bfb54bf4be e2e: Fix flakiness in `test_connection_crossing` test
Addresses intermittent failures in the `test_connection_crossing` e2e test,
which were particularly prevalent on slower CI environments (such as
`rust:trixie`).

Previously, the test spawned two threads to make Alice and Bob dial each
other concurrently and strictly asserted that the "preferred" peer (the
one with the higher Node ID) would always win the `Outbound` link direction.
However, this assumption is flawed in real-world, OS-level network execution
due to two race conditions:

1. Thread Scheduling: One thread could execute and fully establish a
   connection before the other thread even began processing its dial command.
2. Reactor Event Ordering: A node's reactor might wake up and process an
   incoming TCP connection from its peer *before* it processes the `Connect`
   command sent by the test. When it finally processes the `Connect` command,
   it sees a session already exists and skips dialing entirely.

In both scenarios, a true "simultaneous crossing" never occurs. Instead, a
standard sequential connection happens, meaning the link direction is dictated
by whoever dialed first, not by the "preferred" peer logic. This caused the
strict `left: Outbound, right: Inbound` assertions to panic.

To fix this, two changes were made:
- Introduced a `std::sync::Barrier` to synchronize the two test threads.
  This forces both threads to wait for each other before calling `.connect()`,
  maximizing the probability of a true simultaneous dial.
- Relaxed the final assertions. Because OS-level TCP handshakes and reactor
  polling can never guarantee perfect simultaneity, we no longer assert
  *which* peer gets the `Outbound` link. Instead, we assert the core invariant:
  that exactly one connection is established between the nodes, and that their
  link directions are opposite (`s1.link != s2.link`).
2026-04-23 08:39:03 +01:00
Lorenz Leutgeb bbb1279604 cli/config: Obsolete `{get,set,push,remove,unset}` 2026-04-22 15:45:57 +01:00
Lorenz Leutgeb 224c03daf1 node: Expand `target_os` condition for keep-alive
`KeepAlive::with_retries` is not just unavailable on Windows, but on a
bunch of other operating systems. Copy the condition over from
`socket2`.
2026-04-22 12:06:12 +01:00
Lorenz Leutgeb 4a81673d0e node: Fix conditions for socket activation
The implementation of socket activation requires not only the `systemd`
feature, but also `socket2`.
2026-04-22 12:03:05 +01:00
Richard Levitte 354805aeb3 cli/test: Fix failures related to remote HEAD
Two tests assumed that `refs/remotes/rad/HEAD` would automatically be
crated.  It turns out, however, that this only the case if executed
with Git 2.48.0 or newer. Git versions older than 2.48.0 do not touch
'refs/remotes/<remote>/HEAD'.

Git 2.48.0 and newer will update `refs/remotes/<remote>/HEAD` by
default. This can be disabled by setting
`remote.<remote>.followRemoteHEAD` to "never". Thus, with these
versions, setting that configuration to "never" emulates the behaviour
of older versions.

Therefore, to ensure that test results are consistent across Git
versions before and after 2.48.0, the fixture of test repositories
now include setting 'remote.rad.followRemoteHEAD = never'.

The affected tests are adjusted accordingly.

This change can possibly be reverted once Radicle requires usage of Git
2.48.0 or newer.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2026-04-21 23:15:34 +02:00
Lorenz Leutgeb 2d20e249fd cli/init: Deduplicate `ScopeParser` 2026-04-20 09:21:00 +01:00
Lorenz Leutgeb 4f647b2a10 Spell "Radicle" with a captial R
In documentation and output, spell the name of the project with a
captial "R".
2026-04-17 17:15:31 +01:00