Guides

Radicle User Guide

A fantastic journey through the Radicle universe


Software is the fundamental way in which we interface with data that comprises our current reality, and code is the language of software. Most software systems were created through a collaborative process involving multiple people. If we accept that software is important and collaborative by nature, then a shared system for communicating about code and collaborating is needed. This system shouldn’t be owned by one company, as code is too important to be monopolized.

Furthermore, our world is increasingly polarized, and this reality is finding its way into software companies and therefore into software products. Users are banned from using a platform because of their geographic location or the ideas they stand for. This is not tenable.

This is why we’ve created Radicle, an open protocol that enables users like you to collaborate on your code within a sovereign network. It’s a neutral environment where you have full ownership of your data and you have the autonomy to set the rules of your code universe as you see fit. You can work on software projects (and ultimately more) even when offline, while also being connected to a broader network, through which you’ll eventually be able to find contributors.

We’ve created this Radicle Guide to initiate you as a settler of our network through teaching you how to use the software we’ve developed—the Radicle CLI, which is both a node and command line interface tool for interacting with the protocol. This guide is split into multiple chapters and we recommend starting with the first chapter for a higher level introduction that takes you on a tour through Radicle’s core functions. Subsequent chapters will provide deeper dives into specific use cases, but they are still in progress.

👾

Greetings, I’m your guide! Whenever you see 👾, it’s because I’ve got some spicy tips on how to use Radicle. You’ll also see 🥷 for privacy tips, 👻 for things you should be wary of, and 🧠 for opportunities to learn more.

If you ever get stuck and want additional guidance, join us in our chatroom on Zulip and ask your questions in the #support channel.

Also, this guide teaches you how to use Radicle in a narrative fashion. If you’re looking for more straightforward usage docs, check out the Radicle manual or the Protocol Guide for more information on Radicle’s fundamental building blocks and motivations.

1. Getting Started

One of the important facts to understand about Radicle is that it is a peer-to-peer protocol, which is uncommon when it comes to software you typically rely on. Most collaboration technologies are hinged upon a client-server model, where you’re entrusting your data and social interactions to a single entity that is managing a bunch of servers or data centers. This paradigm is transformed in a peer-to-peer protocol, where every user of the protocol is a peer, participating equally. Peers on the Radicle network are also referred to as nodes, and it doesn’t matter what you are doing – whether you’re publishing repositories or discussing issues – you have to run a node.

Within a few paragraphs, you’ll be operating a Radicle Node via the command line by installing the Radicle software stack. It’s a pretty easy process that can be completed in under 5 minutes, but there are a few requirements that are important to run through before you proceed:

  1. You need a Linux or Unix based operating system like macOS or Ubuntu and a basic understanding of how to use your terminal.
  2. Radicle is built on top of Git, so a recent version of Git (2.34.0 or later) is recommended. We also assume you have some experience using Git.
  3. OpenSSH (version 9.1 or later) with ssh-agent has to also be installed and running. This is a helper program that is used to secure the secret key related to your Radicle identity.

👾

You can check your Git and SSH installations by running git --version or ssh -V in your terminal.

If you haven’t used Git before, you’re going to be in a world of pain and should read through the Pro Git book first.

Installation

The easiest way to install Radicle is by firing up your terminal and running the following command:

$ curl -sSf https://radicle.xyz/install | sh

This command runs an installer script that automates the setup process, by:

👾

If you want the above to be installed in a different location than ~/.radicle, you can set the RAD_PATH environment variable before running the installation script.

Note that if you don’t want to use the installer script, you can also always compile from the source code or get the binaries if you prefer.

Once you’ve installed Radicle, you can make sure that it was successful by running:

$ rad --version

Come into being from the elliptic aether

When using Radicle for the first time, you have to create a new Radicle identity, which is simply a cryptographic key pair, using the rad-auth command. The public key is what uniquely identifies you across the network, while the private key is used to authenticate your node, as well as sign code and other artifacts you publish on the network.

$ rad auth

That’s how you’ll cryptographically forge your new Radicle identity.

Follow the instructions by entering an alias, a non-unique name that makes it easier to identify your node, in addition to a passphrase to protect your key pair. Note that your alias can always be changed at a later time.

$ rad auth

Initializing your radicle 👾 identity

✓ Enter your alias: paxel
✓ Enter a passphrase: ********
✓ Creating your Ed25519 keypair...
✓ Adding your radicle key to ssh-agent...
✓ Your Radicle DID is did:key:z6Mkhp7VUnuufpvuQ3PdysShAjL86VDRUpPpkesqiysDBGs9. This identifies your device. Run `rad self` to show it at all times.
✓ You're all set.
...

When you’ve completed the steps, your new Radicle DID (Decentralized Identifier) will be generated and displayed. The Radicle DID is a self-sovereign identity you have full control over. If ssh-agent is running, your private key will also be added to it for future use.

If you run rad auth again you can verify that your key was added to ssh-agent.

$ rad auth
✓ Radicle key already in ssh-agent

Your Radicle DID is similar to your Node ID (NID), the difference is the former is formatted as a Decentralized Identifier, while the latter is just the encoded public key. Share your Radicle DID freely with collaborators.

If you forget your DID or NID, you can query your full identity by running rad self or alternatively you can grab your DID with rad self --did and your NID with rad self --nid.

$ rad self
Alias           paxel
DID             did:key:z6Mkhp7VUnuufpvuQ3PdysShAjL86VDRUpPpkesqiysDBGs9
└╴Node ID (NID) z6Mkhp7VUnuufpvuQ3PdysShAjL86VDRUpPpkesqiysDBGs9
SSH             running (3817)
├╴Key (hash)    SHA256:YCmRe6BkDOp45lYg0m5DeYxgRcPKftQZb4RmQD1nkjQ
└╴Key (full)    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO9xo9DHlsZJeZWnZaaawsnKFjcQxN4LQ…
Home            /home/paxel/.radicle
├╴Config        /home/paxel/.radicle/config.json
├╴Storage       /home/paxel/.radicle/storage
├╴Keys          /home/paxel/.radicle/keys
└╴Node          /home/paxel/.radicle/node

This displays your alias, DID, Node ID, SSH agent status, SSH keys, and the locations of important files or directories.

👾

Many of the other items you see in the rad self output can be viewed individually. Wondering about your alias? A quick rad self --alias has you covered. Need to pinpoint your Radicle home folder? rad self --home is your friend. And for your config file location, just hit up rad self --config.

If you’re ever feeling lost, rad self --help will lay out all your options.

Operate nodes, smoothly

Now that your node has an identity (or ‘public key’), you have what it takes to connect to the Radicle Network. That’s right – we don’t need your email address or any other personal identifying information. Your alias can be your cat’s name for all we care.

The first command to embed deep within your consciousness is the one for checking your node’s status:

$ rad node status

If the node is not running, you can start it as a background daemon with:

$ rad node start

After your node starts, it will attempt to connect to peers on the network.

But let’s take a step back and ponder some fundamental questions: What exactly happens when these nodes interact? And how does Git fit into this picture?

At its core, Radicle marries Git with peer-to-peer networking. Your node isn’t just another cog in the machine; it’s a conduit for collaboration. It’s there to host and synchronize repositories you’re interested in.

Radicle’s peer-to-peer architecture draws inspiration from Secure Scuttlebutt’s gossip protocol where data transmission relies on a publish/subscribe system, allowing peers to host and sync only the data they care about. Similar to BitTorrent, we use the terminology “seeding” to describe distributing and sharing a repository across the network.

With Radicle’s gossip protocol, peers exchange messages about the network that facilitate the discovery and replication of repositories. The replication process kicks off when a node establishes a secure connection with another node, the initiating node conducts a fetch operation via the Git protocol to pull the pertinent objects into the node’s storage, thereby availing them to the local user and other interested nodes.

If you’re curious about how Radicle works under the hood, read our Protocol Guide.

Git going with repositories

Now, time to return to your terminal. The next thing we’ll do is publish a repository to the Radicle network. Once we’ve done this, anyone with a node will be able to fetch and seed your repository, increasing its replica count on the network.

Pick a repository you maintain and navigate to a local copy. It can be any Git repository, but it should have at least one commit.

$ pwd
/home/paxel/src/dark-star

$ git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean

To publish it to the Radicle network, run the rad init command.

$ rad init

Initializing radicle 👾 project in .

✓ Name: dark-star
✓ Description: Decoding data from the dark star to establish a model of the universe
✓ Default branch: main
✓ Visibility: public
✓ Project dark-star created.

Your project's Repository ID (RID) is rad:z31hE1wco9132nedN3mm5qJjyotna.
You can show it any time by running `rad .` from this directory.

✓ Project successfully announced to the network.

Your project has been announced to the network and is now discoverable by peers.
You can check for any nodes that have replicated your project by running `rad sync status`.

To push changes, run `git push`.

Don’t be afraid to do this on one of your existing public repositories! Because even if you aren’t ready to use Radicle for code collaboration, it works great for distribution. Radicle’s network of seed nodes help propagate and host code, forming a decentralized, censorship resistant, and ungovernable distribution system.

What rad init does is gather the essential details from you which are needed to initialize the repository into Radicle: the repository name, description, default branch (typically main or master), and whether it is public or private.

Similar to how git init is used to initialize a Git repository, we use nomenclature you’re familiar with so you don’t go crazy. That’d be so cruel if you had to do rad new, instead, wouldn’t it?

👻

It’s important to only publish repositories you own or are a maintainer of, and to communicate with the other maintainers so that they don’t initialize redundant repository identities.

After entering your repository details, you’re presented with a Repository Identifier (RID) that is a globally unique URN that you can share with your collaborators or friends so that they can clone or seed your repository. Besides generating the RID, rad init creates a special Git remote in your working copy named rad and publishes the repository to the network (assuming it’s public). We’ll talk more about private repositories in another chapter.

When you initialize a repository you are the sole maintainer or delegate, the term used in Radicle. Delegates can represent a group, person or even a bot. In your case, I’m assuming you’re a person. Delegates can be added or removed from a repository, by existing delegates. We’ll talk about this more in one of the next chapters.

👾

A few spicy commands you might want to know about, that can be run within a repository you’ve initialized:

By the way since your Radicle key is a valid SSH key, it can be used to sign Git commits since version Git 2.34.0. This is an alternative to the more common GPG signing method. If you’d like to setup your repository to use your Radicle key for signing commits, enter these commands from its working directory:

git config user.signingKey "$(rad self --ssh-key)"
git config gpg.format ssh
git config gpg.ssh.program ssh-keygen
git config gpg.ssh.allowedSignersFile .gitsigners
git config commit.gpgsign true

You can also set this up globally by passing the --global option to git config.

Commit signing is a purely optional step; Radicle internally signs everything you publish anyway, so don’t worry if you don’t set this up!

Note that your new repository will only be replicated by nodes that you are connected to and either have an open seeding policy, or follow you. Seeding involves both hosting the repository and synchronizing changes with other nodes. In the early stages of the Radicle network, all public repositories are automatically seeded by seed.radicle.garden which is a public seed node run by the core team.

You don’t have to trust or rely on our public seed node. You can run your own. As more people run them, Radicle becomes more resilient and reliable for code collaboration.

We are always looking to attract more people that want to operate public seed nodes: if that’s you, check out our Seeder’s Guide for more details.

Publishing ch-ch-ch-ch-changes

Still don't know what I was waiting for
Requiring an internet connection to push to remote
But now I push to my local-first, oh so `rad` remote
Instead of that one shoddy monopolized forge

As we just mentioned, rad init creates a special remote in your working copy named rad. What’s this doing under the hood? With Radicle, you will typically be interacting with two different repository copies on your device, the working copy and a hidden, stored copy that you interact with via git push rad and git pull rad commands using Radicle’s git remote helper program named git-remote-rad. Remote helpers are particularly useful for integrating Git with specialized network protocols like Radicle. Git calls the custom git-remote-rad program when it encounters rad:// remotes.

🧠

Git remote helpers are custom programs that allow Git to interact with repositories hosted on various types of servers or protocols that are not natively supported. Remote helpers are invoked by Git when it encounters a URL scheme that matches a supported helper. For example, in Radicle’s use case, if Git sees a URL starting with rad://, it will look for a remote helper program named git-remote-rad and use this program to handle operations related to that URL, such as fetching, pushing, and cloning. For more details, check out Git’s documentation on remote helpers.

To view information about the rad remote in your repository, try:

$ git remote show rad
* remote rad
  Fetch URL: rad://z31hE1wco9132nedN3mm5qJjyotna
  Push  URL: rad://z31hE1wco9132nedN3mm5qJjyotna/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C
  HEAD branch: (unknown)
  Remote branch:
    main tracked
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (up to date)

You’ll notice the Repository ID in a URL format under Fetch URL:

rad://z31hE1wco9132nedN3mm5qJjyotna

For the Push URL, you may be wondering, what is the element after the slash?

z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C

Does it look familiar to you? Well, it’s your Node ID. Remember you can always check your Node ID by running rad self --nid.

When using Git with traditional forges, you are pushing to a remote which is a centralized server that all other collaborators are also using. In Radicle, the remote is on your local device, which means that you can push changes even when you’re disconnected from the internet. Changes that you push to the remote while offline will automatically propagate to the network once you come back online. This is one of the very unique aspects of Radicle that makes offline work way smoother.

🧠

Radicle’s storage layout accommodates multiple source trees (forks) per repository. Each repository is stored as a bare Git repository, residing under a common base directory, uniquely identified by its Repository ID (RID). Rather than storing peer data in separate Git repositories with individual object databases (ODBs), Radicle consolidates peer data within the same Git repository. This is achieved using the gitnamespaces feature, where each peer’s unique Node ID serves as a namespace to partition their Git references.

See the storage section of the protocol guide for more information.

Now let’s try publishing some changes to see how this works.

Use your favorite text editor to make a change to the repository that you just initialized. For example, you can update its README to include instructions on how to clone the repository on Radicle:

$ git diff
diff --git a/README.md b/README.md
index 91b304791..70ef6efc9 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,12 @@

+## Radicle
+
+To clone this repository on [Radicle](https://radicle.xyz), simply run:
+
+    rad clone rad:z31hE1wco9132nedN3mm5qJjyotna
+

Once you’re finished, add and commit your changes with git add and git commit just as you would when collaborating on any other Git repository. Then use git push rad master to synchronize the changes with your node (be sure to replace master with your default branch, in case that’s not it).

$ git add README.md
$ git commit -m "Add instruction on cloning with Radicle"
$ git push rad master
✓ Synced with 1 node(s)
To rad://z31hE1wco9132nedN3mm5qJjyotna/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C
   ecb1bf0..74fb8d2  master -> master

Whenever you execute a git push rad command, you are pushing the changes in your local working copy to your remote copy. You see that the remote of your repository is represented by the URL:

rad://z31hE1wco9132nedN3mm5qJjyotna/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C

As the sole delegate of the repository, changes that you push to the repository’s default branch under the rad remote, represent the canonical or authoritative state of the repository. This means that if someone were to clone your repository, they would be getting the state that you just pushed, given your nodes are able to communicate with each other.

🧠

The canonical repository state is established dynamically based on the delegate thresholds defined for the repository. For example, if a threshold of two out of three delegates is set, with the defaultBranch specified as main, and both Alice and Bob have the same commit in their main branches, that specific commit is recognized as the authoritative, current state of the repository. In the current Radicle release, the dynamic establishment of an authoritative state is limited to a single defaultBranch. Future releases of the protocol may enhance this feature to include additional secondary branches.

You may be wondering – now that you’ve pushed to your remote, how do other peers get this update? Any peer who has cloned or seeded the repository will receive a reference announcement message from its peers, via the gossip protocol, which informs the node there was an update. Following this, peers conduct a fetch operation via the Git protocol to pull the pertinent Git objects into their local storage.

The basics of seeding and cloning

Nodes have a seeding policy which specifies the list of repositories they are interested in replicating on the network.

Whenever you clone or initialize a new repository, your node’s seeding policy is updated to keep these repositories in sync with the network.

There are a few ways that seeding relationships are established among nodes. One of the main methods is by one node intentionally cloning another repository. You can run the rad clone command to clone a repository from the Radicle network, by supplying a Repository Identifier (RID).

Let’s try this out. First, navigate to a directory where you typically clone Git repositories. Then run this command to clone the Radicle Heartwood repository:

$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
✓ Seeding policy updated for rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 with scope 'all'
✓ Fetching rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 from z6Mkk4R…SBiyXVM..
✓ Fetching rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 from z6Mksmp…1DN6QSz..
✓ Fetching rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 from z6MkrLM…ocNYPm7..
✓ Creating checkout in ./heartwood..
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFq…bS9wzpT
✓ Repository successfully cloned under /home/paxel/src/heartwood/
╭────────────────────────────────────╮
│ heartwood                          │
│ Radicle Heartwood Protocol & Stack │
│ 8 issues · 14 patches              │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the project directory.

Cloning has some similarities to Git’s clone command, in that it creates a working copy of a remote repository, but this is executed in a much different way since Radicle is a peer-to-peer protocol.

The rad clone command is essentially equivalent to running these lower-level commands:

rad seed Updates your seeding policy to start seeding this repository
rad sync -f Fetches the latest repository data from other seeds and stores it in your local storage
rad checkout Creates a working copy of the repository from local storage
rad remote add Creates a Git remote in your working copy for all repository delegates

When you only want to seed a repository and have no interest in contributing code, you can still be a repository’s friend with the rad seed command. This is Radicle’s version of a ⭐ star or 👍 like, where you show a repository some real love and care by sharing it with others on the network.

This works similarly to cloning, where the RID is provided, the main difference is that it doesn’t create a working copy.

$ rad seed rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
✓ Seeding policy updated for rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 with scope 'all'
✓ Fetching rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 from z6Mkk4R…SBiyXVM..

When you seed a repository, it updates your seeding policy to subscribe to updates. It fetches a copy of the repository under your local storage, and provides a replica to the network.

In the future, if you change your mind and decide that you want to contribute code to a repository you’re seeding, you can always run rad checkout to create a working copy of the repository from local storage and rad remote add to create remotes of peers that you can pull from and push to.

If on the other hand you want to stop seeding a repository, simply run rad unseed. For example:

$ rad unseed rad:z9DV738hJpCa6aQXqvQC4SjaZvsi

You can list all of your seeded repositories with the ls sub-command:

$ rad ls --seeded

If on the other hand you only want to see repositories you’ve contributed to in some way, for example by pushing a branch or commenting on an issue, simply run ls with no arguments:

$ rad ls

👾

The set of peers that are followed in the context of a seeded repository is called the scope.

By default, when cloning or seeding a repository, your node will subscribe to content from all peers. This behavior is part of the seeding policy and can be changed by passing --scope followed to the seed and clone commands, to only subscribe to content from the repository delegates plus any node that is explicitly followed via rad follow.

If a repository wasn’t initially cloned or seeded in this way, it can also be changed later via the rad seed command, e.g.

rad seed rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 --scope followed

Conversely, to reset it to the default, --scope all can be passed.

This marks the end of our initial foray into Radicle. As we move forward to Chapter 2, we’ll dissect the collaborative mechanisms at play, exploring issues and patches from the unique perspectives of both contributors and delegates. It’s here that the true kernel of code collaboration begins to unfold.

2. Collaborating the Radicle Way

Now is the time to delve into Radicle’s approach to working with social artifacts like issues and patches. In the context of version control systems, social artifacts refer to collaborative features that facilitate discussions, bug tracking, and code review. Patches, in particular, represent proposed changes to the codebase, often in response to issues or feature requests. Unlike traditional centralized forges, Radicle leverages Git’s distributed version control system to allow you to maintain local copies of your repository’s associated issues and patches, alongside the repository source tree.

If you’re familiar with Git, you know that when you commit code changes, a unique hash called a commit hash is generated to identify that snapshot in history. In Radicle, issues, patches, and their associated comments work similarly to code commits. They are stored as Git objects within the repository itself, each with its own unique Git Object ID (OID). This means that all collaborative interactions, such as creating issues, submitting patches, and commenting, are self-contained within the Git repository.

Furthermore, these objects are cryptographically signed by their author, providing an additional layer of security and trust. This allows all participants to independently verify the authenticity of the collaborative interactions, ensuring the integrity of the project’s history. By storing social artifacts within Git and leveraging cryptographic signatures, Radicle enables projects to operate independently of centralized platforms, giving users full control over their collaborative data, and the ability to work offline.

In Radicle, these collaborative social interactions are implemented using a novel system of Collaborative Objects or simply “COBs” 🌽, that are implemented using Git primitives.

Working with issues

The more we git together
Together, together
The more we git together
The happier we'll be

Before we get into issue management, you should first get to know our trusted friend, the COB 🌽. Discussing issues and patches is not natively supported by Git, so we had to extend it. We named this data type a Collaborative Object or “COB” for short. They use Conflict-Free Replicated Data Types (CRDTs) under the hood for data consistency and are one of the fundamental building blocks of the Radicle protocol.

Now, to see how COBs are leveraged through the process of collaboration, we’ll open an issue under our “paxel” user alias, in a repository where we are a contributor, but not a delegate.

We’re going to cd into our copy of the dark-star repository we are collaborating on, and open an issue with rad issue open. This will open your text editor, prompting you to enter a title and description for the issue. Radicle follows the same convention as Git commits when it comes to inputing via an editor: the first line is a summary or title of the issue, then comes the description, separated by a blank line.

$ cd dark-star
$ rad issue open

We’ll enter the following title and description in our editor:

Establish data standards for IUI Dark Star Data group

As we go out and recruit the Neboriens and other intelligences to join
the Intergalactic Union of Intelligences (IUI), we are going to have to
establish standards for Dark Star data submissions.

When we save and close the editor, the issue is displayed on the command line:

╭───────────────────────────────────────────────────────────────╮
│ Title   Establish data standards for IUI Dark Star Data group │
│ Issue   e4255cc2a0a65b543c2b5badac14bf9e0d9f409f              │
│ Author  paxel (you)                                           │
│ Status  open                                                  │
│                                                               │
│ As we go out and recruit the Neboriens and other              │
│ intelligences to join the Intergalactic Union of              │
│ Intelligences (IUI), we are going to have to establish        │
│ standards for Dark Star data submissions.                     │
╰───────────────────────────────────────────────────────────────╯

Behind the scenes, rad issue open conjures up a brand-new COB for the issue, bestowing upon it a unique identifier. This COB becomes the guardian of all the metadata and interactions associated with the issue, ready to be replicated and synced across the Radicle network.

👾

To ensure that your preferred editor is chosen when you enter rad issue open, make sure the EDITOR environment variable is set appropriately. It must be set to the full path to your preferred editor, for example:

export EDITOR=/usr/bin/vim

If you’d prefer to enter the issue title and description via the command-line without spawning your editor, you can use the --title and --description options to rad issue open.

COBs such as issues and patches can be created even when offline, since Radicle is local-first. COBs you create offline will simply synchronize with the network once you go online.

You can always see a table with all of the issues associated to a repository by running rad issue within its working directory.

$ rad issue
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ●   ID        Title                                                    Author                    │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ●   80464b3   Categorize initial dataset                               paxel    (you)            │
│ ●   e4255cc   Establish data standards for IUI Dark Star Data group    paxel    (you)            │
│ ●   3e2f653   Add anomalous data from ship 897AF                       calyx    z6Mkgom…unurCap  │
│ ●   badda04   Recruit Neboriens to join IUI and share dark star data   calyx    z6Mkgom…unurCap  │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Let’s display an issue that looks interesting, using rad issue show:

$ rad issue show 3e2f65
╭─────────────────────────────────────────────────────────────╮
│ Title   Add anomalous data from ship 897AF                  │
│ Issue   3e2f653383f0d2fe21ef4e859a25925c364c740a            │
│ Author  calyx                                               │
│ Status  open                                                │
│                                                             │
│ Before we onboard new IUI members like the Neborians, it is │
│ important we upload the data from ship 897AF. I just        │
│ reviewed the database and it seems this is missing.         │
╰─────────────────────────────────────────────────────────────╯

The issue, originally opened by Calyx, the repository’s delegate, outlines a task that aligns perfectly with our knowledge area.

👾

Full identifiers do not need to be used in most commands!

Notice that the full identifier is displayed under the Issue field. When working with COBs, it’s always possible to use the first few characters of a COB’s ID instead of the full hash, as we did above with rad issue show.

Let’s compose a comment, with rad issue comment, expressing our interest in tackling the issue.

$ rad issue comment 3e2f65 --message "I can help."
╭─────────────────────────╮
│ paxel (you) now 74faf0e │
│ I can help.             │
╰─────────────────────────╯

This time, we used the command-line --message option to provide a comment, but if you don’t specify any option, you will be prompted to enter a message via your editor, just like with rad issue open.

🧠

Since Radicle’s COB types are extensible by users, globally-unique Type IDs, such as xyz.radicle.issue and xyz.radicle.patch are used to identify them, preventing naming collisions. This reverse domain name notation not only houses Radicle’s predefined COBs but also accommodates user-defined ones.

For example, if a user or organization were to define a new COB type under their domain, it might look something like com.acme.task, for some hypothetical “task” COB under the acme.com domain. This extensibility allows for an unlimited set of new collaboration primitives to be defined by users.

If you’re interested in creating a custom COB, pop into our chatroom and we can get you started.

Assigning issues

Now, let’s enter Calyx’s point of view for a second. Calyx is the maintainer of the repository and is starting his day, gazing into a fresh terminal window, where he enters rad inbox.

$ rad inbox
╭────────────────────────────────────────────────────────────────────────────────────────────╮
│ dark-star                                                                                  │
├────────────────────────────────────────────────────────────────────────────────────────────┤
│ 004   ●   3e2f653   Add anomalous data from ship 897AF   issue   open   calyx   1 hour ago │
╰────────────────────────────────────────────────────────────────────────────────────────────╯

The Radicle inbox is like a notifications center, displaying issues and patches that are newly created or have unread comments or new activity.

👾

If you run rad inbox from a Radicle repository, only notifications belonging to this repository will be shown. You can override this by using --all to show notifications from all repositories. If the command is run outside a Radicle repository, all repositories will be displayed.

Calyx decides to display the notification in his inbox with rad inbox show, using the notification number.

$ rad inbox show 4
╭─────────────────────────────────────────────────────────────╮
│ Title   Add anomalous data from ship 897AF                  │
│ Issue   3e2f653383f0d2fe21ef4e859a25925c364c740a            │
│ Author  calyx                                               │
│ Status  open                                                │
│                                                             │
│ Before we onboard new IUI members like the Neborians, it is │
│ important we upload the data from ship 897AF. I just        │
│ reviewed the database and it seems this is missing.         │
├─────────────────────────────────────────────────────────────┤
│ paxel z6MkvZw…7aCGq3C 1 hour ago 74faf0e                    │
│ I can help.                                                 │
╰─────────────────────────────────────────────────────────────╯

He sees that we (Paxel) left a comment and we’re interested in helping with the issue, and decides to assign it to us with rad issue assign.

$ rad issue assign 3e2f653 --add did:key:z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C

He adds a comment to the issue as well.

$ rad issue comment 3e2f653 --message "Great, I've assigned it to you."

👾

The assign sub-command can be used to add (--add) or remove (--delete) assignees.

As a delegate of dark-star, Calyx has the rights to assign this issue. Calyx can then use rad issue show, to confirm that the issue was correctly assigned:

$ rad issue show 3e2f65
╭─────────────────────────────────────────────────────────────╮
│ Title      Add anomalous data from ship 897AF               │
│ Issue      3e2f653383f0d2fe21ef4e859a25925c364c740a         │
│ Author     calyx                                            │
│ Assignees  paxel z6MkvZw…7aCGq3C                            │
│ Status     open                                             │
│                                                             │
│ Before we onboard new IUI members like the Neborians, it is │
│ important we upload the data from ship 897AF. I just        │
│ reviewed the database and it seems this is missing.         │
├─────────────────────────────────────────────────────────────┤
│ paxel z6MkvZw…7aCGq3C 6 hours ago 39134b6                   │
│ I can help.                                                 │
├─────────────────────────────────────────────────────────────┤
│ calyx 2 minutes ago 922fac7                                 │
│ Great, I've assigned it to you.                             │
╰─────────────────────────────────────────────────────────────╯

👾

There’s a lot more that you can do with issues, such as:

We don’t want to overwhelm you though! Check out rad issue --help for more details.

Working with patches

The collaboration voyage doesn’t stop at issues though! In the realm of code, issues typically lead to patches.

Since Calyx has assigned the above issue to us, we’ll start working on it by making the changes in a new branch, and once we’re done, we’ll open a patch. Let’s dive into it step-by-step.

First, we need to make sure we’re in our working copy of the dark-star repository. You can always check the current Radicle repository using the rad . invocation.

$ rad .
rad:z3cyotNHuasWowQ2h4yF9c3tFFdvc

Now, let’s create a new branch where we’ll add the missing data from ship 897AF, as mentioned in the issue. We happen to have this data right on our system!

$ git checkout -b anomalous-data-897af
$ cp /var/run/897af.log data/897af.log
$ git add data/

Finally, we commit our changes to the branch.

$ git commit -m "Add anomalous data from ship 897AF"

Now that our changes are committed, we’re ready to open a patch. To do so, we simply push our branch to refs/patches, on rad remote.

$ git push rad HEAD:refs/patches

Radicle will then open your editor, where the patch title can be edited and a description can be added. Just like for issues, the first line of text is the patch title, while the subsequent lines make up the description.

👾

In the above command, HEAD:refs/patches is a refspec, where HEAD is a reference to the last commit in the currently checked-out branch, and refs/patches is a (magic) reference on the remote side. But you don’t have to use HEAD; any local branch will do. Even an arbitrary commit hash can be used to open a patch!

Once we’ve entered the details describing the patch, we save and exit our editor. This pushes our commit to the rad remote, opening the patch.

✓ Patch e5f0a5a5adaa33c3b931235967e4930ece9bb617 opened
✓ Synced with 8 node(s)

To rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C
* [new reference]   HEAD -> refs/patches

In the above Git output, we see our patch’s identifier displayed:

e5f0a5a5adaa33c3b931235967e4930ece9bb617

Patches are a type of COB 🌽, just like issues, and this is the SHA-1 hash that uniquely identifies this patch.

👾

If you want to avoid remembering the git push rad HEAD:refs/patches command, you can add an alias to your local (or global) Git configuration:

$ git config alias.patch 'push rad HEAD:refs/patches'

This allows you to create a patch by simply running git patch. Use the --global option with git config to add the alias to your global Git config.

🧠

Radicle’s patch workflow is implemented using the git-remote-rad helper program developed for Radicle. This program is called by Git when it detects a push to any rad:// URL, such as the one under our rad remote.

In addition to opening a patch, the above push command creates a new remote tracking branch and makes it the upstream of our anomalous-data-897af branch. Any subsequent push from this original branch will now update the patch, synchronizing our local changes with the network.

🧠

The name of the remote patch branch is always rad/patches/ followed by the patch identifier. For example:

rad/patches/e5f0a5a5adaa33c3b931235967e4930ece9bb617

We can take a look at remote tracking branches using git branch --remotes to understand this better.

$ git branch --remotes
  calyx@z6Mkgom1bTxdh9fMFxFNXFMw3SbXnma6NARdsfcFuunurCap/main
  rad/main
  rad/patches/e5f0a5a5adaa33c3b931235967e4930ece9bb617

Here we see three branches: the main branch of Calyx (calyx@z6Mk..), our own main branch (rad/main), and the branch associated with our recent patch (rad/patches/e5f0a..).

We can also check the commit hashes associated with the rad remote references:

$ git ls-remote rad
e81db74197f48cc5198d9c03cceaec955af82abf  refs/heads/main
80b8d420658834afc444a13c03f0c3ff6875a71c  refs/heads/patches/e5f0a5a5adaa33c3b931235967e4930ece9bb617

Since the rad remote is associated with our published copy of the repository, these are the branch heads that are publicly accessible on the network.

Updating patches

So let’s say we’d like to update the patch we just submitted. We found some formatting issues with the log file we included and we’d like to correct it and update the patch.

First, let’s dive into our editor and fix the formatting:

$ vi data/897af.log

Then, we commit the changes and push.

$ git commit --amend
$ git push --force

To update the patch, we simply use git push. Since we amended the original commit, we have to use the --force option when pushing. This is common practice when a patch has been reworked.

As with opening a patch, this opens an editor to enter a reason for updating the patch. Leave it blank if you’d like to skip that.

✓ Patch e5f0a5a updated to revision 4d0c1156f8ac7af2297d1314cd7556185cd16ae4
✓ Synced with 6 node(s)

To rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C
 + b766431...80b8d42 anomalous-data-897af -> patches/e5f0a5a5adaa33c3b931235967e4930ece9bb617 (forced update)

We see that the change that was pushed is represented by a new identifier:

4d0c1156f8ac7af2297d1314cd7556185cd16ae4

This identifier represents the current version of the patch. Patch versions, called revisions in Radicle, are immutable. Therefore, each update to a patch creates a new revision, and patch updates are non-destructive, even when you force-push.

👾

If the branch upstream is not set to the patch reference, eg. rad/patches/e5f0a5a.., you can set it with rad patch set e5f0a5a.

As with issues, we can display the patch using the show sub-command:

$ rad patch show e5f0a5a
╭────────────────────────────────────────────────────────────────────────────────╮
│ Title     Add anomalous data from ship 897AF                                   │
│ Patch     e5f0a5a5adaa33c3b931235967e4930ece9bb617                             │
│ Author    paxel (you)                                                          │
│ Head      80b8d420658834afc444a13c03f0c3ff6875a71c                             │
│ Branches  anomalous-data-897af                                                 │
│ Commits   ahead 1, behind 0                                                    │
│ Status    open                                                                 │
│                                                                                │
│ Data from ship 897AF.                                                          │
├────────────────────────────────────────────────────────────────────────────────┤
│ 80b8d42 Add anomalous data from ship 897AF                                     │
├────────────────────────────────────────────────────────────────────────────────┤
│ ● opened by paxel (you) (b766431) 58 minutes ago                               │
│ ↑ updated to 4d0c1156f8ac7af2297d1314cd7556185cd16ae4 (80b8d42) 44 minutes ago │
╰────────────────────────────────────────────────────────────────────────────────╯

Notice at the bottom that there is a timeline, with the original patch head (b766431) and the updated head (80b8d42) after a new revisions (4d0c115) was created via a patch update.

Revisions are much more than changesets; they can carry useful metadata such as reviews, comments, and a top-level description that spans all the commits under it. That’s why you see two identifiers for the revision, the first one is the hash of the revision, and the second is the hash of the code itself (eg. your branch head).

🧠

All patches start with an initial revision. This revision has the same identifier as the patch itself, ie. e5f0a5a5adaa33c3b931235967e4930ece9bb617 in the example above.

Merging patches into the canonical

Now that we’ve opened a patch, let’s switch perspectives again and return to Calyx-land. Calyx will want to checkout the patch, review it, possibly make changes and run tests, and then merge it. Let’s walk through this process together.

First, within the repository, Calyx runs rad patch and notices there is a new patch.

$ rad patch
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ●  ID       Title                                Author           Head     +       -   Updated    │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ●  e5f0a5a  Add anomalous data from ship 897AF   z6MkvZw…7aCGq3C  80b8d42  +50382  -0  1 hour ago │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯

Calyx then uses the checkout sub-command of patch to switch to the patch:

$ rad patch checkout e5f0a5a
✓ Switched to branch patch/e5f0a5a at revision 4d0c1156f8ac7af2297d1314cd7556185cd16ae4
✓ Branch patch/e5f0a5a setup to track rad/patches/e5f0a5a5adaa33c3b931235967e4930ece9bb617

When collaborating with peers, it’s often useful to be able to checkout a patch in its own branch. With a patch checkout, you can browse the code, run tests and even propose your own revision to the patch.

The checkout sub-command switches to a new local branch which has the patch checked out at a specific revision. By default, the latest of the patch author’s revisions is chosen, but you can specify a different revision using the --revision option. See rad patch --help for more information.

👾

If you’d just like to view the patch changes without checking out the code, use the diff command!

rad patch diff e5f0a5a

It’s a rare occasion that someone submits a perfect patch that can be merged as-is. For example, in some cases a maintainer may want to rebase the patch’s commits on top of the main branch, which could have moved since the patch was proposed.

Calyx will do just that by running the following command from the patch checkout:

$ git rebase main

This replays the patch’s commits on top of the current main branch. All that’s left to do is to update the patch itself, with a new revision:

$ git push -o patch.message="Rebased!" --force
✓ Patch e5f0a5a updated to revision 1e0596d917725c447106b1efc900bebf4b95a810
✓ Synced with 12 node(s)

To rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6Mkgom1bTxdh9fMFxFNXFMw3SbXnma6NARdsfcFuunurCap
* [new branch]      patch/e5f0a5a -> patches/e5f0a5a5adaa33c3b931235967e4930ece9bb617

Although Calyx isn’t the author of the patch he can propose a new revision. In fact, anyone can do that, and you will see their revision if you follow them!

Since Calyx did a rebase, the --force flag was required. Notice also the -o patch.message option; this allows Calyx to directly provide a comment related to this revision, bypassing his editor.

🧠

Git has something called “push options”, these are options that are passed to the remote helper and/or server when executing a push, and can be specified via -o <string> or --push-option=<string>. Radicle as well as other forges use these to configure a push.

Radicle supports other options such as:

See man rad-patch for the full list of options.

If we look at the patch now, we’ll see the new revision Calyx just pushed:

$ rad patch show e5f0a5a
...
│ ● opened by z6MkvZw…7aCGq3C (b766431) 2 hours ago                           │
│ ↑ updated to 4d0c1156f8ac7af2297d1314cd7556185cd16ae4 (80b8d42) 2 hours ago │
│ * revised by calyx in 1e0596d (25f8515) 3 minutes ago                       │
╰─────────────────────────────────────────────────────────────────────────────╯

Here we see that Calyx’s changes are associated to the revision identifier starting with 1e0596d and to the commit hash 25f8515.

To merge this latest revision, Calyx uses regular Git commands. When Radicle detects that a patch’s revision was merged into the main branch, the associated patch is marked as merged.

First Calyx merges the patch’s changeset into the main branch, by merging the patch branch he is on:

$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'rad/main'.
$ git merge patch/e5f0a5a
Updating e81db74..25f8515
Fast-forward
data/897af.log | 50382 ++++++++++
1 file changed, 50382 insertions(+)
create mode 100644 data/897af.log

Then he pushes main to the rad remote.

$ git push rad main
✓ Patch e5f0a5a5adaa33c3b931235967e4930ece9bb617 merged at revision 1e0596d
✓ Canonical head updated to 25f851529d68180a780cfafc15a48b89208ba8c4
✓ Synced with 7 node(s)

To rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6Mkgom1bTxdh9fMFxFNXFMw3SbXnma6NARdsfcFuunurCap
  e81db74..25f8515  main -> main

This marks the patch as merged, as seen in the output, updates the repository’s canonical or “authoritative” state to 25f851, and synchronizes the changes with the network.

The patch no longer shows up under rad patch:

$ rad patch
Nothing to show.

We can verify that it is merged via rad patch show e5f0a5a, or rad patch --merged, which shows all merged patches.

👾

Patch commands work similar to issues. You can:

For more details on how to work with patches, read the manual with man rad-patch.

From remote viewing to adding remotes

Whenever you init or clone a repository, your node will follow all peers for that repository, which means that everyone’s patches and issues will be synchronized to your device.

You can change this behavior by specifying a different scope when cloning or initializing a repository, with the --scope option. For example, --scope followed will only synchronize changes by peers you explicitly follow with the rad follow command. The default is --scope all.

But if a peer becomes a regular collaborator, it would be useful to add them as a Git remote to track their changes and checkout their branches, even if they haven’t been submitted as patches. The rad remote family of commands can help you achieve this.

Calyx decides to do this, and adds us (Paxel) as a remote:

rad remote add z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C --name paxel

This allows Calyx to keep up with our changes by creating a remote tracking branch in his working copy, that has our main branch as its upstream.

$ git remote -v
paxel   rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C (fetch)
paxel   rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C (push)
rad     rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc (fetch)
rad     rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6Mkgom1bTxdh9fMFxFNXFMw3SbXnma6NARdsfcFuunurCap (push)

In the above output, we see the names of the remotes on the left. We see that the paxel remote points to a different namespace than the rad remote, which points to Calyx’s namespace. Remotes can also be viewed using rad remote:

$ rad remote
paxel z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C (fetch)
rad   (canonical upstream)                             (fetch)
rad   z6Mkgom1bTxdh9fMFxFNXFMw3SbXnma6NARdsfcFuunurCap (push)

Now that a remote is setup, Calyx can fetch our branches with git fetch:

$ git fetch paxel
  From rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc/z6MkvZwzK64f3GuDcAs6dEcje89ddfHkBjS1v9Dkh7aCGq3C
    * [new branch]      dark-star-secrets       -> paxel/dark-star-secrets

Oh darn, what has he found! If you don’t want a branch to be published on the Radicle network, avoid pushing it to your rad remote!

👾

You can always use rad remote list --untracked to find peers that you are following but are not tracking in your working copy.

Synchronizing the canonical

Now that Calyx has updated the repository’s main branch by merging our patch, all we need to do is pull those changes ourselves, to have the most up to date state.

We’ll start by switching back to our main branch with git checkout main, and then we’ll initiate a regular pull:

$ git pull
Fetching rad
From rad://z3cyotNHuasWowQ2h4yF9c3tFFdvc
  e81db74..25f8515  main       -> rad/main
Updating e81db74..25f8515
Fast-forward
...

🧠

The git pull command is really just a git fetch followed by a git merge. If you’re comfortable with Git, it can be a good idea to first run the fetch, see which remote-tracking branches were updated, and decide what to merge yourself.

There we go, our working copy is now up to date with Calyx’s.

If you get Already up to date., it could mean that the changes pushed by Calyx haven’t reached your node yet. In that case, it’s a good idea to ask your node to fetch changes from the network:

$ rad sync --fetch
✓ Fetching rad:z3cyotNHuasWowQ2h4yF9c3tFFdvc from z6MkrLM…ocNYPm7..
✓ Fetching rad:z3cyotNHuasWowQ2h4yF9c3tFFdvc from z6Mkg5Z…4Q8qCHw..
✓ Fetched repository from 2 seed(s)

After the sync command succeeds, try to git pull again.

And, that’s a wrap! You now know the most relevant nitty gritty details of how to leverage Radicle for code collaboration.

One thing to keep in mind is that Radicle doesn’t have advanced code review features yet for patches, but that is coming in a near-term release.