Commit Graph

44 Commits

Author SHA1 Message Date
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 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
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 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 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
Defelo e70850cb36 remote-helper: Add patch.branch option 2025-09-19 07:57:44 +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 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 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
Erik Kundt ee9ecfda76 remote-helper: Remove `SyncWriter` in favor of `PaintTarget` 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 bbd1e2cff6 crates: 1.4.0 release
Update crate versions for the 1.4.0 release
2025-08-27 10:30:28 +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 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
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
Lorenz Leutgeb c089727e90 term, cli, remote-helper: Status Symbols
Symbols signaling status of a process and similar are duplicated in
three different crates. Also two different symbols to signal success are
used:

  1. ✓ (      Check Mark, U+2713)
  2. ✔ (Heavy Check Mark, U+2714)

Work against that by referring to `radicle-term` from the other crates.
2025-08-13 13:45:21 +01:00
Fintan Halpenny 0e48723b41 release: prepare crates
The `radicle` crate required a bump `0.16.1 -> 0.17`.

Due to this, all its dependents are also bumped
- `radicle-cli`: `0.14.0` -> `0.15.0`
- `radicle-fetch`: `0.12.0` -> `0.13.0`
- `radicle-node`: `0.12.0` -> `0.13.0`
- `radicle-remote-helper`: `0.11.0` -> `0.12.0`
- `radicle-schemars`: `0.1.0` -> `0.2.0`

The following also required upgrading:
- `radicle-ssh`: `0.9.0` -> `0.10.0`
- `radicle-systemd`: `0.9.0` -> `0.10.0`
- `radicle-term`: `0.13.0` -> `0.14.0`
2025-08-12 12:13:49 +02:00
Lorenz Leutgeb d7b48b9e29 radicle: Move logging setup calls to binaries 2025-08-07 08:30:59 +01:00
Lorenz Leutgeb 3d352f23e6 canonical: Support Annotated Tags
The logic for computing canonical references conflated the semantics of
annotated and lighweight tags, yielding confusing/wrong results. The
main culprit was the call to `peel_to_commit` in
`impl ReadRepository for Repository`, peeling tag objects away.

To resolve this, we separate out quroum computation per object type: one
implementation for commits, and one for tags.

Also move  move canonical error types and methods into their own
module to have a cleaner file structure for the main logic.

Capture the `BTreeMap<Oid, u8>` type into a `Votes` struct, with its own API.

We were getting the objects from the repository twice – once in `Canonical::new`
and once again in `ensure_commit_or_tag` – so the latter was removed.

Use an `enum CanonicalObject` to specify which object types the canonical
process supports.

During `converges`, separate commits and tags, and ensure that we are only
looking at objects of one type. Note, that Fintan think this is actually the
*wrong* place to do it because we already filter the objects – so we should
keep track of that information higher up the callstack.

Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
2025-08-04 17:00:54 +02:00
Lorenz Leutgeb 9068033789 chore: Automated fixes generated by clippy 2025-07-25 11:25:05 +02:00
Lorenz Leutgeb e7fb5647ac term: Remove dependency on libc
Introducing the new enum `TerminalFile` to model which standard stream
stream the terminal uses allows us to remove the dependency on `libc`
which does not build on Windows.
2025-07-23 10:30:33 +02:00
Fintan Halpenny afe64d5175 remote-helper: improve canonical handling
This set of changes refactors the canonical handling by isolating the business
logic into another type, `Canonical`.

The functionality remains the same, but certain error scenarios are better
handled. Primarily, the commits of the inner `Canonical` type are checked to see
if they exist in the working copy, and if not, provide a more useful message
about which commit was missing or invalid for which DID.
2025-07-19 12:32:31 +01:00
Fintan Halpenny 14444a43ea remote-helper: refactor pushing action
Reduce the nesting of if statements by using a `PushAction` enum that is
constructed by checking the `dst` reference and parsing the correct action, one
of:

- `OpenPatch`
- `UpdatePatch`
- `PushRef`

This makes the intent of the code a little bit more clear and reduces the tabbing
of big code blocks. It also captures the error specifically for handling the
parsing of this `dst` reference.
2025-07-19 10:53:51 +01:00
Tobias Hunger a9f75d47ec remote-helper: allow any revision in push src
This allows any revision that can resolve to an object. The `src` side of the
refspec is resolved to its corresponding `Oid` which is further used for remote
helper pushing and patch creation/update.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
2025-07-18 22:22:43 +01:00
Tobias Hunger da72557cf7 git-remote-rad: Allow pushing from bare repositories
Bare git repositories are meant to fetch into/push from and
fetching works, but pushing from a bare repo into radicle-storage
was prohibited.

This is a problem for me as I want to use `jj`, which uses
a bare git repository in the back (when not co-located into
a "normal" git repository).
2025-07-18 22:04:07 +01:00
Fintan Halpenny ff365e2d8b radicle: disallow default branch
Ensure that the default branch is only ever synthesized from the `Doc`'s
`threshold` and `delegates` value. If the rule is ever written into a `RawDoc`,
then the update of the that `RawDoc` must go through the `update::veryify`
function, which checks that the rule is not present.
2025-07-16 16:41:16 +02:00
Fintan Halpenny 7f646666bc radicle: canonical references payload 2025-07-16 16:41:16 +02:00
Fintan Halpenny b77809ae9d chore: prepare crates release
Prepare the crates for releasing to crates.io:
- radicle-remote-helper: 0.10 -> 0.11
- radicle-term: 0.12 -> 0.13
- radicle-cli: 0.13 -> 0.14
- radicle-node: 0.11 -> 0.12
- radicle-fetch: 0.11 -> 0.12
- radicle: 0.15 -> 0.16

It was also necessary to specify the `version` in the workspace Cargo.toml file,
since this is required for publishing to crates.io.
2025-07-14 15:38:09 +01:00
Fintan Halpenny b2bcd561cf radicle: store threshold in Canonical
The Canonical type is generally constructed with the threshold, needed
for the quorum computation, in scope. To make it easier to use and
encapsulate its functionality, it now holds the threshold as an
additional field.
2025-06-12 13:19:48 +02:00
Lorenz Leutgeb 5bc2dc677b repo: Move workspace crates into `crates` subdirectory
This keeps the top level of the repo cleaner, e.g.
<https://github.com/radicle-dev/heartwood> scrolls less and
<https://app.radicle.xyz/nodes/rosa.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/>
might show more files at the top level.

This way we also don't have to explicitly name all crates in
`Cargo.toml`.
2025-06-09 15:09:21 +02:00