Commit Graph

129 Commits

Author SHA1 Message Date
Lorenz Leutgeb fa94638abb radicle/storage/init: Remove placeholder files
These files contain stub texts (see added reference) and only clutter
storage.
2026-02-16 11:16:30 +00:00
Lorenz Leutgeb dfe3b501c7 radicle/git/raw: `Config` not used on Windows
Fix a clippy lint that sneaked in…
2026-02-16 09:24:05 +00:00
Fintan Halpenny 8ee32168b3 radicle: remove file
The `mod id` statement was removed, but the file was not removed.
Remove the file since it is no longer needed.
2026-02-12 13:52:28 +01:00
Lorenz Leutgeb ebf7d876b4
node: Control via `uds_windows` not `winpipe`
Recent versions of Windows support Unix Domain Sockets natively, see
<https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/>.

By using that feature instead of Windows named pipes, the difference
for handling communication via the control socket comparing Unix-like
systems and Windows becomes smaller:

 1. No special paths like `\.\pipe\…` have to be handled.
 2. Not two I/O mechanisms are abstracted (named pipe and UDS) but
    just one.
 3. `winpipe` relies on background threads while `uds_windows` does
    not.

Once the feature `windows_unix_domain_sockets` (which is tracked at
https://github.com/rust-lang/rust/issues/150487) stabilizes, it will
likely be possible to just drop the dependency `uds_windows` and use
the implementation in `std::os::windows::net` directly.
2026-02-12 10:18:48 +01:00
Lorenz Leutgeb 8fb2d96b5c
radicle/profile: No pager from Git on Windows
Git for Windows horribly mangles the configured binaries through a POSIX
`sh`-like environment even on Windows. We might be able to figure out
which `sh.exe` we would have to execute on Windows, but this is too
brittle. Windows users will have to use other mechanisms (like `$EDITOR`
and `$PAGER`) to configure their programs.
2026-02-11 10:26:22 +01:00
Lorenz Leutgeb faeee9f370 crypto: Optionally provide JSON Schema
Providing the schema in `radicle-crypto` saves a lot of repetition
in `radicle`, and can be done via an optional dependency.
2026-02-10 09:39:34 +00:00
Fintan Halpenny 1cab036c33 protocol/service: Wire up `FetcherService`
Wire up the new `FetcherService`. This reduces the `Service` fetch
methods down to:
- `fetch`
- `fetched`
- `dequeue_fetches`
- `fetch_refs_at`

This simplifies the code by off-loading the logic to the `fetcher`
family of types, rather than handling the intricacies in the `Service`
itself.

The `Service` now just performs the necessary I/O based on the
returned events from the `fetcher`.

This also removes the fetching state from the node sessions. This
follows the single responsibility, and sessions now only care about
their connectivity.

Breaking changes:
- The `Connected` state of a peer no longer contains fetching
  information, which was being returned as part of the JSON payload
  result.
- The `rad debug` information for ongoing fetches contained the number
  listeners awaiting for results, this was removed.
2026-02-05 20:04:28 +01:00
Fintan Halpenny 7cf73300f6 radicle/storage/refs: Derive `Hash` for `RefsAt` 2026-02-05 20:04:28 +01:00
Defelo 589925e3a3 radicle/node: avoid unnecessary allocations in `Emitter::emit_all` 2026-01-26 17:40:02 +00:00
Adrian Duke 67a4a712e4 radicle: add new error type for unauthorized non-delegates
The previous error `UnexpectedState` was opaque, and ended up in a
confusing message when a user would try to edit the identity document
while not being a deleagte themselves.

Instead, provide a more specific error that mentions the `Did`
performing the action, and the action itself.

Note that the `UnexpectedState` variant was matched on during
evaluation, meaning that operations would still appear in the timeline
of the COB. With the change to the other variant, the timeline does
not record the operation anymore.
2026-01-22 10:24:26 +00:00
Fintan Halpenny b41ce2e6fa radicle/canonical: provide public Canonical JSON link
The previous link is now behind an authentication layer.
Replace it with a webarchive link instead.
2026-01-22 09:10:53 +00:00
Defelo a4e66d141e radicle: Allow all references to be included in sigrefs
Previously, the `references_of` implementation restricted the set of
references to certain categories: heads, tags, notes, rad, and cobs.

This is too restrictive to allow peers to share any references they
want. The only exceptions is references `refs/tmp/heads` that are
created for creating patches.
2026-01-21 11:19:06 +00:00
Fintan Halpenny 958422a7c4 node/routing: remove error scenario from Database::prune
A call to `Database::prune` with no limit could result in a
domain-defined overflow error.

If the limit is not provided, then it uses `i64::MAX`. It is
reasonable to expect that if the `usize` provided is more than
`i64::MAX` then the `i64::MAX` value is acceptable to use for pruning.
2026-01-19 17:56:56 +01:00
Fintan Halpenny 02318f199c radicle: Re-export `radicle_core::RepoId` 2026-01-09 14:04:18 +01:00
Fintan Halpenny 73827f5362 radicle: Re-export `radicle_core::NodeId` 2026-01-09 14:04:04 +01:00
Fintan Halpenny d98033a1ff localtime: localise the localtime dependency
The `localtime` crate was defined by cloudhead, and is a minimal
repository with a single `lib.rs`.

Instead of using it as an external dependency, copy the code directly
into a new workspace crate `radicle-localtime`.

The default `serde` implementation goes through the `LocalTime`'s
seconds values rather than milliseconds, since this is the more common
format. This allows the removal of the `serde_ext` functions.
The one place milliseconds was used was for the
`radicle::cob::common::Timestamp` type, where the `Serialize` and
`Deserialize` implementations are manually written.

It also adds a `schemars` feature to remove `schemars_ext` functions
in `radicle` as well.
2026-01-08 14:58:41 +01:00
Fintan Halpenny b8a6e1a52f radicle/cob/stream: skip commits that do not have a manifest
For some COBs, parent commits can be specified for actions, for
example, patches will bundle their base and head commits as parents.

When the COB stream performs the revwalk, these commits will be
included, and will not have the manifest file – resulting in an error.

Instead, this error can be matched against, and the commit is skipped
instead. The other errors should still be resurfaced, since a commit
with a manifest should be expected to load the operation correctly,
and the commit should exist in the repository when attempting to load
it.

One might argue that a valid operation with a missing manifest could
occur, but that would mean that `radicle-cob` has performed an invalid
write. This is undetectable by the stream API, and is ambiguous with
the case of non-Op commits.
2025-12-28 14:01:01 +00:00
Fintan Halpenny 58305cda32 protocol/limiter: impl AsTokens for limits
Provide `AsTokens` implementations for the `Outbound` and `Inbound`
newtypes of `RateLimit`.

This allows them to be used for the rate limiting method.
2025-12-02 14:18:18 +01:00
Lorenz Leutgeb 37d4ae4a9f radicle/node: Revisit routeability of IP addresses
All IPv6 addresses would be considered globally routeable, even though
the Rust standard library offers convenience functions to check for
loopback, link-local addresses etc.

Improve checks for IPv6 routeability to catch the most obvious cases of
local or unspecified addresses.

Refactor the check for IPv4 routeability to be more readable and refer
to RFCs, IANA lists, and Rust stabilization tracking issues as
appropriate.
2025-12-01 12:34:02 +00:00
Lorenz Leutgeb f9a62e7d8d radicle/node: `Address::is_local` for DNS names
`Address:is_local` would return `false` for all DNS names. This is
incorrect, with one counterexample being the name "localhost", which
generally resolves to a local (usually loopback) address.

The function is changed to catch "localhost", but also the top level
domain ".localhost", which is reserved in RFC 2606, Section 2.

`Address::is_routable` would return `true` for all DNS names. While it
is much harder to decide global routeability based on a domain name, as
these usually have to be resolved to an address before being able to
judge routability, there is one particular class of names, namely local
ones (see above), which are not globally routable.
2025-12-01 12:34:02 +00:00
Lorenz Leutgeb dc624ed518
radicle/node/bootstrap: Remove IP addresses
Due to connection retries in `radicle-node` not choosing new addresses,
mistakenly using an IPv6 address when IPv6 is not supported would result
in failure to bootstrap.

Work around by removing IP addresses again. This means that DNS or Tor
will be required to bootrstrap.
2025-12-01 12:27:53 +01:00
Fintan Halpenny 45abb881a3 node: Only consider onion addresses if configured
There is an unfortunate lack of single-responsibility with handling the
addresses to connect to.

This is a band-aid fix until the point in time where this can better improved.
2025-11-28 21:52:39 +01:00
Sebastian Martinez 7e5a1ababc radicle: Skip invalid named folders
Instead of failing with an `InvalidId` error in
`radicle::storage::ReadStorage::repositories` for repos that don't have
a valid repository ID, skip them and log a warning.


Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2025-11-28 20:31:06 +01:00
Sebastian Martinez 6d0c571ea9 radicle: Return individual results for repo in `repositories_by_id`
Change `repositories_by_id` to return `impl Iterator<Item = Result<RepositoryInfo,
RepositoryError>>`
instead of `Result<Vec<RepositoryInfo>, RepositoryError>`.
This allows callers to handle failures on a per-repository basis rather
than having the
entire operation fail if a single repository lookup fails.

Previously, the method would stop processing and return an error as soon
as any repository failed to load. Now it processes all repositories and
returns individual results, making the API more resilient and giving
callers
more control over error handling.
2025-11-06 10:43:04 +01:00
Lorenz Leutgeb c268e809e9 chore: Fix mistakes discovered by typos 2025-11-04 14:09:07 +00:00
Yaroslav Halchenko 32d77641ab chore: Fix spelling errors with codespell 2025-11-01 12:11:02 +01:00
Fintan Halpenny 3c89525003 cli/sync: migrate to clap
Due to the idiosyncracies of this command there are a few things to note about
this migration.

The `rad sync` command acts as a default subcommand in itself, in that it has
options that do not apply to its subcommand, `rad sync status`. This means that
the options will print with `rad sync --help`, but they will not print with `rad
sync status`.

The `--inventory` flag is not compatible with any of the other flags and
options, thus they are all marked with `conflicts_with`. This cannot be done for
a positional argument, so `clap` will "helpfully" print out the usage, for
example, as:

    error: the argument '--inventory' cannot be used with '--fetch'

    Usage: rad sync --inventory [RID]

    For more information, try '--help'.

Which is incorrect, since `RID` is ignored. This is explained in the `--help`
documentation for `--inventory`.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2025-11-01 10:52:13 +01:00
Johannes Kühlewindt 5741bafa3b radicle/cob/db: Add index for issues and patches
To improve query performance for issues and patches, add a composite
index on "repo" and "id" to the SQLite database schema via a
migration.
2025-10-25 10:22:50 +02:00
Erik Kundt f20c2f215a node: Re-export `PeerAddrParseError`
In order to build higher-level errors that use `PeerAddrParseError`, it
must be re-exported.
2025-10-22 11:27:23 +01:00
Sebastian Martinez f8fe296ceb radicle/rad: Fix typo 2025-10-20 15:09:43 +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
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
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 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 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
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 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 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
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
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
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
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
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 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 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