A Rust client for rngit, Git over Reticulum. It is a library plus two programs:
- the library (
rngit) has no UI and doesn't need agitbinary: repository work is done in-process with gix, and the network side uses rsReticulum; git-remote-rnsis a git remote helper, so plaingit clone,fetchandpushwork withrns://URLs;rngitis a command line with a subcommand for every call the library makes. It shares its name with stock rngit's command, so install only one of them onPATH.
It is written to be embedded in any Rust application that needs to talk to rngit nodes, and it follows stock rngit's behaviour request for request (see Parity with stock rngit).
Requires Rust 1.89 or newer (edition 2024).
| Area | Calls |
|---|---|
| Git | list, fetch_bundle, fetch_bundle_with, fetch_into, fetch_objects, clone_into, push_from, push_from_with, push_bundle, delete_ref, update_ref |
| Repositories | create, fork, mirror, sync |
| Permissions | repo_perms, group_perms, work_perms and their set_ calls |
| Releases | releases, release, fetch_artifact, fetch_verified_release, create_release, delete_release |
| Work documents | work_list, work_create (create or propose), work_view, work_comment, work_edit, work_complete, work_activate, work_delete |
| Finding nodes | discovery::discover listens for rngit announces |
Fetches only download what the local repository lacks. Pushes send a bundle, or just move the ref
(update_ref) when the node already holds the object. Annotated tags keep their tag object.
/git/delete deletes a branch or tag (delete_ref). No request that deletes a whole repository
was found, so the library has none. Details are in docs/protocol.md.
use rngit::{Client, RepoUrl};
use rngit::rns::RnsTransport;
let repo: RepoUrl = "rns://727ca184e3d7e83e0e35146940920442/public/demo".parse()?;
// `runtime` is an rsReticulum `ReticulumHandle`; `identity` is the long-lived client identity
// the node authorizes. `rngit::session::Session` starts both the way the programs below do.
let client = Client::new(RnsTransport::new(runtime, identity, repo.destination));
client.clone_into(&repo, std::path::Path::new("demo.git")).await?;The protocol sits behind the Transport trait, so it can be driven without Reticulum
(default-features = false drops rsReticulum). The tests do this with an in-process node.
cargo install --path . installs git-remote-rns and rngit. Put them on PATH, then:
git clone rns://<destination hash>/<group>/<repo>
git push origin main
git push origin :old-branch
Settings, the same as for stock rngit:
| Variable | Meaning |
|---|---|
RNS_CONFIG |
The Reticulum config directory (default: Reticulum's own). |
RNGIT_CONFIG |
Where client_identity is kept (default ~/.rngit). It is created on first use. |
RNGIT_PATH_WAIT |
Seconds to wait for the node's path before the first request (default 5). |
rngit nodes 60 # listen for nodes
rngit refs rns://<hash>/public/demo
rngit create rns://<hash>/public/demo
rngit delete-ref rns://<hash>/public/demo refs/heads/old
rngit work-new rns://<hash>/public/demo "Fix the thing" "Details"
rngit release-new rns://<hash>/public/demo v1.0 "Notes" app.tar.gz
rngit help lists every command. Releases and work documents are signed with the same client
identity git uses. The command logic is rngit::cli, so it is tested without a network.
Everything a node sends is untrusted. Replies, bundles, artifacts and lists are size-capped
(Limits), parsing never panics, ref names from bundles must be valid names under refs/, a
bundle must carry the objects it builds on, artifact names with path separators are refused, and
a pushed ref is checked for fast-forward locally because the node's refusal can't be told apart
from a corrupt bundle. Release files are verified offline: signature, signer-equals-hash-of-key,
artifact hash and the manifest's notes hash. Trusting the signer is the caller's decision.
The protocol is implemented from docs/protocol.md, a clean-room spec
observed from stock rngit (its provenance is in section 0 of that file), and checked against
recordings of a real bundle, a real manifest and the requests a real client sent.
It has been run against live stock rngit nodes (rns 1.5.7) over a local Reticulum TCP link:
list, clone, create, fork, mirror, sync, incremental fetch, push (up to 40 MB), tags, branch and
tag deletion, the whole release cycle, the whole work document cycle, and permission reads all
work, and stock git fsck accepts the repositories on both sides. examples/live.rs drives
those calls; run it with cargo run --example live -- <reticulum config dir> <rns:// url>.
Known limits:
- Setting repository and group permissions answers
Not allowedon the tested node, even for the repository's creator (stock Python gets the same). Reads work. - Nodes reached over several hops and client aliases from
client_configare untested. - A node can't fork or mirror from itself; use another node as the source.
tests/captured.rsreplays the 49 distinct requests recorded from a stock client and checks that rngit builds the same ones.scripts/parity/runs one long scenario twice against one stock node, through the stock git helper and throughgit-remote-rns: clone, push, tags, annotated tags, force pushes, deletes, incremental fetch, errors and a 4 MB push. The requests the node saw, the exit codes and the resulting repositories match step for step. See that folder's README to run it.
cargo fmt
cargo clippy --all-targets -- -D warnings
cargo clippy --no-default-features --all-targets -- -D warnings
cargo test
CI runs these on Linux, macOS and Windows. Line coverage is kept at or above 85%
(cargo llvm-cov). Tests need a git binary. The parity script also needs pip install rns.
AGPL-3.0-or-later, the same as rsReticulum, which it links against.