Commit Graph

458 Commits

Author SHA1 Message Date
Fintan Halpenny 6cfed884bf cli/remote: migrate to clap 2025-10-21 10:12:33 +01:00
Sebastian Martinez f8fe296ceb radicle/rad: Fix typo 2025-10-20 15:09:43 +01:00
Sebastian Martinez 8604d3bcc7 cli/config: Use clap 2025-10-20 12:04:31 +01:00
Erik Kundt 8d90699c30 cli/cob: Use clap 2025-10-20 11:21:58 +01:00
Fintan Halpenny 38ca038a0d cli: fix test regression in 27a85987c3
A test failure was caused in commit
27a85987c3, which expected `STRING`
instead of `ALIAS`.

This change fixes that discrepancy.
2025-10-17 16:57:29 +01:00
Sebastian Martinez c1d9f04963 cli/self: Use clap 2025-10-17 16:45:20 +01:00
Erik Kundt 27a85987c3 cli: Fix argument value names in `auth` and `init`
We decided to use the value names to indicate which type the value
is parsed into (instead of just saying that the value is a `STRING`).

This makes the value names consistent with other commands again.
2025-10-17 13:43:42 +01:00
Lorenz Leutgeb b0beef4391
workspace/rust/clippy: Fix all warnings
Fix clippy warnings following the upgrade of the Rust toolchain to
version 1.90. Almost all are "mismatched_lifetime_syntaxes".
2025-10-17 12:56:43 +02:00
Erik Kundt 407abc6a2c cli/follow: Use clap 2025-10-17 10:50:21 +01:00
Erik Kundt deaf77e5b4 cli/unfollow: Use common DID / NID argument parser 2025-10-17 10:50:21 +01:00
Erik Kundt e488ddb446 cli: Add common value parser for NID / DID 2025-10-17 10:50:21 +01:00
Richard Levitte 06e22434e1 cli/checkout: use Clap 2025-10-17 08:51:10 +02:00
Erik Kundt 28824a3127 cli/init: Fix argument value names 2025-10-17 08:20:30 +02:00
Erik Kundt 634866889f cli/id: Use clap
This implementation works around the fact that `clap` does currently
not support value parsers for a series of values, so representing
`--payload` as a `Vec<Payload>` does not work.

Instead, we parse eveything into a `Vec<String>` and do the validation
on the application side.

Using value parsers for a series of values will probably be supported in
`clap` v5, though.
2025-10-16 18:14:11 +02:00
Lorenz Leutgeb d3ed4bb497 radicle/id: Introduce `PayloadUpsert`
Instead of using triples, introduce a `struct` to carry upserts.
2025-10-16 18:14:11 +02:00
Erik Kundt ec1d754308 cli/unblock: Use clap 2025-10-16 15:31:47 +01:00
Erik Kundt faf19af9de cli: Share common argument types used in `(un)block`
Since `rad block` and `rad unblock` share the same argument type,
`BlockTarget` is moved to the exising `terminal/args.rs` module.
2025-10-16 15:31:47 +01:00
Fintan Halpenny cef0ff571d oid: use `std:#️⃣:Hasher::hash` over `Hasher::write`
Previously, the `RepoId` was backed by the `git2::Oid` type, which has a `Hash`
implementation that uses `std:#️⃣:Hasher::hash`. This uses a length prefixed
form for hashing.

When switching to `radicle_oid::Oid` and implementing `Hash` without the length
prefix, this subtly changed the hashes going forward.

These hashes are used in `BloomFilter`, which is the type underlying `Filter`.
This resulted in cases where a previous `Filter` that certainly contained
`rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5` were suddenly reporting that they did not
contain this repository.

Changing the implementation of `Hash` for `radicle_oid::Oid` fixes this, and is
confirmed by adding the regression test in `filter.rs`.
2025-10-16 15:14:10 +01:00
Erik Kundt d9ae29deaa cli/seed: Use clap 2025-10-16 13:02:06 +02:00
Erik Kundt 80198d68b6 cli: Share value parser for `Scope` 2025-10-16 13:02:06 +02:00
Fintan Halpenny b7a7f55e78 gix: CVE-2025-31130
Update the `gix` family of crates to avoid the vulnerability reported in
[CVE-2025-31130].

Since `gix-hash` is used in two places, its version definition was moved to the
top-level `Cargo.toml`. `cargo` warned that `default-features` not being defined
in the top-level could result in a future error, so that was carried along with
it. This did not affect the build of `radicle-fetch`.

[CVE-2025-31130]: https://www.cve.org/CVERecord?id=CVE-2025-31130
2025-10-15 11:34:01 +01:00
Lorenz Leutgeb 0b3424857e node/reactor: Correctly handle error events
`fn handle_events` would panic, if there were multiple events for one
token, and the first one that happened to be handled was an error.
Indeed it is concerning if a token is encountered that was never
registered before. However, tokens that were just deregistered must be
tracked.

Using `Vec` here seems a bit costly, in the future,
`smallvec::SmallVec` could be considered.

The "unregister" methods are renamed to "deregister" to better line up
with `mio` vocabulary.

Log statements that helped analysis of the panic that occurred here
are overhauled and improved, requiring a `Debug` bound on types that
obviously implement it.
2025-10-15 10:42:27 +01:00
Lorenz Leutgeb 72cf3d1919 cli/diff: Use clap
The implementation of the *deprecated* command is removed, it now
directly invokes `git diff`.
2025-10-15 10:30:34 +02:00
Richard Levitte 384c506489 cli/auth: use Clap 2025-10-14 12:55:33 +01:00
Erik Kundt dfd35480be cli/watch: Use clap 2025-10-14 12:49:16 +02:00
Lorenz Leutgeb efe10f95be
workspace: Enable vendored `libgit2`
Multiple crates are enabling this feature. Do that at the workspace
level.
2025-10-13 16:14:23 +02:00
Lorenz Leutgeb b7cfcfff75
workspace: A Little Less `git2`
Make `git2` an *optional* dependency of `radicle-cob`, and refactor
`radicle` to depend on crates that in turn do not depend on `git2`
*non-optionally*

The main offending dependency of `radicle-cob` is `radicle-git-ext`
from the `radicle-git` workspace in repository
(rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) which *non-optionally* depends on
`git2`.

So, to achieve removal of this dependency:

 1. The crate is refactored to depend on the new crates
    `radicle-git-ref-format` `radicle-git-metadata`, and
    `radicle-oid` introduced in the previous commits, instead of
    `radicle-git-ext`.
 2. Some code from the `radicle-git-ext` crate in the `radicle-git`
    workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is
    copied. See `crates/radicle-cob/src/backend/git/commit.rs`.

This cascades to `radicle` and its dependents.

  Firstly, the there is an
`impl Deref<Target=git2::Oid> for radicle_git_ext::Oid`. This made
it very convenient to just deref to obtain the wrapped `git2::Oid`,
so there are many expressions of the shape `*oid` in `radicle` and
its dependents. However `radicle-oid` does not provide
`impl Deref<Target=git2::Oid> for radicle_oid::Oid`, as notably,
`Target` is an associated type, and not a type parameter, so an
implementation of `Deref` would tie the new `Oid` too tightly to a
particular implementation.
Instead, work with `impl From<radicle_oid::Oid> for git2::Oid`
(which can be enabled using the feature flag `radicle-oid/git2`).
This explains the changes from `*oid` to `oid.into()` at every
transition to "`git2` land".

  Secondly, `radicle` and its dependents are refactored to also depend
on `radicle-git-ref-format` and (much less prominently)
`radicle-git-metadata` instead of `radicle-git-ext`
This is to avoid pulling in `git2` via these dependencies.

  Thirdly, as the re-exports in `crates/radicle/src/git.rs` change,
they are at the same time also cleaned up. Notably, the types from
`radicle-git-ref-format` are re-exported under `fmt` only, not "twice".

While overall this obviously is very much a breaking change, these
changes should mostly amount to changing from `Deref` to `Into`, i.e.,
`*oid` → `oid.into()` and rewriting imports for `radicle::git::fmt`.
This is indicated by the mostly mechanical nature of the changes to
`crates/radicle-{cli,node,remote-helper}`.
2025-10-13 16:14:23 +02:00
Lorenz Leutgeb f4a8908727
git-metadata: New crate extract from `radicle-git`
This is just enough to get heartwood going.

Some code from the `radicle-git-ext` crate in the `radicle-git`
workspace in repository (rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt) is
copied.
2025-10-13 16:07:39 +02:00
Lorenz Leutgeb 793f53b5f0
oid: New crate
For further details on the crate see its documentation in
`crates/radicle-oid/src/lib.rs`.

Changes to `crates/radicle{,-crypto}/Cargo.toml` are to preserve the
default features of their dependencies `serde` and `schemars`,
which are disabled to support `radicle-oid` being `no_std`.
2025-10-13 15:00:12 +02:00
Lorenz Leutgeb 3fcf2a86da
git-ref-format: New crate
For further details on the crate see its documentation in
`crates/radicle-git-ref-format/src/lib.rs`.
2025-10-13 13:34:15 +02:00
Lorenz Leutgeb 292befdb36 radicle: Add `tempfile` to dev-dependencies
In commit 2149770a4b the dependency
`tempfile` was made optional, and added to be enabled by the feature
flag `test`. However, the *tests of this crate itself* need the
dependency *unconditionally* as they want to create temporary
directories.

This was not noticed earlier, since the error does not surface when
the whole workspace is tested (`cargo test --workspace`), but only
when the crate itself is tested (`cargo test --package=radicle`),
because when testing the workspace, dependents of `radicle` will
enable the `test` feature flag.
2025-10-13 12:26:31 +01:00
Lorenz Leutgeb 84dd89180a radicle: Fix required features for `fastrand`
The `radicle` crate would not build on its own with
`cargo build --package=radicle` because some `fastrand` features were
missing.
2025-10-13 10:39:25 +01:00
Lorenz Leutgeb f232acda74 cli/diff: Print cross-platform line endings 2025-10-13 10:32:17 +01:00
Lorenz Leutgeb d8ab40777d radicle/storage: Fix temporary path generation
`impl Display for RepoId` will produce a `rid:` prefix, which
contains a colon, and this trips up various filesystems. Use
`RepoId::canonicial` instead.
2025-10-13 10:29:09 +01:00
Lorenz Leutgeb 013da67aa1 cli/test: Skip `rad_diff` on macOS
macOS's `sed` requires an argument for `-i`, which we don't provide
in the example. Providing it makes the test fail on Linux.
Since this command is deprecated anyway, we just skip macOS.
2025-10-13 10:27:02 +01:00
Lorenz Leutgeb f7af918190 cli/test: Fix `rad_patch_delete` for macOS
It looks like macOS does not like this command:

    ln: -f: No such file or directory

Since `LICENSE` and `MIT` are both just empty here, we don't need to
link anything.
2025-10-13 10:18:10 +01:00
Lorenz Leutgeb 7d6d2e51d8 cli/test: Clean up testing environment
This is a small cleanup to path generation during testing.
 1. Separate `rad_home` and `unix_home` to avoid confusion.
 2. Remove now unnecessary `trait HasHome`.
 3. Move setting `$USER` to the environment.
 4. Make paths shorter overall, to fix macOS tests.
2025-10-13 08:56:23 +01:00
Lorenz Leutgeb 3780f908a2 radicle/identity: Stabilize tests
The following tests generated an unstable object ID, thus linearizing
history in a different order, causing failures. Fix this by using the
`stable-commits` feature of `radicle-cob`.

    thread 'cob::identity::test::test_identity_redact_revision'
    panicked at crates\radicle\src\cob\identity.rs:1338:9:
    assertion `left == right` failed
      left: [Oid(b4307ded046befba374bf8cd9fd787592ceb615c),
             Oid(a04165e3d3717c5a1413ec78e852bd8e1bb049d4),
             Oid(820d3faf5b507888173b26ccd1a2a81666bd2573),
             Oid(30ca6f078a401bf542049594fbb1c8d2371c9819),
             Oid(9e9c46971014b123a31cd1195078f95c2e319419)]
     right: [Oid(b4307ded046befba374bf8cd9fd787592ceb615c),
             Oid(a04165e3d3717c5a1413ec78e852bd8e1bb049d4),
             Oid(820d3faf5b507888173b26ccd1a2a81666bd2573),
             Oid(9e9c46971014b123a31cd1195078f95c2e319419),
             Oid(30ca6f078a401bf542049594fbb1c8d2371c9819)]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    thread 'cob::identity::test::test_identity_reject_concurrent'
    panicked at crates\radicle\src\cob\identity.rs:1494:9:
    assertion `left == right` failed
      left: [Oid(b4307ded046befba374bf8cd9fd787592ceb615c),
             Oid(bce4233f6a09d6e9c021ba436e3c6923a1a78f31),
             Oid(7cacb23be7079c16c8ad1b2458ae5bac95a1295b),
             Oid(12b00e2e871b699c84c20427a64e029934c15ec4),
             Oid(07431c8d80c11492ef248e07126370e874de3435),
             Oid(98b42450159a359c2f482652c66d79b05d0af624)]
     right: [Oid(b4307ded046befba374bf8cd9fd787592ceb615c),
             Oid(bce4233f6a09d6e9c021ba436e3c6923a1a78f31),
             Oid(7cacb23be7079c16c8ad1b2458ae5bac95a1295b),
             Oid(98b42450159a359c2f482652c66d79b05d0af624),
             Oid(12b00e2e871b699c84c20427a64e029934c15ec4),
             Oid(07431c8d80c11492ef248e07126370e874de3435)]
2025-10-13 08:49:24 +01:00
Lorenz Leutgeb 60798cdbc3 radicle/profile: Canonicalize during test
Fixes test `profile::test::canonicalize_home`.

Since `Home::new` canonicalizes its argument (which is what the test
wants to ensure), also canonicalize the expected value, which is
especially important on Windows as the canonicalization is more
complex than on Linux.

For example:

    thread 'profile::test::canonicalize_home' panicked at crates\radicle\src\profile.rs:802:9:
    assertion `left == right` failed
      left: "C:\\Users\\runneradmin\\AppData\\Local\\Temp\\.tmpMnNkr6\\Home\\Radicle"
     right: "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\.tmpMnNkr6\\Home\\Radicle"
2025-10-13 08:43:49 +01:00
Lorenz Leutgeb 86b7ef23c6 radicle/git/raw: Limit scope of `RemoteCallbacks`
Found with `cargo clippy --package=radicle`, this is only used in
tests.
2025-10-13 08:34:03 +01:00
Richard Levitte 191c287955 cli/ls: use Clap
This adds `conflicts_with` for `private` and `public` to simplify the logic that checks for the document visibility
(thank you Fintan)

Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2025-10-10 17:08:08 +02:00
Richard Levitte 8ba3087cb9 cli/init: use Clap 2025-10-10 16:03:14 +02:00
Lorenz Leutgeb ea562215ea
radicle/git/raw: Introduce `trait ErrorExt`
This extension trait is more ergonomic than what `radicle-git-ext`
provides.
2025-10-10 11:29:24 +02:00
Lorenz Leutgeb 880634acd4
radicle/git/raw: Capture all `git2` re-exports
A refactoring internal to the `radicle` crate, with the goal of
making dependency on `git2` clearer and more controlled.
`radicle::git::raw` is changed from a complete re-export of `git2`
to a module that selectively re-exports (many) members of `git2`.
required to build the workspace (potentially breaking dependents
outside the workspace, but given just how many types are
re-exported, this seems unlikely).

In the future, no more `use git2::…` statements should be added
outside of `crates/radicle/src/git/raw.rs`. This is in an effort to
decrease dependence on `git2` in the future.
2025-10-10 11:29:24 +02:00
Lorenz Leutgeb 633059040c node/wire: Refactor precedence
No change in the logic here, just making this slightly easer to read and
understand.
2025-10-10 09:45:05 +01:00
Lorenz Leutgeb cb7bed5562 node: Remove dependency on amplify 2025-10-10 09:45:05 +01:00
Lorenz Leutgeb 3c3d81a085 node: Use Mio
The dependencies `netservices`, `io-reactor` and `popol` served us well,
however they do not support Windows and are not actively maintained.

This change removes the aforementioned dependencies (and `libc` along
with them). It reuses the integration with `cyphernet` from
`netservices` for Noise and SOCKS.

The new module `reactor` is a rewrite of `io-reactor` on top of Mio.

Note that no tests were changed.
2025-10-10 09:45:05 +01:00
Lorenz Leutgeb b25d80d272
crypto: Depend on `git-ref-format-core` only
The `git-ref-format-core` crate is enough to implement everything
required by `radicle-crypto`, and has a much smaller dependency
footprint than `radicle-git-ext`. Notably, it does not depend on
`git2` and also does not depend on procedural macros.
2025-10-09 17:18:59 +02:00
Lorenz Leutgeb ac572e64e5 node: Support systemd credential for passphrase
While it is possible to pass the passphrase via the environment, e.g.
`EnvironmentFile=<path to file that contains "RAD_PASSPHRASE=…">`
this is less secure than passing it via a file, because the environment
is inherited down the process tree.

Thus, allow using a systemd credential. The ID of the credential must be

    xyz.radicle.node.passphrase

and is not user-configurable.

Passing the passphrase via file is now possible with
`LoadCredential=xyz.radicle.node.passphrase:<path to file that contains passphrase>`

This requires just a bit of plumbing in `radicle-node`.

Because this mechanism is more secure than using the environment
variable `RAD_PASSPHRASE`, it takes priority. That is, if both the
systemd credential is available, *and* the environment variable
`RAD_PASSPHRASE` is set, the former is preferred.

Heads-up:
 1. The contents of the file must be valid UTF-8 (see documentation of
    `std::fs::read_to_string`). Assuming that the passphrase is at some
    point chosen by the user and typed on a keyboard, this does not
    seem like a severe restriction.
 2. The contents of the file are not processed otherwise, i.e. line
    breaks (notably at the end of the file) are not stripped.

The related `issue/8bd040e9de05e7fc27e373ebc1649ff4ad930e7a` asked for a
very similar feature: Passing the passphrase via a file named by the
value of the of the environment variable `RAD_PASSPHRASE_FILE`.
It was also briefly discussed at
<https://radicle.zulipchat.com/#narrow/channel/369277-heartwood/topic/.60RAD_PASSPHRASE_FILE.60/with/529104447>.
2025-10-09 10:03:21 +01:00
Lorenz Leutgeb ae39f24b58 node: Support systemd credential for secret
While it is possible to use systemd credentials via

    LoadCredential=xyz.radicle.node.secret:…
    ExecStart=radicle-node … --secret "${CREDENTIALS_DIRECTORY}/xyz.radicle.node.secret"

Make usage more convenient and directly support passing the secret key
via a systemd credential. The ID of the credential must be

    xyz.radicle.node.secret

and is not user-configurable.

A systemd service unit file might contain:

    LoadCredential=xyz.radicle.node.secret:…
    ExecStart=radicle-node …

This requires just a bit of plumbing in `radicle-node`.

The preference order for the path of the secret key is:
 1. The command line argument `--secret`.
 2. The systemd credential.
 3. The configuration file.
 4. The default location to preserve backward compatibility.

The reason to prefer the systemd credential over the configuration file
is that it uses a mechanism that is influenced by the environment of the
process, which is deemed "closer at runtime" or "more dynamic" than a
configuration file. Ad-hoc overrides are still possible via the
commandline argument.
2025-10-09 10:03:21 +01:00
Lorenz Leutgeb 0c513e981f systemd: Support Credentials
Add `mod credential` with `fn path` which implements a simple lookup of
systemd credentials. See <https://systemd.io/CREDENTIALS/>
2025-10-09 10:03:21 +01:00
Lorenz Leutgeb 8c1073b9c9 cli/publish: Use clap 2025-10-09 10:52:21 +02:00
Lorenz Leutgeb 7effa7c64c
node: Report sync status for given namespaces
The "seeds" command does only take a repository ID, and
carries the implicit assumption that the namespace with the same public
key as the Node ID of the receiving `radicle-node` process should be
used as reference for determining whether other nodes are in sync,
but no other namespace.

In the spirit of separating user and node identity, relax this, so that
the command also carries the public keys relative to which the sync
status should be computed. This allows to inspect sync status for
arbitrary namespaces via command.

For now, this feature is not exposed to the CLI, but `rad sync status`
always passes the public key of the active profile.
2025-10-09 07:55:11 +02:00
Lorenz Leutgeb 9689de9af0
node: Allow announcing refs for given public keys
The "announce references" command does only take a repository ID, and
carries the implicit assumption that the namespace with the same public
key as the Node ID of the receiving `radicle-node` process should be
announced, but no other namespace.

In the spirit of separating user and node identity, relax this, so that
the command also carries the public keys for which the announcement
should be made. This allows to announce arbitrary namespaces out of
storage via command.

For now, this feature is not exposed to the CLI, but rather:
 - `rad sync` always announce for the public key of the active profile.
 - `git-remote-rad` ditto, as it calls the same codepath.
2025-10-09 07:55:10 +02:00
Lorenz Leutgeb 4dbb022d22
radicle/node: Create submodule "command"
Purely for better overview, keeping backwards
compatibility with re-exports.
2025-10-08 23:58:41 +02:00
Lorenz Leutgeb 8e7d193202
radicle: Update `CHANGELOG.md` for next iteration 2025-10-08 23:58:41 +02:00
Lorenz Leutgeb 7b8da0e72f cli/fork: Use clap 2025-10-08 23:31:50 +02:00
Lorenz Leutgeb 3992d519c3 cli/clone: Use clap 2025-10-08 15:52:16 +02:00
Lorenz Leutgeb 7c89045ea0 cli/debug: Use clap 2025-10-08 15:30:18 +02:00
Erik Kundt 2e77d5ef4d cli/block: Improve example values for target argument
This visual representation follows the one that `clap` is generating
for defaults and possible values.
2025-10-07 15:01:57 +02:00
Lorenz Leutgeb 6d698bb794 cli/block: Use clap 2025-10-07 14:20:13 +02:00
Fintan Halpenny ed5a68c1d7 node: mark modules as private
In an effort to reduce the change of version numbers, dependents of
`radicle-node` should not need to know about the modules:
- `control`
- `wire`
- `worker`

These are internal to the `radicle-node` running, and none of its dependents
accessed them.

This resulted in removing two unused type aliases.
2025-10-06 18:57:30 +02:00
Fintan Halpenny 59e09078bb radicle: mark `CheckoutError` as `non_exhaustive`
Recently, this error enum was changed. To ensure it does not cause crate version
changes, in the future, mark it as `non_exhaustive`.
2025-10-06 18:57:30 +02:00
Fintan Halpenny 5b260964cb crypto: mark errors as `non_exhaustive`
To improve crate version churn, mark the error types as `non_exhaustive`.
This removes the need to change the version number of the crate when adding a
new variant.

Note that these errors are never matched on, so no other code is changed in
dependent crates.
2025-10-06 18:57:30 +02:00
Lorenz Leutgeb 30908dcf48 radicle/git/canonical: Actually use qualified refs
Some tests actually do not use qualified patterns. The removed lines
*should* not compile because the `qualified_pattern!` proc macro
*should* validate the string literals at compile time, but it contains
a bug and actually does not assert well-formedness of qualified
refspec patterns. Rectify.
2025-10-06 17:31:12 +01:00
Fintan Halpenny ee041d8d06 crates: bump for release
- `radicle-term`: 0.15.0 -> 0.16.0
- `radicle-node`: 0.15.0 -> 0.16.0
- `radicle-fetch`: 0.15.0 -> 0.16.0
- `radicle-crypto`: 0.13.0 -> 0.14.0
- `radicle-cli`: 0.16.0 -> 0.17.0
- `radicle`: 0.19.0 -> 0.20.0
- `radicle-cob`: 0.16.0 -> 0.17.0
- `radicle-remote-helper`: 0.13.0 -> 0.14.0
- `radicle-protocol`: 0.3.0 -> 0.4.0
- `radicle-systemd`: 0.10.0 -> 0.11.0
2025-10-06 16:36:30 +01:00
Lorenz Leutgeb 2149770a4b
storage: Rewrite temporary repositories for clones
On Windows, all attempts to clone repositories failed with

    Fetch failed for rad:… from z6Mk…: Access is denied. (os error 5)

The reason is that, other than Unix-like systems, it forbids that
directories that are in use are moved.

To improve the situation, take back control over what is moved and
removed exactly by implementing `cleanup` instead of relying
on `impl Drop for tempfile::TempDir`.

A new type `TempRepository` is introduced to capture a repository that can be
used temporarily, and either cleaned up on failure, or moved on success.
This `TempRepository` can be constructed using `Storage::temporary_repository` –
renamed from `Storage::lock_repository` since it is only creating a temporary
resource and not locking it.

In the future (once Rust 1.89 is a little less cutting edge and more
widely available), we may opt for actual locking via
`std::fs::File::lock`.
2025-10-06 11:52:42 +02:00
Lorenz Leutgeb 31a7d3bd3f
fetch: add `Handle::into_inner`
Adds an `into_inner` method to deconstruct the `Handle` back into the underlying
repository type.
2025-10-06 11:52:42 +02:00
Fintan Halpenny e40fe86ff8 fetch: use `AsRef<Repository>`
Allow the fetch interface to accept anything that implements
`AsRef<Repository>`. This allows flexibility in the types that the `Handle` can
accept.

This change is motivated by wanting to introduce a type that is a temporary
repository that wraps a `Repository`.
2025-10-06 11:52:42 +02:00
Fintan Halpenny a163f4e939 fetch: refactor repository access
The interface of the `Handle` has two methods: `Handle::repository` and
`Handle::repository_mut`. However, the calling logic would always access the
`Handle::repo` field.

This is cleaned up by making this field private, and using the
`Handle::repository` method instead. There were no calls to `repository_mut`,
and so the method was removed.
2025-10-06 11:52:42 +02:00
Lorenz Leutgeb 5caa7b302a remote-helper: Check base when matching revisions
It is impossible to preserve the head of a revision and only change
the base of same revision via push.

Strenghthen the precondition for skipping updates to also consider the
base commit.
2025-10-03 11:30:22 +01:00
Lorenz Leutgeb 9bb136105c remote-helper: Only update patch after evaluating base
Without quorum for the default branch, the remote helper would report
an error, suggesting that the patch was not updated, while,
inconsistently, actually updating the ref in storage without creating
a new revision.

    $ git push …
    To rad://z…5/z…z
    …
     ! [remote rejected]       612… -> patches/a77… (…)
    error: failed to push some refs to 'rad://z…5/z…z'
    …
    $ git ls-remote rad://z…5/z…z
    612…        refs/heads/patches/a77…

This is very confusing.

Fix this by using a temporary reference name that is different from the
reference name that the patch would use, implementing cleanup via
`Drop`.

While at it, also move the logic into `patch_base` and share it
between `patch_open` and `patch_update`. Computation of the canonical
head via `stored.canonical_head` is moved to a branch that is only
taken if the user did not specify a base explicitly. This allows to
update patches even when there is no quorum for the default branch.
2025-10-03 11:30:22 +01:00
Lorenz Leutgeb 5cd016b587 radicle-systemd: Guard `mod listen` for Unix
This crate does not build on Windows, because there is a Unix-only `use`
in `mod listen`. To get the crate to build, guard the module
appropriately.
2025-10-02 15:46:56 +01:00
Lorenz Leutgeb 9c8ab7fa62
fetch: Rewrite `git::repository::direct`
This function (and the helpers `ancestry` and `find_and_peel`) are
eagerly peeling to commits, leading to updates of tags that target the
same commit to be missed.

For example, there could be tag two tag objects with *different* OIDs
A and B, from *different* authors, using *different* tag names, signed
with *different* secret keys, and both pointing to *the same* commit C.

The implementation would consider A and B to be the same, just becuase
A and B both peel to C, skipping the update.

This is counterintuitive, and when combined with canonical references
can be quite confusing.

Change this to only reason about an ancestry if the two objects in
question really both are commits directly. Otherwise, treat cases where
no structure can be used as ancestry similarly to non-fast-forward
updates.
2025-10-02 12:31:38 +02:00
Lorenz Leutgeb d9ce078d56 protocol: Decrease log-level for logging ping/pong
When logging writes, use a lower more verbose level for ping and pong,
as these messages are quite noisy.
2025-10-02 10:47:29 +01:00
Lorenz Leutgeb 474b97950b
node/log: Define syslog identifier
The argument passed to `fn logger` would only be used as a fallback.
Make it required to pass an identifier, as this is a good practice.
2025-10-01 16:46:46 +02:00
Lorenz Leutgeb ee49e28766 cli/unfollow: Use clap 2025-10-01 14:36:39 +02:00
Lorenz Leutgeb 6fb1ebec45 cli/clean: Use clap 2025-10-01 14:12:22 +02:00
Lorenz Leutgeb 753b7aef93 cli/path: Use clap 2025-10-01 13:04:28 +02:00
Lorenz Leutgeb 01f9f3fcd4 cli/unseed: Use clap 2025-09-30 15:13:09 +02:00
Lorenz Leutgeb 80bc95269b
cli/stats: Use clap 2025-09-29 16:44:18 +02:00
Lorenz Leutgeb f1c7c98607
cli/issue: Move definition of "about" to args 2025-09-28 19:47:50 +02:00
Christopher Fredén c7bff28452 cli/issue: Use clap
A rewrite of the argument parsing portions of the `rad issue` subcommand
using `clap` instead of `lexopt`.

From a user's perspective, the look-and-feel of `rad issue` does not
change much. Although, the most prominent change is to rely on
`clap`'a default error template which looks a bit different from what we
use usually (`error: ...` vs. `✗ Error: ...`).

Leaving the above restriction aside, subcommand mis-usage is
reported more verbosely.

Also configure `clap` to use colored output for errors and help pages.
In help output, headers are colored with `Magenta` to match the overall
CLI styling.

Specify a type that captures the different actions that can be take
when using the `comment` command: `Comment`, `Reply`, and `Edit`.
Move the functions that run the corresponding actions into a `comment`
module.

To preserve the "default command" behaviour being `rad issue list`,
e.g., `rad issue --solved` behaving like `rad issue list --solved`,
introduce a new type `EmptyArgs`to also parse arguments without a
subcommand. In case no subcommand was parsed, construct `Command::List`
with from `EmptyArgs`.

Co-authored-by: Matthias Beyer <mail@beyermatthias.de>
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
Co-authored-by: Erik Kundt <erik@zirkular.io>
Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2025-09-28 18:02:55 +02:00
Lorenz Leutgeb 675a0f81d9
cli: Depend on clap 2025-09-28 18:02:55 +02:00
Lorenz Leutgeb 5a958b5ebd
cli/test: Output of `rad --help`
With migration subcommands to `clap` planned one-by-one,
prepare this to catch regressions.
2025-09-28 18:02:19 +02:00
Lorenz Leutgeb 22720e718b node: Make location of secret key configurable
With this change, the location of the secret SSH key can be configured
through `${RAD_HOME}/config.json` so that the node key does not have to
be placed under `${RAD_HOME}/keys` anymore.

Further, there is now an option to override `config.json` directly when
executing `radicle-node` via the command line argument
`--secret`.

The primary motivation is more flexible deployments, for example
leveraging external secret management solutions, like
<https://systemd.io/CREDENTIALS/>.

The secret key is fingerprinted by taking the fingerprint of the public
key corresponding to the secret key. This allows to detect when the
secret key changes.
2025-09-26 13:03:57 +01:00
Lorenz Leutgeb 5887edf93b
crypto/ssh/keystore: Explicit paths
The implementation of `Keystore` requires a particular layout and naming
of keys. This is a rather strong limitation. Lift it and allow
specifying an arbitrary path for the secret and public key.

As it is possible to derive the public key from the secret key, make
the public key in `Keystore` optional. Also allow constructing
`MemorySigner` can now be initialized without a public key, by
deriving it.
2025-09-26 13:21:38 +02:00
Lorenz Leutgeb 9e1d6b1feb radicle: Detect current repository using `jj`
When using Jujutsu and a non-colocated Git repository, the detection
using `git2` directly fails (just as `git` in a shell would fail)
because there is no `.git` directory found by traversing up the
filesystem hierarchy.

Add an invocation of `jj git root` as a fallback.
2025-09-26 12:03:01 +01:00
Lorenz Leutgeb abc963f2f2 radicle-cli/tests: Add `rad_jj_patch` 2025-09-26 12:03:01 +01:00
Lorenz Leutgeb fd5043d572 radicle-cli-test: Prepare testing with `jj`
Most of this is taken from Jujutsu's own testing setup, see:
98d884827e/cli/tests/common/test_environment.rs (L106-L150)

Not all features are preserved, but this is good enough.
2025-09-26 12:03:01 +01:00
Lorenz Leutgeb 53522288c1 radicle-cli/tests: `fn program_reports_version`
Introduce this `fn` to check whether a program is available.
2025-09-26 12:03:01 +01:00
Lorenz Leutgeb fafb3493dc
cli: Detect key mismatch
When run with a secret and public key that do not cryptographically
match, `fn radicle_cli::terminal::io::signer` would prompt the user
for a password to unlock the secret key, and then carry on with a
mismatching key pair.

Fix this by verifying that the keys match in `MemorySigner::load`
and only swallow errors initializing the signer in cases where
prompting for a password makes sense. It does not make sense in the
case of mismatched keys.
2025-09-26 00:33:59 +02:00
Fintan Halpenny ed8b086045 node: Log Panics
Register a panic handler that logs a backtrace via the `backtrace`
crate, instead of setting `RUST_BACKTRACE`.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xy>
2025-09-24 10:51:34 +01:00
Defelo 0441b048f9
cli: Don't add newlines for empty descriptions in editor 2025-09-23 19:52:54 +02:00
Defelo ae01a42434
radicle: Allow to clear issue descriptions and comments
Previously it was possible to create an issue with an empty description
(i.e. an empty "root" comment), and also to change it later to a
non-empty string, however it was not possible to clear it again.
2025-09-23 19:52:54 +02:00
Defelo ec22c94321
cli/issue: Show previous title in `rad issue edit` editor 2025-09-23 19:52:53 +02:00
Defelo 9f62a82b0b
radicle: Fix `cob::common::Title::new`
The title should be trimmed before checking whether it is empty.
Also added a few tests.
2025-09-23 19:52:52 +02:00
Sebastian Martinez 5fea9ac05c node: Add `--log-logger structured` and `--log-format json`
Optionally depend on crate `structured-logger`. Allow enabling it via
`--log-logger structured`. For future compatibility, provide
`--log-format` which currently only supports JSON.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2025-09-23 16:33:50 +02:00
Lorenz Leutgeb e563117397 node: Refactor logging initialization
Logging initialization was infallible previously, i.e., a logger would
always have to be chosen, falling back to our own logger
implementation in case of failure with setting up journald submission.

This lead to complexity like attempting to log errors from parsing
arguments, as there is a circular dependency.

Change this to make logging initialization fallible. If it fails, exit
non-zero instead of falling back to another logger. This makes the
initialization code slightly simpler.

At the same time, make choosing the logger possible via the command
line and deprecate `--log`.

Note that we still default to journald if detected.
2025-09-23 16:33:50 +02:00
Yorgos Saslis 9793b4e7b6 systemd: Require Linux for journal module
The dependency `systemd-journal-logger` does not build on macOS, and
there are no tests for other Unixes. Also, systemd is most common on
Linux.

To avoid compilation errors on non-Linux platforms, only depend on the
crate when building on Linux.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2025-09-19 15:39:53 +02:00
Defelo e70850cb36 remote-helper: Add patch.branch option 2025-09-19 07:57:44 +01:00
Lorenz Leutgeb 7b00bf2e3a cli/patch/review: Obsoletion Warning
As discussed in:
<https://radicle.zulipchat.com/#narrow/channel/369873-Support/topic/new.20to.20reviewing.20patches/with/533862639>
2025-09-17 13:32:50 +01:00
Lorenz Leutgeb 8dd17e2a60 cli/warning: Add `fn obsolete`
A helper to print obsoletion warnings to stderr.
2025-09-17 13:32:46 +01:00
Lorenz Leutgeb 7d1db6a013
cli/diff: Deprecation Warning
This command is just a small wrapper around `git diff` not worth
maintaining.
2025-09-17 10:35:15 +02:00
Lorenz Leutgeb 8558cc2233
cli/self: `--nid` deprecation warning to stderr
Printing the warning to standard output breaks scripts.

Instead, print to standard error.
2025-09-17 10:31:53 +02:00
Lorenz Leutgeb 3fb04623a4
cli/warning: Add `fn deprecate`
A helper to print deprecation warnings to stderr.
2025-09-17 10:31:25 +02:00
Lorenz Leutgeb 2635562c92
cli/node/status: Add `--only nid` 2025-09-17 10:31:21 +02:00
Erik Kundt d2e10fdef6 cli/tests/commands: Clean up test `rad_patch`
This removes the `rad issue` test examples from being used in the
`fn rad_patch` test example execution. Removing it here is fine, since
it is being executed on its own anyways.
2025-09-16 19:40:35 +02:00
Sebastian Martinez 19210faab8 protocol/service: Change `Routing table updated..` from info to debug 2025-09-16 13:38:26 +01:00
Lorenz Leutgeb 86472fdccb
remote-helper/fetch: Improve error handling 2025-09-16 11:51:08 +02:00
Lorenz Leutgeb f542df1833
radicle: Use `git fetch-pack` for "local fetch" 2025-09-16 11:51:08 +02:00
Lorenz Leutgeb 20663a4e39
remote-helper: Use `git send-pack` for "internal push" 2025-09-16 11:44:56 +02:00
Lorenz Leutgeb f9ff484c9e cli: Make `rad patch show` prettier
The output of this command is confusing. It is not clear which of the
hashes printed is that of the revision and which one is the respective
head commit.

Further, the hash is omitted on the initial revision, adding confusion.

Further, the terminology of "revised" vs. "updated" is confusing.

This rewrites `timeline.rs` to drop the distinction between various
revisions (in wording, the small icon is still different) and cleans up
the output.

Attention is paid to alignment of the output. Now all verbs of updates
("accepted", "rejected", "reviewed", "merged") are aligned, and authors
("… by …") are also aligned.

The rewrite itself is not for better code quality or readability.
Note that all the code in this file only accessed via one `fn` by one
callsite in the implementation of `rad patch`.
2025-09-16 11:37:13 +02:00
Lorenz Leutgeb 43246fe6c8 remote-helper: Parse base revision early
Translation of the string passed as the value for push-option base
happens delayed.

Change this by parsing as soon as the value is written.

This also decreases the dependency fingerprint on `radicle-cli`.
2025-09-15 16:28:02 +01:00
Lorenz Leutgeb ef27961866
remote-helper: List `HEAD`
Report the default branch correctly via the symbolic reference `HEAD`.
2025-09-15 17:10:29 +02:00
Fintan Halpenny 14fcf50677 docs: fix doc string linking 2025-09-15 10:34:42 +01:00
Lorenz Leutgeb ee9e6de5f3 remote-helper: Do not assume remote name
Avoid ending up with an upstream to a potentially non-existent remote
when pushing to an anonymous remote.
2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 6b9ff4f99e cli/init: Allow `--setup-signing` with bare repos
The `--setup-signing` flag is a no-op when combined with `--existing`
and errors on bare repositories.

Make it effective in combination with `--existing` and also rewrite it
to support bare repositories, gracefully falling back to just avoid
writing to `.gitsigners`.
2025-09-15 10:21:09 +01:00
Lorenz Leutgeb fd93240b9d cli/git: Remove dead code `fn view_diff` 2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 86ea33b0ca cli/clone: Add flag `--bare` 2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 766e281d39 radicle: Allow creating bare clones 2025-09-15 10:21:09 +01:00
Lorenz Leutgeb e528c40a07 radicle-cli: Test initializing from bare repos 2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 606882f01f remote-helper: Simplify handling of `GIT_DIR`
The contract for Git remote handlers says that we can expect
`GIT_DIR` to be set, and this also simplifies logic regarding
bare vs. non-bare repositories.
2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 354565c579 radicle: Handle `GIT_DIR` more uniformly
Pull specification of the working directory into `radicle::git::run`,
to unify how higher level wrappers pass it down.
2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 4e67467966 remote-helper: Refactor option handling
Group `["option", "progress", ..]`, and `["option", ..]` cases together during
match.

Make the key/value matching cleaner using `ok_or_else` and then matching on the
key value.
2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 876d22b072 remote-helper: Remove unused push error variant 2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 550fcccc27 remote-helper: Do not hard-code binary name 2025-09-15 10:21:09 +01:00
Lorenz Leutgeb 897404164b remote-helper: Make crate binary-only
This crate is not a library, i.e., it is not intended to be depended
upon by other crates. Rather, it implements the `git-remote-rad`
binary.

Reflect this by moving `lib.rs` to `main.rs`, merging it with
`git-remote-rad.rs`.
2025-09-15 10:21:09 +01:00
Fintan Halpenny 66adbffd61 term: Use static template in spinner initialization
Use `static` + `LazyLock` to ensure that the templates evaluation only
happens once and the value can be re-used on each subsequent access.
2025-09-15 11:17:35 +02:00
Erik Kundt 8fc37e3316 term: Move to indicatif spinner
This fixes line flodding on narrow terminals and a missing cursor after
cancelation by replacing the custom spinner with an indicatif spinner.
2025-09-15 11:17:35 +02:00
Erik Kundt ee9ecfda76 remote-helper: Remove `SyncWriter` in favor of `PaintTarget` 2025-09-15 11:17:35 +02:00
Erik Kundt 153a8f9fbe node: Remove `SyncWriter` in favor of `PaintTarget` 2025-09-15 11:17:35 +02:00
Erik Kundt 11a109ef41 term: Introduce `PaintTarget`
This introduces a `PaintTarget`, that defines the stream an operation
uses to draw to (`stdout`, `stderr`, `sink`). It will be used in
subsequent commits to configure the new spinner.
2025-09-15 11:17:35 +02:00
Lorenz Leutgeb 379037956a remote-helper: Prevent doubly verifying each push
The context of the invocation of `git push` "internally" by the
remote helper is the same as by the user invoking `git push` most
of the time. That is, these pushes run within the same repository,
thus the configuration for pre-push hooks applies to both. This
leads to every push being verified *twice*.

If the user intends to configure pre-push hooks, then they would
do that on the remote with URL `rad://…`, and so the hook would
be executed before `git-remote-rad` even gets called.

We thus specify `--no-verify` to not verify again.
2025-09-11 16:59:12 +01:00
Lorenz Leutgeb a0f6cbf5f1 radicle: Move interpretation of output to binary
`radicle::git::run` interprets the `Output` returned by the `git`
process. However, depending on the application we have different
requirements for interpreting the output, e.g., how to handle errors.

Make `radicle::git::run` not interpret `Output`, but move this
logic to the respective binary crates (`radicle-remote-helper` and
`radicle-cli`).
2025-09-11 16:59:12 +01:00
Lorenz Leutgeb 0a8317c35f remote-helper: Interpret verbosity option
The `verbosity` option is ignored, which makes it more difficult to
obtain debugging information if the "internal" push, i.e., the
`git push` invocation by `git-remote-rad` fails.

Provide two types for verbosity, and wire them up.
2025-09-11 16:59:12 +01:00
Fintan Halpenny 11e8b89b20 radicle/src/git: additionally specify pruneTags for remote
Ensure that, when adding a remote, `pruneTags` is set to `false` so
that tags are not pruned when fetching from a remote's namespace.
2025-09-11 11:25:34 +01:00
Matthias Beyer 646d4360e7 cli/node: Replace manual pushing with Table::extend()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:29:47 +02:00
Matthias Beyer 2f28232676 cli/issue: Replace manual pushing with Table::extend()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:29:36 +02:00
Matthias Beyer 8554e996b8 cli/diff: Replace manual pushing with Table::extend()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:28:53 +02:00
Matthias Beyer fbef60eed6 cli/sync: Replace manual pushing with Table::extend()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:27:59 +02:00
Matthias Beyer 370ae3643a cli/patch: Replace manual iterator partitioning with Itertools::partition_result()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:23:40 +02:00
Matthias Beyer 3f489354b6 cli: Add itertools dependency
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:23:30 +02:00
Matthias Beyer 9c2f97abe1 cli/remote: Replace manual building table from iterator
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:23:30 +02:00
Matthias Beyer 4bd45cdabd cli/inbox: Replace manual building table from iterator
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:23:30 +02:00
Matthias Beyer 016cab1dbf term/table: Add Table::with_opts() to set options after Table obj was constructed
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:23:30 +02:00
Matthias Beyer fb458537b4 cli/patch: Replace manual building table from iterator
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:23:30 +02:00
Matthias Beyer 41a742ed9b term/table: Add impl FromIterator for Table
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-10 21:23:28 +02:00
Matthias Beyer ff021d5889 cli: Rewrite `#[path="…"]` module declarations
Rewrite module declarations that use the `#[path="…"]` annotation
to the more idomatic `pub mod …` + `pub use …` forms.

For the "self" module, that is not possible because `self` is an
identifier in Rust, and also `r#self` is not admissable as a raw
identifier, so stick to `rad_self`.

Files in submodules were patched as appropriate.
2025-09-10 21:09:29 +02:00
Matthias Beyer 11fc98c9c9
term: Replace manual Extend impl for Table
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-09-07 12:39:32 +02:00
Lorenz Leutgeb f00d1d6743 cli/self: Stop printing information about the node
To get to a point of separating the users' identity from the node, then the `rad
self` command should not display information related to the node so prominently.

The relation between `rad` and `radicle-node` is really similar to that between
`rad` and SSH Agent. They communicate via socket. So, when this connection is
successful, it is printed, but not more.

There may be some re-learning for users here, but it is worth the improvement.

Note that the information being removed here is available via `rad node status`
(see previous commit).
2025-09-04 17:12:19 +03:00
Lorenz Leutgeb e1af550a09 cli/node: Print Node ID
If we ever want to disentangle the users' identity from the node, then
the `rad node` commands must learn to print the information related to
the node, so here we go.
2025-09-04 17:12:19 +03:00
Lorenz Leutgeb 79505fa905 radicle/test/fixtures: Use "radicle.xyz" as `const`
Ideally, we would like to use a name that is compliant with RFC 2606,
such as "radicle.example.com", but this would change *lots* of hashes,
which is not worth the hassle.

Instead, make changing this in the future as easy as changing one
constant.
2025-09-04 16:44:05 +03:00
Lorenz Leutgeb 1a3fda547a radicle/explorer: Allow overriding default URL
This makes the default Radicle Explorer URL configurable at compile-time
via the environment variable `RADICLE_EXPLORER`.

In order to stay backwards compatible, we still default to
"app.radicle.xyz"
2025-09-04 16:44:05 +03:00
Lorenz Leutgeb e2c476a387 radicle/test: Don't hard-code "radicle.xyz" 2025-09-04 16:44:05 +03:00
Lorenz Leutgeb 2127782b74 term: Remove dependency on `anyhow`
As a library crate, it is bad to return such generic errors.

To get there, errors from `inquire` are downcasted.

This allowed to clean up the public interface of `radicle-term`, so that
it does also not leak the `inquire::InquireError` in its public API.
2025-09-04 16:28:20 +03:00
Lorenz Leutgeb edd88a59bf term: Remove `mod command`
This is dead code that imports `anyhow`. On the way to removing the
dependency on `anyhow` from `radicle-term`, it just seems to be a good
idea to remove the cruft.
2025-09-04 16:28:20 +03:00
Lorenz Leutgeb 79d928551b node: Remove dependency on `anyhow`
`anyhow` is only used to capture a handful of errors. It also turns out that
usage of `lexopt` was wrong, since it features `parse_with`, which neatly
integrates with `FromStr`. The types, luckily, implement this trait already –
making for cleaner parsing.

For now, the execution errors are all "transparent", which shouldn't be
much of a regression, if at all.
2025-09-04 16:28:20 +03:00
Matthias Beyer 55cdd880bf radicle-term: Pass in `mut self` rather than rebinding
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-27 23:18:33 +02:00
Matthias Beyer 19c484e956 radicle-term: Optimize impl of VStack::children()
This way the Iterator::size_hint() function can be used by the
Extend::extend() implementation, which might safe reallocations for
the underlying buffer.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-27 23:18:33 +02:00
Matthias Beyer 93388e366c radicle-term: Preallocate in Line::spaced()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-27 23:18:33 +02:00
Matthias Beyer 3036509e42 radicle-term: Add testcase for Line::spaced()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-27 23:13:32 +02:00
Matthias Beyer 23b9d30639 radicle: Remove unnecessary clone()
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-27 23:13:32 +02:00
Matthias Beyer 29a95fb1a6 radicle-fetch: Replace manual std::matches!() impl
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-27 23:12:50 +02:00
Lorenz Leutgeb ded0d19d59 protocol/wire/test: Define `fn roundtrip` and macro
In many tests we assert that encoding and decoding gives the same
object. Let's call that a "roundrip", and also provide a proc macro to
generate such tests.
2025-08-27 12:48:11 +02:00
Fintan Halpenny bbd1e2cff6 crates: 1.4.0 release
Update crate versions for the 1.4.0 release
2025-08-27 10:30:28 +01:00
Lorenz Leutgeb 819ae5fdf5 node/main: Refactor initialization of logging
It turns out that failing to read configuration resulted in no output,
because the logger would only be set after configuration had been read.

Fix this, by setting the logger early, with a log level that is then
corrected as soon as configuration is available.

While at it, also clean this up to allow logging errors about failure to
set up the logger.
2025-08-27 11:17:18 +02:00
Fintan Halpenny 7d2f0e387c node: e2e test for canonical ref update
Adds a test to ensure that canonical reference updates are received on the node
events stream.

Added a helper to `NodeHandle` for perfoming a commit to a reference and signing
the nodes references, to help with this use case.
2025-08-25 18:49:43 +01:00
Fintan Halpenny 0d96af5d07 node: update default branch for canonical refs
After a fetch is performed, `set_head` is called, which essentially updates the
default branch based on the canonical reference rule for that branch.

Later, `set_canonical_refs` is called, which originally could be the empty set
of rules. This would mean that the event for the default branch would never be
emitted.

The approach taken here is to always use the default branch rule if there are no
rules set in the identity document. Unfortunately, this ends up in computing the
default branch twice, due to `set_head`.

Another approach is to use the result of `set_head` to update the set of
`UpdatedCanonicalRefs`, to prevent the need for defaulting to the default branch
rule. However, this does not prevent the double counting as soon as the rules
are added to the identity document, since the default branch rule will then be
synthesised into the rule set.
2025-08-25 18:49:43 +01:00
Fintan Halpenny c38b9d9e1e node: simplify canonical reference calculation
The logging version was verbose, and not required. It also could end up double
processing canonical reference updates since multiple remotes may update the
same reference.

Instead, use `filter_map` to convert all the reference names, stripping
namespaces, and collect them into a `BTreeSet` to ensure uniqueness.
2025-08-25 18:49:43 +01:00
Fintan Halpenny a8255a2e07 Introduce a node event for canonical reference updates
Whenever the node fetches new updates, it checks if canonical references can be
updated. The node has learned how to return these results and emit them as node
events. This is a breaking change since it adds a new variant the `Event` type,
which is not forwards-compatible.
2025-08-25 18:49:43 +01:00
Fintan Halpenny 690f6b02c0 remote-helper: remove check for fast-forward
Since the commit is now always pushed to the Radicle storage, there is no need
to check for fast-forward. If heads diverge, it will be reported by the quorum
error.
2025-08-25 18:57:50 +02:00
Lorenz Leutgeb 119a124897 radicle: the great Canonical rewrite
This change was inspired by the a story as old as time:

    𝕃𝕖𝕥 𝕦𝕤 𝕞𝕚𝕩 𝕠𝕦𝕣 𝕓𝕦𝕤𝕚𝕟𝕖𝕤𝕤 𝕝𝕠𝕘𝕚𝕔 𝕨𝕚𝕥𝕙 𝕠𝕦𝕣 𝕀𝕆!

It was motivated by the fact that the canonical quorum logic was spread across
two modules and also two different repositories (in the API sense). The
`radicle-remote-helper` contained special logic for computing the quorum, and
also relied on the logic with `radicle` itself. It would also mix using the
Radicle storage repository and the working copy repository – resulting in issues
where objects could exist in one and not the other.

The change begins by separating away the IO away from any of the business logic
in the `git::canonical` module. To follow along, there are two important submodules:

1. `voting` captures the different type of voting processes for commits and tags
  a. Tags are simple, where one `Oid` means one vote
  b. Commits are slightly more complicated. They begin with one `Oid` means one
     vote, but then it is expected that merge bases are calculated for pairs of
     commits. These merge bases are used to increase a vote for an `Oid` if it
     is the merge base of another commit.
2. `quorum` builds on top of `voting` and uses the voting processes as well as
    the `threshold` to find the quorum for the tag or commit reference.
  a. For tags, the first past the threshold wins, but if multiple pass then it
     is an error.
  b. For commits, the merge base process should be used to increase the votes,
     until the caller is ready to find the quorum. At this point, the commits
     that pass the `threshold` are then compared to find the commit that is the
     child-most commit of all other candidates. If they diverge, then an error
     is returned.

There is also a `convergence` module that captures the logic that is required
for the `radicle-remote-helper`. It essentially checks if a candidate object
matches the expected objects, tags or commits, and performs the necessary
convergence logic – checking that the candidate commit is converging with
at least of the other `Did`s.

These two quorum processes, and the convergence process, essentially act as
state machines and can be driven by the use of a Git repository for finding the
merge bases. This is where the `effects` module comes in. The `effects` capture
the necessary traits that are required to drive the state machines of the quorum
processes. It is expected that a Git repository implements these, and in fact, a
`git2::Repository` implementation is provided. The traits are useful, since it
means that a `git2::Repository` can be swapped out and the logic would stay the
same.

This all culminates into the new and improved `Canonical` and
`CanonicalWithConvergence`. Both of which have methods `find_quorum` for
performing the quorum process using a *single* provided repository.

This resulted in the semantic change of requiring that the
`radicle-remote-helper` pushes the candidate commit, irregardless of whether it
will be a fast-forward. For now, this is something that will be accepted while
the UX can be improved in the future by providing detailed warnings of the
divergence and ways to fix it. The benefit is that the tooling will never stop
someone from diverging if that is in fact what they want to do.
2025-08-25 18:57:50 +02:00
Lorenz Leutgeb 8a66e4d044 node/systemd: Check that received sockets are AF_UNIX
Our implementation for the control socket is based on AF_UNIX, and as
the note (which is removed) suggests, we should actually check that the
received socket really is of that domain.

This check was not implemented because it is not exposed via `std`, and
a bit cumbersome to do via `libc`.

I did not realize that `socket2` which neatly sits between `std` and
`libc` in terms of its abstractions and is cross-platform allows us to
do this, and we even already depend on it!

So, add the suggested check.

While at it, refactor the function to return early in cases. Now the
progression from a file descriptor to a socket to a listener is nicely
captured in the types and not obstructed too much by indentation.

Also log at the error level.
2025-08-25 17:17:34 +01:00
Lorenz Leutgeb 192cc993a8 radicle: Fix panic when reading from SQLite database fails
I was greeted by `rad patch redact` with

    called `Result::unwrap()` on an `Err` value:

      Error { code: None, message: Some("failed to convert") }

    stack backtrace:

    […]
       3: core::result::Result<T,E>::unwrap
             at …/rust-1.88.0/lib/rustlib/src/rust/library/core/src/result.rs:1137:23
       4: sqlite::cursor::Row::read
             at …/index.crates.io-1949cf8c6b5b557f/sqlite-0.32.0/src/cursor.rs:136:9
       5: radicle::cob::patch::cache::query::find_by_revision
             at ./crates/radicle/src/cob/patch/cache.rs:624:65
       6: <… as radicle::cob::patch::cache::Patches>::find_by_revision
             at ./crates/radicle/src/cob/patch/cache.rs:553:9
       7: radicle_cli::commands::rad_patch::redact::run
             at ./crates/radicle-cli/src/commands/patch/redact.rs:23:9
       8: radicle_cli::commands::rad_patch::run
             at ./crates/radicle-cli/src/commands/patch.rs:1026:13
    […]

It turns out that `sqlite::cursor::Row::read` is the panicky version of
`sqlite::cursor::Row::try_read` (which returns a `Result`).

While it is somewhat rare that SQLite reads fail, it is not unheard of.
In `radicle-cli` it might not be critical, but also `radicle-protocol`
and `radicle-fetch` are affected, and they could potentially panic a
`radicle-node` process.

Use `try_read` instead, and propagate down the error handling.
2025-08-25 17:05:21 +01:00
Lorenz Leutgeb a568e7f484 protocol: Refactor decoding
Remove `fn deserialize` and instead provide a trait `fn decode_exact`
for testing.

Refactor errors, so that we can now cleanly distinguish between
`Error::UnexpectedEnd` which means that decoding might work given
more data, and `Error::Invalid`, which means that no amount of
additional data will decode.

While at it, cleanup the variants of `Error::Invalid`, and provide `ControlType`.
2025-08-25 11:57:44 +01:00
Lorenz Leutgeb cbd2a7070c protocol: Refactor encoding
What made the issue fixed in the parent commit harder to spot
for me was the multitude of concepts and terms involved in
encoding: What's *serializing* vs. *encoding*? Why is there
this odd `Frame::to_bytes`?

So I decided to clean up a bit. I removed `fn serialize` so that the
logic stays close to the data, and instead of it provide
`fn encode_vec` in `trait Encode` so that we don't add up with
one-offs like `Frame::to_bytes` again.

Also we make use of `encode_vec` in the tests.
2025-08-25 11:57:44 +01:00
Lorenz Leutgeb a8426dfdac protocol: Fix panic when serializing large frames
In 3c5668edd2 I mistakenly moved the check
the length of an encoded `wire::Message` from its `impl wire::Encode` to
the `fn serialize`, so that it would not apply only to messages (as
originally intended), but to *any* argument of `serialize`.

This is not a problem for gossip messages, but catastrophic for Git
streams, which tend to send a lot of data.

The fix is simple: Move the check back into `impl Encode for Message`.
2025-08-25 11:57:44 +01:00
Lorenz Leutgeb c5b99db101 doc: Oversight when integrating winpipe 2025-08-25 10:10:52 +02:00
Lorenz Leutgeb a4d83ec8d2 node/control: Please the borrow checker
This only surfaced after building again on Windows.
2025-08-24 20:04:00 +02:00
Lorenz Leutgeb fca96e697f node: Fix test on Windows
Small oversight in previous commit, as mutability of course also needs
to change in tests.

This only surfaced after building again on Windows.
2025-08-24 20:03:39 +02:00
Lorenz Leutgeb 19a262d3d6 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.
2025-08-22 15:45:20 +01:00
Lorenz Leutgeb ce11f03fe6 radicle/profile: Control socket path for Windows
The `winpipe` crate which is our current best bet for support on Windows
requires that named pipes are at the magical location `\\.pipe\`.

Provide a default on Windows that matches this, and use our freshly
assigned IANA service name for it.

This is a breaking change, as we remove a `pub const`, it should be
reasonably easy to fix by dependents, and probably it was a mistake to
make the default `pub const` as dependents should call `Home::socket`
anyway.
2025-08-22 15:29:08 +01:00
Lorenz Leutgeb fd34b680b5 signals: Guard most of the crate for Unix
The signals crate does not build on non-Unix-like platforms, such as
Windows. It uses constants for signals that are not exported from
`libc` on Windows, and transitive dependencies such as `sem_safe` fail
to compile.

Resolve this, by guarding most of the crate, certainly all real
features, by `cfg(unix)`.

To require less uses of the `cfg` macro, move all affected parts into a
new module, and re-export it.

The crate now builds on Windows (although it does not do anything
interesting on that platform) and its Unix interface remains unchanged.

Changes are integrated into `radicle-node`, but only affect Windows.
2025-08-22 15:19:31 +01:00
Lorenz Leutgeb 5229fb8a59 fix: Normalize filesystem paths with `dunce`
Microsoft Windows has Universal Naming Convention (UNC) paths, see
<https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths>

The Rust standard library normalizes paths to this form when calling
`std::fs::canonicalize`, see
<https://doc.rust-lang.org/std/fs/fn.canonicalize.html>

However, some programs, do not parse these paths correctly, including
our ally `git`:

    $ git clone \\?\C:\Users\lorenz\tmp
    Cloning into 'tmp'...
    ssh: Could not resolve hostname \\\\?\\c: No such host is known.
    fatal: Could not read from remote repository.

    Please make sure you have the correct access rights
    and the repository exists.

The `dunce` crate solves the problem of having to deal with
normalization and avoiding weird UNC paths for us. Note that on
non-Windows platforms, it *behaves exactly like*
`std::fs::canonicalize`!
2025-08-22 15:11:43 +01:00
Lorenz Leutgeb 31039bbceb radicle-fetch: More fine-grained errors
The `fn io_other` disguises all sorts of errors as I/O errors, making
errors in the transport component of `radicle-fetch` harder to
understand.

Instead, expose a new error type that allows to discriminate.
2025-08-22 14:56:03 +02:00
Lorenz Leutgeb d2517c5004 radicle-fetch: Fallback to known version
There's a good chance that gix can just keep going in this case.
2025-08-22 14:55:19 +02:00
Fintan Halpenny a670b6e668 radicle: fix stream tests
The stream tests did not take into account that the tips of previous COB entries
need to be inserted so that a Git history can be formed.

This was surfaced by the added regression test.
2025-08-20 11:43:18 +01:00
Fintan Halpenny 0c6ff06c65 radicle: use repository fixture in cob stream tests
If the tests are run without a global Git config, then the tests will error with
no name or email set for the commits.

Instead, use the test fixture setup to ensure there is a name and email for
running the tests.
2025-08-20 10:45:57 +01:00
Sebastian Martinez 1d7478cd90 radicle: introduce `cob::common::Title`
This patch adds the `cob::common::Title` struct, this allows instead of
using `std::str::String` or generics and traits for it, to define more
precisely what a title should be.
It trims the provided string and makes sure it contains no carriage
return or new line characters.

Where a `std::str::String` makes more sense so it's easier to mutate it,
we keep the code as is.

Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2025-08-19 17:05:33 +02:00
Fintan Halpenny 2a0f6fd3c5 cli: extend `rad cob log` behaviour
Use the COB stream behaviour for implementing `rad cob log` and introducing the
from and until options.
2025-08-19 15:01:26 +01:00
Fintan Halpenny 044ff8adde radicle: introduce specialised Stream types
Add type aliases to specialise the `Stream` type for each of `Patch`, `Issue`,
and `Identity`.

An `init` constructor is also added for each to make it easier to construct each
stream.
2025-08-19 14:49:51 +01:00
Fintan Halpenny c0ac228c37 radicle: introduce COB stream
Introduce a new `stream` module to `radicle::cob`. The purpose of this module is
to provide an API for iterating over a COB's operations, given a range of
commits.

The `CobStream` trait provides the generic API for implementing and using, while
`Stream` provides a concrete implementation using the `git2` crate.

The approach is to use a `git2::Revwalk` for walking over each commit in the
range, then loading the `Op` for that commit – skipping any commits that are
unrelated to that COB type, in the case of associated COBs.

The `Stream` is tested using some properties of the API.

The next steps are to introduce specialised types for `Patch` and `Issue` to
ensure that it works for those.
2025-08-19 14:49:51 +01:00
Fintan Halpenny 9b59c0e2c6 radicle: add Op::load method
Adds an `Op::load` helper method for loading an `Entry`, given its `Oid` and the
`store` it is stored in.

This can be useful for downstream consumers to inspect operations on COBs given
a single `Oid`, without having to load the entire object and/or graph.

It can also be useful when looking at implementing COB stream primitives.
2025-08-19 14:27:35 +01:00
Fintan Halpenny dbfcf424d6 radicle: add Op::manifest_of
Add a helper method `Op::manifest_of`, to allow the caller to inspect the
`Manifest` of the entry. This can be used to avoid attempting to call `Op:load`
with a manifest that would not match the expected `Action`s of the `Op`.
2025-08-19 14:27:35 +01:00
Fintan Halpenny bfe8c5234f cob: add method for loading entry manifest
Adds a helper for only loading the manifest of a COB entry. This allows callers
to inspect the manifest without loading the whole entry.
2025-08-19 14:27:35 +01:00
Fintan Halpenny 86119473b7 test: add alphanumeric generator
Add an `alphanumeric` function for generating alphanumeric strings.
2025-08-19 14:27:35 +01:00
Matthias Beyer 8953ec4c87 cli/inbox: Clearify "clear" behaviour
This patch clearifies (in the help message of the command) how the
"clear" subcommand behaves when passing ids.

Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2025-08-19 12:46:07 +01:00
Lorenz Leutgeb 9a7c22536b radicle/config/node: Use newtypes 2025-08-19 13:16:19 +02:00
Lorenz Leutgeb c4ff0d8eae radicle/config/node: Granular Default Values
Provide default values not only per struct, but for each individual
struct member. This allows more granular configuration and fallback to
defaults.
2025-08-19 13:16:19 +02:00