Commit Graph

267 Commits

Author SHA1 Message Date
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
Josh Soref 5dae2a8b58 treewide: Spelling
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2026-04-30 15:50:29 -04: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 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 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 06ff36ebc4
cli: Update warning for new bootstrap node names 2026-04-27 18:20:25 +02:00
Lorenz Leutgeb bbb1279604 cli/config: Obsolete `{get,set,push,remove,unset}` 2026-04-22 15:45:57 +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
Lorenz Leutgeb bb97414a2d cli: Deduplicate RID Argument Handling
Multiple commands take an RID as a command line argument. However, the
handling, which usually involves falling back to detection of the
repository associated with the current directory, is duplicated many
times over.

Introduce `fn rid_or_cwd` which can be used by many commands.
2026-04-17 17:09:15 +01:00
Lorenz Leutgeb f2e96c96ce cli: Rename all `RepoId` args to `repo`
Many commands take RIDs as arguments. Most of them call the
corresponding struct member `repo`, some call it `rid`.

For consistency, name these members `repo` for all commands.

This change is intended to be invisible to the user. That is why,
in some cases, the "long" version of the argument must be renamed to
match `--rid`.
2026-04-17 17:09:15 +01:00
Adrian Duke 9c6bcd2778 cli: Fix typo "writeable" → "writable"
Detected by `typos`.
2026-04-16 21:01:11 +02:00
Lorenz Leutgeb ac3eba09a1 I2P Support
Co-authored-by: ps
2026-04-15 12:01:50 +01:00
Lorenz Leutgeb dbec748c47
clippy: Disallow unused lints
The following clippy lints were allowed in the past but are not
required anymore:
 - box_default
 - large_enum_variant
 - let_unit_value
 - or_fun_call
2026-04-15 10:06:05 +02:00
Lorenz Leutgeb ca732015fb
clippy: Disallow lints `collapsible_*`
The three lints `collapsible_{if,else_if,match}` are disallowed.
2026-04-15 10:06:05 +02:00
Lorenz Leutgeb 44244dc00f
clippy: Disallow lint `type_complexity`
We just need two exceptions.
2026-04-15 10:06:05 +02:00
Lorenz Leutgeb ffc81154e4
clippy: Disallow lint `enum_variant_names`
We just need one exception.
2026-04-15 10:06:05 +02:00
Lorenz Leutgeb 94f65a71db
cargo: Remove unused dependencies
A number of dependencies were unused, causing confusion. Remove them.
2026-04-15 01:50:14 +02:00
Lorenz Leutgeb 80c1bba76f
radicle: Prevent `RAD_SOCKET` from polluting tests
The value of `RAD_SOCKET` influences the outcome of `cargo test`.

Users that set this environment variable to, e.g. point `rad` at
a socket at a custom location, were surprised that execution of
tests would interact with their node.

Split `Home::socket` into two functions. `Home::socket_default` is
"pure" and only dependent on the path represented by `Home`. On the
other hand, `Home::socket_from_env` interprets the environment.

Most users will want to use `Home::socket_from_env`.
2026-04-09 17:53:49 +02:00
Lorenz Leutgeb 22b2871f64 radicle: Add Version to User Agent
The main motivation behind this change is to get just a little
telemetry information from nodes on the network, namely the version
of Radicle they are running.

This is achieved by rewriting `impl Default for UserAgent` which now
uses the version information provided by the build script also used
in other crates.

Also, a new configuration option `node.userAgent` is added, which
allows users to override the user agent if they so please, or set
the value `null`, which will in turn send the user agent
`/radicle/`, which is not really helpful, and the default prior to
this commit.

Creations of `UserAgent` in the whole workspace is cleaned up. In
order to do that `UserAgent::test` is introduced.
2026-04-02 16:26:46 +01:00
Wiktor Kwapisiewicz 48551cde93 crypto: Use `ssh-agent-lib` for SSH Agent
This makes the `radicle-ssh` crate obsolete.

Signed-off-by: Wiktor Kwapisiewicz <wiktor@metacode.biz>
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2026-04-02 16:03:23 +01:00
Lorenz Leutgeb 96f573b767 cargo: Update dependencies with `cargo update` 2026-04-02 14:46:41 +01:00
Richard Levitte 91590a3287 cli/patch: Show Commit Ranges of Revisions
Each revision now shows a `{base}..{head}` range instead of just the
head.  This is meant to clarify when a patch is rebased, and to be
helpful when one might want to observe changes using 'git range-diff'.
2026-04-02 14:08:44 +01:00
Richard Levitte b8f8cfb319 cli/terminal: Formatting Functions for Ranges
These allow convenient formatting of long/full object identifiers,
as well as formatting ranges.
2026-04-02 14:08:44 +01:00
Lorenz Leutgeb f223afd9d7 radicle: Refactor COB Storage Access
Instead of passing the signer as an argument to many methods on `Store`,
scope the `Store` itself to a signer.

This further allows to differentiate two different access modes on the
store in `radicle::cob::store::access`: `WriteAs` (which requires signer)
and `ReadOnly` (which does not require a signer).

The caches for issues and patches in `radicle::cob::{issue,patch}::Cache`
are concretised by removing the first type parameter, since it was
specific to issues and patches anyway. This was done in this commit as
it touches very similar usage sites.

Make `Device` less prominent, and instead lean more heavily towards
traits from the `signature` crate, such as `Keypair` and `Verifier`
in addition to `Signer`. Trait bounds regarding `Signer` could be
simplified, but this is left for the future.

In `radicle-cli`, the function `term::cob::patches_mut`, which generates
errors with a hint is used instead of the lower-level `Profile::patches_mut`.

Commands `rad issue cache` and `rad patch cache` now construct a
writeable cache on top of a read-only store.

Many knock-on changes are handled as well, to arrive at a clean state.
2026-03-31 17:57:14 +01:00
Lorenz Leutgeb 10a82958ca
node: Explicit default for `AddressConfig`
The intent to drop outgoing connections is modeled as `Option::None`
which is brittle and easy to miss.

Extend `enum AddressConfig` with a variant that is more explicit.
2026-03-31 16:24:22 +02:00
Lorenz Leutgeb 1e132685bc
cargo: Add feature for Tor support 2026-03-31 16:24:22 +02:00
Lorenz Leutgeb 8bac24d6ab
rust/edition/fmt: 2021 → 2024
Reformat code with `rustfmt`, now that the edition changed from
Rust 2021 to Rust 2024.
2026-03-30 16:57:51 +02:00
Lorenz Leutgeb 0494227e75
rust/edition: 2021 → 2024
Switch from Rust 2021 to Rust 2024.

See <https://doc.rust-lang.org/edition-guide/rust-2024/>.

Regarding the added bound `use<>`, see
<https://blog.rust-lang.org/2024/09/05/impl-trait-capture-rules/>.
2026-03-30 16:56:58 +02:00
Fintan Halpenny d685d6f91d cli: Update to 0.20.0 2026-03-26 15:32:43 +00:00
Lorenz Leutgeb 6d771abfcc radicle/storage/refs: Remove `RefAt::load`
This is a very thin wrapper around `SignedRefs::load_at` which only
has a single usage in all of the workspace. Remove it.
2026-03-25 08:19:36 +00:00
Lorenz Leutgeb ec36d1c3ae radicle/sigrefs: Merge `SignedRefs{,At}`
Since now all callers expect `SignedRefsAt`, merge the two structs
and name the result just `SignedRefs`.
2026-03-25 08:07:49 +00:00
Lorenz Leutgeb 5d583338c8 radicle/sigrefs/git: Use stable `RAD_LOCAL_TIME`
For stable commits, use the same timestamp as the CLI tests do.
This preserves that commits that are created in the Signed References
module appears as "now" in the output.
2026-03-25 08:07:49 +00:00
Lorenz Leutgeb 86cacfb846 radicle: Have migration repair downgrades
Instead of repairing downgrades, automatic migration could fail.
This occurs when attempting to load signed references, and a downgrade
is detected while coalescing `RepositoryInfo`.

Similarly to how fetch recovers, automatic migration must also recover
by detecting for the downgrade error, and allowing the migration to
happen in this case.
2026-03-25 08:01:29 +00:00
Fintan Halpenny 33db6637b4 radicle: Teach `rad sync` and `rad clone` to accept feature levels
Teach the `rad sync` and `rad clone` commands to accept the
`--signed-refs-feature-level` option. This option configures that
fetch to use the minimum feature level provided when fetching from
other nodes. This overrides the value of
`node.fetch.signedReferences.featureLevel.minimum`, and should only be
used in scenarios where it is necessary to downgrade the security of a
fetch for backwards compatibility.


To achieve this, the `Handle::fetch` method introduces an optional
parameter for threading through the `FeatureLevel`.
2026-03-24 15:07:18 +00:00
Lorenz Leutgeb e245e3115b fetch: Configure Minimum Feature Level
Add a configuration knob `node.fetch.signedReferences.featureLevel.minimum` to
allow node operators to enforce more strict verification when fetching
Signed References.
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb 47063057a8 radicle/sigrefs: Automatically Migrate
In order to allow automatic migration to the feature level 'parent',
introduce a way to force writing signed references, even if the
user-controlled refs are unchanged.

To detect the special case of a Signed References history with a single
and root commit (which would otherwise be detected as feature level
'parent', even though the `refs/rad/sigrefs-parent` ref cannot be
written), keep information about the parent commit in `SignedRefs`.

The output of `rad inspect --sigrefs` is changed to match the more
strict interpretation of the feature level for histories with a single
and root commit.
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb 7fb032da9b cli/inspect: Show feature level of sigrefs
In order for users to be able to inspect the feature level of Signed
References in a repository, have `rad inspect --sigrefs` attempt to
load Signed References, and, if successful, print the feature level.
2026-03-24 14:40:42 +00:00
Lorenz Leutgeb 372a78754a radicle/sigrefs: Implement feature detection
To be able to reason about backwards compatibility, downgrade attacks,
and future configurable enforcement of minimum required (security)
features, introduce the concept of a "feature level" in Signed
References.

In this implementation feature levels are assumed to be monotonic, i.e.,
that higher feature levels behave like lower ones, and only add
semantics on top.

Implementing this as an `enum` that is `Ord` flows nicely.

Treatment of the write-side is left for the next commit. Currently,
the write side "promises" `FeatureLevel::Parent`, but is not able
to "keep the promise", since it does not actually inspect the
feature level of the head in case it is unchanged.

CHANGELOG
2026-03-24 14:40:42 +00:00
Fintan Halpenny d7e7db35ed cli: Update to 0.19.0 2026-03-18 20:53:00 +00:00
Lorenz Leutgeb 6967bf8fca
radicle: Automatically upgrade sigrefs
When enumerating repositories in storage, gracefully handle the
case where the identity root is missing from sigrefs and automatically
migrate.
2026-03-17 21:31:42 +01:00