Create patch

Rad-Related: 08844cf184
Rad-Related: e130b4dc06
Rad-Resource: 0f44f78b78
This commit is contained in:
derick
2024-09-14 15:55:30 -07:00
481 changed files with 3 additions and 95755 deletions
-4
View File
@@ -1,4 +0,0 @@
[advisories]
ignore = [
"RUSTSEC-2023-0071", # issue bd8af7a5e3fd02c0963d7550a5cc2cab00950775
]
-4
View File
@@ -1,4 +0,0 @@
# We want the tests to stop running if they are running
# longer than 1min
[profile.default]
slow-timeout = { period = "30s", terminate-after = 2, grace-period = "10s" }
-4
View File
@@ -1,4 +0,0 @@
target
scripts
debian
systemd
-2
View File
@@ -1,2 +0,0 @@
RADICLE_DOMAIN=seed.radicle.xyz
RADICLE_NODE_OPTIONS=--force
-1
View File
@@ -1 +0,0 @@
use flake
-3
View File
@@ -1,3 +0,0 @@
/target
/radicle-cli/target
/build/artifacts
-11
View File
@@ -1,11 +0,0 @@
cloudhead ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL460KIEccS4881p7PPpiiQBsxF+H5tgC6De6crw9rbU
adaszko ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM7gBkM8PwWFe5tiJkCXdYvrYSKapm8MSNVzjJWBy7DL
fintohaps ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOl44zVBYvu1Uhmq9nshhic4r1Moz5fdNOoWTtdJrUwd
erikli ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBrJyJTwj/xG7F7qY0HDFXbb8A+xNNH8eILQ8hlvKW7/
slack-coder ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDJhzdajJ/SzvIlpywXRiXU5K3oxNjCoDawwIiSAJrbU
sebastinez ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw+wCPZUh8OndhgavJMSZluorVvzJjz22PH81XrkvIu
zlatan ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPE9CnXiiTVCbXGQJSgZuboN4aPzNw4klqp/6/+djcD5
rudolfs ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPueml1FxzjvwbD7vRZfwoaoyuxLy0L+WLBwSNiVoJe5
dave ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEyzfcjfYYBnTLWEZYjsHUW1Pczg6HN0BHMWmoV5dg96
canndrew ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICGRGjNA2OMOCbrEKIIO7cUvArmryX18XQB2AH+czSTb
yorgos ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILc7h6NDgUUORlg/M9n9jq5EK5o76PKtyWBDEAc/ogrs
-9
View File
@@ -1,9 +0,0 @@
shell: |
cargo --version
rustc --version
cargo fmt --check
cargo clippy --all-targets --workspace -- --deny clippy::all
cargo build --all-targets --workspace
cargo doc --workspace
cargo test --workspace --no-fail-fast
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"target":"delegates","title":"signals: Use `signals_receipts` crate instead","type":"edit"}
-3
View File
@@ -1,3 +0,0 @@
# ARCHITECTURE
See the [Protocol Guide](https://radicle.xyz/guides/protocol).
-236
View File
@@ -1,236 +0,0 @@
# CONTRIBUTING
Contributions are very welcome. When contributing code, please follow these
simple guidelines.
* Follow the coding guidelines when proposing code changes ([see below](#code-style)).
* Write properly formatted git commits messages ([see below](#writing-git-commit-messages)).
* Read the DCO and if you are contributing a significant amount of code, make sure your commits are signed off, using `git commit -s`.
* Contribute code via a Radicle Patch, by following the [guide][].
* If it's a feature addition or major change, check with the maintainers first
before submitting a patch. We wouldn't want you to waste your time!
* If you need help or would like to discuss your changes, come to our community chat on [Zulip][zulip].
[guide]: https://radicle.xyz/guides/user#working-with-patches
[zulip]: https://radicle.zulipchat.com
## Submitting patches
Patch formatting follows the same rules as commit formatting. See below.
## Linting & formatting
Always check your code with the linter (`clippy`), by running:
$ cargo clippy --workspace --tests
And make sure your code is formatted with, using:
$ cargo fmt
Finally, ensure there is no trailing whitespace anywhere.
## Running tests
Make sure all tests are passing with:
$ cargo test --workspace
## Checking the docs
If you make documentation changes, you may want to check whether there are any
warnings or errors:
$ cargo doc --workspace --all-features
## Code style
The following code guidelines will help make code review smoother.
### Use of `unwrap` and `expect`
Use `unwrap` only in either of three circumstances:
1. Based on manual static analysis, you've concluded that it's impossible for
the code to panic; so unwrapping is *safe*. An example would be:
let list = vec![a, b, c];
let first = list.first().unwrap();
2. The panic caused by `unwrap` would indicate a bug in the software, and it
would be impossible to continue in that case.
3. The `unwrap` is part of test code, ie. `cfg!(test)` is `true`.
In the first and second case, document `unwrap` call sites with a comment prefixed
with `SAFETY:` that explains why it's safe to unwrap, eg.
// SAFETY: Node IDs are valid ref strings.
let r = RefString::try_from(node.to_string()).unwrap();
Use `expect` only if the function expects certain invariants that were not met,
either due to bad inputs, or a problem with the environment; and include the
expectation in the message. For example:
logger::init(log::Level::Debug)
.expect("logger must only be initialized once");
### Module imports
Modules are declared at the top of the file, before the imports. Public modules
are separated from private modules with a blank line:
mod git;
mod storage;
pub mod refs;
use std::time;
use std::process;
...
Imports are organized in groups, from least specific to more specific:
use std::collections::HashMap; // First, `std` imports.
use std::process;
use std::time;
use git_ref_format as format; // Then, external dependencies.
use once_cell::sync::Lazy;
use crate::crypto::PublicKey; // Finally, local crate imports.
use crate::storage::refs::Refs;
use crate::storage::RemoteId;
### Variable naming
Use short 1-letter names when the variable scope is only a few lines, or the context is
obvious, eg.
if let Some(e) = result.err() {
...
}
Use 1-word names for function parameters or variables that have larger scopes:
pub fn commit(repo: &Repository, sig: &Signature) -> Result<Commit, Error> {
...
}
Use the most descriptive names for globals:
pub const KEEP_ALIVE_DELTA: LocalDuration = LocalDuration::from_secs(30);
### Function naming
Stay concise. Use the function doc comment to describe
what the function does, not the name. Keep in mind functions are in the
context of the parent module and/or object and repeating that would be
redundant.
### Logging
When writing log statements, always include a `target` and include enough
context in the log message that it is useful on its own, eg.
debug!(target: "service", "Routing table updated for {rid} with seed {nid}");
Check the file you are working on for what the `target` name should be; most
logs should be at the *debug* level.
## Dependencies
Before adding any code dependencies, check with the maintainers if this is okay.
In general, we try not to add external dependencies unless it's necessary.
Dependencies increase counter-party risk, build-time, attack surface, and
make code harder to audit.
## Documentation
Public types and functions should be documented. Modules *may* be documented,
if you see the need.
Code comments should usually be full english sentences, and add missing context
for the reader:
// Ensure that our inventory is recorded in our routing table, and we are tracking
// all of it. It can happen that inventory is not properly tracked if for eg. the
// user creates a new repository while the node is stopped.
for rid in self.storage.inventory()? {
...
## Proposing changes
When proposing changes via a patch:
* Isolate changes in separate commits to make the review process easier.
* Don't make unrelated changes, unless it happens to be an obvious improvement to
code you are touching anyway ("boyscout rule").
* Rebase on `master` when needed.
* Keep your changesets small, specific and uncontroversial, so that they can be
merged more quickly.
* If the change is substantial or requires re-architecting certain parts of the
codebase, write a proposal in English first, and get consensus on that before
proposing the code changes.
**Preparing commits**
1. Each commit in your patch must pass all the tests, lints and checks. This is
so that they can be built into binaries and to make git bisecting possible.
2. Do not include any commits that are fixes or refactorings of previous patch
commits. These should be squashed to the minimal diff required to make the
change, unless it's helpful to make a large change over multiple commits,
while still respecting (1). Do not include `fixup!` commits either.
3. A commit *may* include a category prefix such as `cli:` or `node:` if it
mainly concerns a certain area of the codebase. For example. These prefixes
should usually be the name of the crate, minus any common prefix. Eg.
`cli:`, and *not* `radicle-cli:`. For documentation, you can use `docs:`,
and for CI-related files, you can use `ci:`.
To help with the above, use `git commit --amend` and `git rebase -i`. You can
also interactively construct a commit from a working tree using `git add -p`.
## Writing commit messages
A properly formed git commit subject line should always be able to complete the
following sentence:
If applied, this commit will _____
In addition, it should be capitalized and *must not* include a period.
For example, the following message is well formed:
Add support for .gif files
While these ones are **not**: `Adding support for .gif files`,
`Added support for .gif files`, `add support for .gif files`.
When it comes to formatting, here's a model git commit message[1]:
Capitalized, short (50 chars or less) summary
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body. The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase can get confused if you run the
two together.
Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug." This convention matches up with commit messages generated
by commands like git merge and git revert.
Further paragraphs come after blank lines.
- Bullet points are okay, too.
- Typically a hyphen or asterisk is used for the bullet, followed by a
single space, with blank lines in between, but conventions vary here.
- Use a hanging indent.
---
[1]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
Generated
-3273
View File
File diff suppressed because it is too large Load Diff
-41
View File
@@ -1,41 +0,0 @@
[workspace]
package.version = "0.9.0"
members = [
"radicle",
"radicle-cob",
"radicle-cli",
"radicle-cli-test",
"radicle-crdt",
"radicle-crypto",
"radicle-dag",
"radicle-fetch",
"radicle-node",
"radicle-remote-helper",
"radicle-ssh",
"radicle-tools",
"radicle-signals",
"radicle-systemd",
]
default-members = [
"radicle",
"radicle-cli",
"radicle-cob",
"radicle-crdt",
"radicle-crypto",
"radicle-node",
"radicle-ssh",
"radicle-remote-helper",
"radicle-term",
"radicle-signals",
"radicle-systemd",
]
resolver = "2"
[profile.container]
inherits = "release"
debug = true
incremental = false
[workspace.lints]
clippy.type_complexity = "allow"
clippy.enum_variant_names = "allow"
-28
View File
@@ -1,28 +0,0 @@
Developer's Certificate of Origin 1.1
Copyright © 2004, 2006 The Linux Foundation and its contributors.
---
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
-178
View File
@@ -1,178 +0,0 @@
# HACKING
Welcome to the Radicle "Heartwood" hacking guide!
We appreciate your interest in contributing to the Radicle project. If you come across
a bug or a missing feature, please feel free to submit a patch. This guide is meant as
an introduction to the codebase, on how to debug issues, write tests and navigate the
repository.
Please make sure to read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting code,
and follow the included guidelines. To download a development version of Radicle,
see the [README.md](README.md).
For an architectural overview of Heartwood, see [ARCHITECTURE.md](ARCHITECTURE.md).
---
The repository is structured in *crates*, as follows:
* `radicle`: The Radicle standard library that contains shared libraries used across the project.
* `radicle-cli`: the Radicle command-line interface (`rad`).
* `radicle-cli-test`: The Radicle CLI testing framework, for writing documentation tests.
* `radicle-cob`: Radicle Collaborative Objects (COBs). Provides a way of creating and traversing edit histories.
* `radicle-crdt`: Conflict-free replicated datatypes (CRDTs) used for things like discussions and patches.
* `radicle-crypto`: A wrapper around Ed25519 cryptographic signing primitives.
* `radicle-dag`: A simple directed acyclic graph implementation used by `radicle-cob`.
* `radicle-node`: The radicle peer-to-peer daemon that enables users to connect to the network and share code.
* `radicle-remote-helper`: A Git remote helper for `rad://` remotes.
* `radicle-ssh`: OpenSSH functionality, including a library used to interface with `ssh-agent`.
* `radicle-term`: A generic terminal library used by the Radicle CLI.
* `radicle-tools`: Tools used to aid in the development of Radicle.
## Running in debug mode
To run the services or the CLI in debug mode, use `cargo run -p <package>`.
For example, the equivalent of `rad auth` in debug mode would be:
$ cargo run -p radicle-cli --bin rad -- auth
Arguments after the `--` are passed directly to the `rad` executable.
This is useful if you are running multiple nodes on the same machine. You can also
specify different listen addresses for the peer-to-peer protocol using `--listen`.
To view all options, run `cargo run -p radicle-node -- --help`.
You may want to set the appropriate environment variables before running these commands
to prevent them from interfering with an existing installation of radicle. See the
following section on environment variables.
## Running in isolation
When testing things, if you want to run one or more nodes in isolation from the
public Radicle network of nodes:
* In the node `config.json` file:
- set `preferredSeeds` to `[]`
- set `node.connect` to `[]`
- set `node.network` to `test`
- set `node.peers.type` to `static`
This allows you to make any changes to the repositories on your nodes without
having them replicated to outside your nodes. This would be good to do for any
experiments you make that might be burdensome for other nodes, such as creating
very large repositories.
## Environment variables
When developing radicle, some environment variables may be used to make the
development environment more friendly.
**`RAD_HOME`**
Set this to a path on your file system where you'd like radicle to store keys
and repositories. Typically you'll want to set this to a temporary folder, eg.
`/tmp/radicle`, that can be safely deleted. If set, all radicle data will be
stored within this folder.
**`RAD_KEYGEN_SEED`**
Set this to a 32-byte hexadecimal string to generate deterministic Node IDs
when creating new profiles. For example, integration tests use the following
setting:
RAD_KEYGEN_SEED=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
**`RAD_PASSPHRASE`**
Set this to the passphrase chosen during profile initialization (`rad auth`) to
skip the passphrase prompt. It's recommended to set this while developing to
avoid storing development keys with `ssh-agent`.
## Logging
Logging for `radicle-node` is turned on by default. Check the respective
`--help` output to set the log level.
## Writing tests
### Documentation tests
When implementing changes to the CLI, or adding a new sub-command, it's a good
idea to add a documentation test. You can find examples of these in
`radicle-cli/examples`.
Each documentation test must be accompanied by a regular unit test. These are
located in `radicle-cli/tests/commands.rs`. To keep tests deterministic,
environment variables are used. If your document test output is changing on
each test run, make sure to account for any variability in the test environment
(clocks, RNGs, etc.).
### Node service logic tests
When testing the core service logic, eg. the gossip protocol; tests can be
added to `radicle-node/src/tests.rs`. These service-level tests simply test
inputs and outputs and do not perform any I/O.
### Node end-to-end tests
If you find the need to test the replication protocol or networking layer, it's
possible to write an end-to-end test. These tests can be found in
`radicle-node/src/tests/e2e.rs`.
## Debugging
### Repository storage
Radicle stores git repositories inside `$RAD_HOME/storage`, which defaults to
`~/.radicle/storage` on UNIX-based operating systems. You can use standard git
tooling to inspect references and other git objects inside storage. Each radicle
repository is stored under its own folder under storage as a bare Git repository.
Once inside a repository folder, the following commands may come in handy.
`git show-ref` to show all references:
$ git show-ref
f60b291752bc38be7dfc90c4c4034de13e01a66b refs/heads/master
f60b291752bc38be7dfc90c4c4034de13e01a66b refs/namespaces/z6MkqTY5aQepDGNCrkPqzdmzveX3D4oAmyVXUDDVQaDGdyVH/refs/heads/master
805b7d0df927dcbc4d3911ab07cd497953eecbd1 refs/namespaces/z6MkqTY5aQepDGNCrkPqzdmzveX3D4oAmyVXUDDVQaDGdyVH/refs/rad/id
86136a42a69572015466bac2d974154ee76f0853 refs/namespaces/z6MkqTY5aQepDGNCrkPqzdmzveX3D4oAmyVXUDDVQaDGdyVH/refs/rad/sigrefs
5575035d8b4faf1f18c532b08516f18031dd7b28 refs/namespaces/z6MkuGSynjxM8SLhcsiEPWZgDeGLAVNXf5g7WePmc1Tri1FS/refs/heads/master
805b7d0df927dcbc4d3911ab07cd497953eecbd1 refs/namespaces/z6MkuGSynjxM8SLhcsiEPWZgDeGLAVNXf5g7WePmc1Tri1FS/refs/rad/id
89e4f0baa327595c7b2849189fc8808388a29033 refs/namespaces/z6MkuGSynjxM8SLhcsiEPWZgDeGLAVNXf5g7WePmc1Tri1FS/refs/rad/sigrefs
`git cat-file` to examine refs:
$ git cat-file -p f60b291752bc38be7dfc90c4c4034de13e01a66b
tree 1afc38724d2b89264c7b3826d40b0655a95cfab4
author cloudhead <cloudhead@anonymous.xyz> 1678097961 +0100
committer cloudhead <cloudhead@anonymous.xyz> 1678097961 +0100
gpgsig -----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgvjrQogRxxLjzzWns8+mKJAGzEX
4fm2ALoN7pyvD2ssQAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQHXhUf7QjXNlgCjDbGSG+zoyIlE4S9/d9qjvG7x9jw8J/fXDVIMkh/Lkp743g7EliM
X+88wqit9BeQoHXuxj2Ao=
-----END SSH SIGNATURE-----
Init
You can also run `git ls-remote rad` from inside a working copy to examine the
remote refs in storage.
### Connecting to your local node
The radicle node listens on a UNIX domain socket located at
`$RAD_HOME/node/control.sock`. Make sure this file is accessible and has the
required permissions for your user to read and write to it.
### Radicle keys
Radicle uses Ed25519 keys that are located in `$RAD_HOME/keys`. These keys are
encoded in the standard OpenSSH format. It's therefore possible to use standard
OpenSSH tools to interact with them, eg. `ssh-add`.
Your radicle secret key is protected with a passphrase (See: `$RAD_PASSPHRASE`).
-176
View File
@@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 The Radicle Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-68
View File
@@ -1,68 +0,0 @@
# ❤️🪵
*Radicle Heartwood Protocol & Stack*
Heartwood is the third iteration of the Radicle Protocol, a powerful
peer-to-peer code collaboration and publishing stack. The repository contains a
full implementation of Heartwood, complete with a user-friendly command-line
interface (`rad`) and network daemon (`radicle-node`).
Radicle was designed to be a secure, decentralized and powerful alternative to
code forges such as GitHub and GitLab that preserves user sovereignty
and freedom.
See the [Protocol Guide](https://radicle.xyz/guides/protocol) for an
in-depth description of how Radicle works.
## Installation
**Requirements**
* *Linux* or *Unix* based operating system.
* Git 2.34 or later
* OpenSSH 9.1 or later with `ssh-agent`
### 📀 From binaries
> Requires `curl` and `tar`.
Run the following command to install the latest binary release:
curl -sSf https://radicle.xyz/install | sh
Or visit our [download](https://radicle.xyz/download) page.
### 📦 From source
> Requires the Rust toolchain.
You can install the Radicle stack from source, by running the following
commands from inside this repository:
cargo install --path radicle-cli --force --locked --root ~/.radicle
cargo install --path radicle-node --force --locked --root ~/.radicle
cargo install --path radicle-remote-helper --force --locked --root ~/.radicle
Or directly from our seed node:
cargo install --force --locked --root ~/.radicle \
--git https://seed.radicle.xyz/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git \
radicle-cli radicle-node radicle-remote-helper
## Running
*Systemd* unit files are provided for the node under the `/systemd` folder.
They can be used as a starting point for further customization.
For running in debug mode, see [HACKING.md](HACKING.md).
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) and [HACKING.md](HACKING.md) for an
introduction to contributing to Radicle.
## License
Radicle is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details.
-60
View File
@@ -1,60 +0,0 @@
# Versioning
This document describes the versioning scheme used by the Radicle Stack, ie.
the package that includes the Radicle CLI (`rad`), Radicle Node
(`radicle-node`) and other binaries. It is not relevant to the Rust *crate*
versions which follow [Semantic Versioning][semver].
[semver]: https://semver.org/
## Format
Versioning of the Radicle Stack is based on Git tags. During the build phase,
we search for the most recent tag that starts with a `v` character, eg.
`v1.0.0`, and use that as the basis for computing the version.
If we're building code that is pointed to by that tag directly, that code will
inherit that version number, with the `v` character stripped. For example:
1.0.0
If on the other hand, the commit we are building has no version tag pointing to
it, the output of `git describe` is used as-is. This indicates a development
version that is not released and not meant to be packaged or distributed. For
example:
1.0.0-6-ga3ffe51d
Tags used for versioning are always annotated and signed, and follow the format:
"v" <major> "." <minor> "." <patch>
When the tag is parsed, we strip the `v` prefix, which results in a version
matching:
<major> "." <minor> "." <patch>
For pre-releases or release candidates, we add the `-rc` suffix, plus a number.
For example:
1.0.0-rc.2
These releases are meant to be packaged.
## Semantics
The Radicle version numbers do not follow strict rules, but these are the
guidelines we use:
1. Increment the `<major>` number when significant changes and/or improvements
are made to the Radicle Stack. This should happen rarely.
2. Increment the `<minor>` number when new features are added or existing
features are improved in a noticeable way.
3. Increment the `<patch>` number when bugs are fixed, docs are updated or
features are tweaked.
Unless clearly stated in the documentation, none of the user-facing commands
should be considered stable APIs, and may therefore break in `<minor>` or
`<major>` version updates. If command output is considered stable, and an
effort is made to maintain that stability, this will be stated in the command's
documentation.
-54
View File
@@ -1,54 +0,0 @@
use std::env;
use std::process::Command;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Set a build-time `GIT_HEAD` env var which includes the commit id;
// such that we can tell which code is running.
let hash = env::var("GIT_HEAD").unwrap_or_else(|_| {
Command::new("git")
.arg("rev-parse")
.arg("--short")
.arg("HEAD")
.output()
.ok()
.and_then(|output| {
if output.status.success() {
String::from_utf8(output.stdout).ok()
} else {
None
}
})
.unwrap_or("unknown".into())
});
let version = if let Ok(version) = env::var("RADICLE_VERSION") {
version
} else {
"pre-release".to_owned()
};
// Set a build-time `SOURCE_DATE_EPOCH` env var which includes the commit time.
let commit_time = env::var("SOURCE_DATE_EPOCH").unwrap_or_else(|_| {
Command::new("git")
.arg("log")
.arg("-1")
.arg("--pretty=%ct")
.arg("HEAD")
.output()
.ok()
.and_then(|output| {
if output.status.success() {
String::from_utf8(output.stdout).ok()
} else {
None
}
})
.unwrap_or(0.to_string())
});
println!("cargo::rustc-env=RADICLE_VERSION={version}");
println!("cargo::rustc-env=SOURCE_DATE_EPOCH={commit_time}");
println!("cargo::rustc-env=GIT_HEAD={hash}");
Ok(())
}
-107
View File
@@ -1,107 +0,0 @@
# Builds release binaries for Radicle.
FROM rust:1.77.2-alpine3.19 as builder
LABEL maintainer="Radicle Team <team@radicle.xyz>"
WORKDIR /src
COPY . .
ARG TZ
ARG LC_ALL
ARG SOURCE_DATE_EPOCH
ARG RADICLE_VERSION
ARG GIT_HEAD
# Copy cargo configuration we're going to use to specify compiler options.
RUN mkdir -p .cargo && cp build/config.toml .cargo/config.toml
# Install dependencies.
RUN apk update && apk add --no-cache git musl-dev minisign curl xz asciidoctor
# Build man pages and strip metadata. Removes all comments, since they include
# non-reproducible information, such as version numbers.
RUN asciidoctor --doctype manpage --backend manpage --destination-dir . *.1.adoc && \
find . -maxdepth 1 -type f -name '*.1' -exec sed -i '/^.\\\"/d' '{}' \;
# Add cargo targets.
RUN rustup target add \
x86_64-unknown-linux-musl \
aarch64-unknown-linux-musl \
x86_64-apple-darwin \
aarch64-apple-darwin
# Install dependencies for cross-compiling to macOS.
# We use Zig as the linker to perform the compilation from a Linux host.
# Zig is not yet available on Debian, so we download the official binary.
# Compilation is done via `cargo-zigbuild` which is a wrapper around `zig`.
RUN curl -sSf -o zig.tar.xz https://ziglang.org/download/0.12.0/zig-linux-x86_64-0.12.0.tar.xz && \
curl -sSf -o zig.tar.xz.minisig https://ziglang.org/download/0.12.0/zig-linux-x86_64-0.12.0.tar.xz.minisig && \
minisign -Vm zig.tar.xz -P RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U && \
xz -d -c zig.tar.xz | tar -x && \
mv zig-linux-x86_64-0.12.0/zig /usr/bin/zig && \
mv zig-linux-x86_64-0.12.0/lib /usr/lib/zig && \
cargo install cargo-zigbuild@0.18.3
# Parts of the macOS SDK are required to build Radicle, we make these available
# here. So far only `CoreFoundation` and `Security` frameworks are needed.
RUN xz -d -c build/macos-sdk-11.3.tar.xz | tar -x
# This env var is used by `cargo-zigbuild` to find the SDK.
ENV SDKROOT /src/macos-sdk-11.3
# Build binaries.
RUN cargo zigbuild --locked --release \
--target=x86_64-apple-darwin \
--target=aarch64-apple-darwin \
--target=aarch64-unknown-linux-musl \
--target=x86_64-unknown-linux-musl \
-p radicle-node \
-p radicle-remote-helper \
-p radicle-cli
# Now copy the files to a new image without all the intermediary artifacts to
# save some space.
FROM alpine:3.19 as packager
ARG RADICLE_VERSION
ARG SOURCE_DATE_EPOCH
COPY --from=builder \
/src/target/x86_64-unknown-linux-musl/release/rad \
/src/target/x86_64-unknown-linux-musl/release/git-remote-rad \
/src/target/x86_64-unknown-linux-musl/release/radicle-node \
/builds/x86_64-unknown-linux-musl/bin/
COPY --from=builder \
/src/target/aarch64-unknown-linux-musl/release/rad \
/src/target/aarch64-unknown-linux-musl/release/git-remote-rad \
/src/target/aarch64-unknown-linux-musl/release/radicle-node \
/builds/aarch64-unknown-linux-musl/bin/
COPY --from=builder \
/src/target/aarch64-apple-darwin/release/rad \
/src/target/aarch64-apple-darwin/release/git-remote-rad \
/src/target/aarch64-apple-darwin/release/radicle-node \
/builds/aarch64-apple-darwin/bin/
COPY --from=builder \
/src/target/x86_64-apple-darwin/release/rad \
/src/target/x86_64-apple-darwin/release/git-remote-rad \
/src/target/x86_64-apple-darwin/release/radicle-node \
/builds/x86_64-apple-darwin/bin/
COPY --from=builder /src/*.1 /builds/x86_64-unknown-linux-musl/man/man1/
COPY --from=builder /src/*.1 /builds/aarch64-unknown-linux-musl/man/man1/
COPY --from=builder /src/*.1 /builds/aarch64-apple-darwin/man/man1/
COPY --from=builder /src/*.1 /builds/x86_64-apple-darwin/man/man1/
# Create and compress reproducible archive.
WORKDIR /builds
RUN x86_64-unknown-linux-musl/bin/rad version --json > radicle.json
RUN apk update && apk add --no-cache tar xz
RUN find * -maxdepth 0 -type d -exec mv '{}' "radicle-$RADICLE_VERSION-{}" \; && \
find * -maxdepth 0 -type d -exec tar \
--sort=name \
--verbose \
--mtime="@$SOURCE_DATE_EPOCH" \
--owner=0 \
--group=0 \
--numeric-owner \
--format=posix \
--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime \
--mode='go+u,go-w' \
--remove-files \
--create --xz \
--file="{}.tar.xz" \
'{}' \;
-70
View File
@@ -1,70 +0,0 @@
# Builds
Radicle uses a [reproducible build][rb] pipeline to make binary verification
easier and more secure.
[rb]: https://reproducible-builds.org/
This build pipeline is designed to be run on an x86_64 machine running Linux.
The output is a set of `.tar.xz` archives containing binaries for the supported
platforms and signed by the user's Radicle key.
These binaries are statically linked to be maximally portable, and designed to
be reproducible, byte for byte.
To run the build, simply enter the following command from the repository root:
build/build
This will build all targets and place the output in `build/artifacts` with
one sub-directory per build target.
Note that it will use `git describe` to get a version number for the build.
You *must* have a commit tagged with a version in your history or the build
will fail, eg. `v1.0.0`.
When the build completes, the SHA-256 checksums of the artifacts are output.
For a given Radicle version and source tree, the same set of checksums should
always be output, no matter where or when the build is run. If they do not
match, either the build pipeline has a bug, making it non-reproducible, or one
of the machines is compromised.
Here's an example output for a development version of Radicle:
b9aa75bba175e18e05df4f6b39ec097414bbf56ccdeb4a2229b557f8b8e05404 radicle-1.0.0-rc.4-3-gb299f3b5-aarch64-apple-darwin.tar.xz
c7070806bf2d17a8a0d3b329e4d57b1e544b7b82cb58e2863074d96348a2ab0d radicle-1.0.0-rc.4-3-gb299f3b5-aarch64-unknown-linux-musl.tar.xz
1a8327854f16ea90491fb90e0c3291a63c4b2ab01742c8435faec7d370cacb79 radicle-1.0.0-rc.4-3-gb299f3b5-x86_64-apple-darwin.tar.xz
709ac67541ff0c0c570ac22ab2de9f98320e0cc2cc9b67f1909c014a2bb5bd49 radicle-1.0.0-rc.4-3-gb299f3b5-x86_64-unknown-linux-musl.tar.xz
A script is included in `build/checksums` to output these checksums after
the artifacts are built.
## Requirements
The following software is required for the build:
* `podman`
* `rad` (The Radicle CLI)
* `sha256sum`
## macOS
macOS binaries are not signed or notarized, so they have to be downloaded via
the CLI to avoid issues. A copy of a small subset of the Apple SDK is included
here to be able to cross-compile.
## Podman
We use `podman` to make the build reproducible on any machine by controlling
the build environment. We prefer `podman` to `docker` because it doesn't
require a background process to run and can be run without root access out of
the box.
The first time you run `podman`, you may have to give yourself some extra UIDs
for `podman` to use, with:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER
Then update `podman` with:
podman system migrate
-4
View File
@@ -1,4 +0,0 @@
x86_64-unknown-linux-musl
aarch64-unknown-linux-musl
x86_64-apple-darwin
aarch64-apple-darwin
-90
View File
@@ -1,90 +0,0 @@
#!/bin/sh
set -e
main() {
# Use UTC time for everything.
export TZ=UTC0
# Set minimal locale.
export LC_ALL=C
# Set source date. This is honored by `asciidoctor` and other tools.
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
if ! command -v rad > /dev/null; then
echo "fatal: rad is not installed" >&2 ; exit 1
fi
if ! command -v podman > /dev/null; then
echo "fatal: podman is not installed" >&2 ; exit 1
fi
if ! command -v sha256sum > /dev/null; then
echo "fatal: sha256sum is not installed" >&2 ; exit 1
fi
rev="$(git rev-parse --short HEAD)"
tempdir="$(mktemp -d)"
gitarchive="$tempdir/heartwood-$rev.tar.gz"
keypath="$(rad path)/keys/radicle.pub"
version="$(build/version)"
image=radicle-build-$version
if [ ! -f "$keypath" ]; then
echo "fatal: no key found at $keypath" >&2 ; exit 1
fi
# Authenticate user for signing
rad auth
echo "Building Radicle $version.."
echo "Creating archive of repository at $rev in $gitarchive.."
git archive --format tar.gz -o "$gitarchive" HEAD
echo "Building image ($image).."
podman --cgroup-manager=cgroupfs build \
--build-arg SOURCE_DATE_EPOCH \
--build-arg TZ \
--build-arg LC_ALL \
--build-arg RADICLE_VERSION=$version \
--build-arg GIT_HEAD=$rev \
--arch amd64 --tag $image -f ./build/Dockerfile - < $gitarchive
echo "Creating container (radicle-build-container).."
podman --cgroup-manager=cgroupfs create --ulimit=host --replace --name radicle-build-container $image
# Copy build artifacts to output folder.
outdir=build/artifacts
mkdir -p $outdir
podman cp --overwrite radicle-build-container:/builds/. $outdir/
for target in $(cat build/TARGETS); do
echo "Signing artifacts for $target.."
filename="radicle-$version-$target.tar.xz"
filepath="$outdir/$filename"
# Output SHA256 digest of archive.
checksum="$(cd $outdir && sha256sum $filename)"
echo "Checksum of $filepath is $(echo "$checksum" | cut -d' ' -f1)"
echo "$checksum" > $filepath.sha256
# Sign archive and verify archive.
rm -f $filepath.sig # Delete existing signature
ssh-keygen -Y sign -n file -f $keypath $filepath
ssh-keygen -Y check-novalidate -n file -s $filepath.sig < $filepath
done
# Remove build artifacts that aren't needed anymore.
rm -f $gitarchive
podman rm radicle-build-container > /dev/null
podman rmi --ignore localhost/$image
}
# Run build.
echo "Running build.."
main "$@"
# Show artifact checksums.
echo
build/checksums
echo
echo "Build successful."
-2
View File
@@ -1,2 +0,0 @@
#!/bin/sh
find build/artifacts -type f -name '*.sha256' -exec cat {} +
-13
View File
@@ -1,13 +0,0 @@
[target.x86_64-unknown-linux-musl]
rustflags = [
"-C", "codegen-units=1",
"-C", "incremental=false",
"-C", "opt-level=3",
]
[target.aarch64-unknown-linux-musl]
rustflags = [
"-C", "codegen-units=1",
"-C", "incremental=false",
"-C", "opt-level=3",
]
Binary file not shown.
-38
View File
@@ -1,38 +0,0 @@
#!/bin/sh
set -e
if [ $# -ne 1 ]; then
echo "Usage: $0 <version-number>"
exit 1
fi
version="$1"
tag="v$version"
commit="$(git rev-parse HEAD)"
signing_key=$(git config user.signingKey)
git show "$commit"
if [ "$signing_key" != "$(rad self --ssh-key)" ]; then
echo "The Git signing key does not match the output of 'rad self --ssh-key'."
exit 1
fi
printf "\n"
printf "Tag the above commit with \033[35m$tag\033[0m, using \033[35m$(rad self --did)\033[0m? [y/N] "
read confirmation
rad auth
case "$confirmation" in
[Yy]*)
if git tag --annotate --sign "$tag" -m "Release $version" "$commit"; then
echo "Tag $tag created and signed over $commit."
else
echo "Failed to create tag."
exit 1
fi ;;
*)
echo "Operation aborted."
exit 1 ;;
esac
-47
View File
@@ -1,47 +0,0 @@
#!/bin/sh
set -e
SSH_LOGIN=${SSH_LOGIN:-release}
SSH_ADDRESS=${SSH_ADDRESS:-$SSH_LOGIN@files.radicle.xyz}
SSH_KEY="$(rad path)/keys/radicle"
main() {
version="$(build/version)"
echo "Uploading Radicle $version..."
if [ -z "$version" ]; then
echo "fatal: empty version number" >&2 ; exit 1
fi
# Create remote folder.
ssh -i $SSH_KEY $SSH_ADDRESS mkdir -p /mnt/radicle/files/releases/$version
# Copy files over.
scp -i $SSH_KEY build/artifacts/radicle-$version* $SSH_ADDRESS:/mnt/radicle/files/releases/$version
scp -i $SSH_KEY build/artifacts/radicle.json $SSH_ADDRESS:/mnt/radicle/files/releases/$version
for target in $(cat build/TARGETS); do
archive=/mnt/radicle/files/releases/$version/radicle-$version-$target.tar.xz
symlink=/mnt/radicle/files/releases/$version/radicle-$target.tar.xz
echo "Creating symlinks for $target.."
ssh -i $SSH_KEY $SSH_ADDRESS ln -snf $archive $symlink
ssh -i $SSH_KEY $SSH_ADDRESS ln -snf $archive.sig $symlink.sig
ssh -i $SSH_KEY $SSH_ADDRESS ln -snf $archive.sha256 $symlink.sha256
done
if git describe --exact-match --match='v*' 2>/dev/null; then
echo "Creating 'latest' symlink.."
ssh -i $SSH_KEY $SSH_ADDRESS ln -snf /mnt/radicle/files/releases/$version /mnt/radicle/files/releases/latest
else
echo "Skipping 'latest' symlink creation for development build."
fi
echo "Pushing tags.."
git push rad --tags
echo "Done."
}
main "$@"
-9
View File
@@ -1,9 +0,0 @@
#!/bin/sh
if ! version="$(git describe --match='v*' --candidates=1 2>/dev/null)"; then
echo "fatal: no version tag found by 'git describe'" >&2 ; exit 1
fi
# Remove `v` prefix from version.
version=${version#v}
echo $version
-34
View File
@@ -1,34 +0,0 @@
#!/bin/bash
set -euo pipefail
rm -f ../radicle*_*
git clean -fdx
# Get name and version of source package.
name="$(dpkg-parsechangelog -SSource)"
version="$(dpkg-parsechangelog -SVersion)"
# Get upstream version: everything before the last dash.
uv="$(echo "$version" | sed 's/-[^-]*$//')"
orig="${name}_${uv}.orig.tar.xz"
# Create a tar ball without the Debian packaging.
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
git archive HEAD | tar -C "$tmp" -xf -
rm -rf "$tmp/debian"
tar -C "$tmp" -cf - . | xz >"../$orig"
# Actually build the package, without signing the .changes file.
dpkg-buildpackage -us -uc
# Dump some information to make it easier to visually verify
# everything looks OK. Also, test the package with the lintian tool.
arch="$(dpkg --print-architecture)"
deb="../${name}_${version}_${arch}.deb"
changes="../${name}_${version}_${arch}.changes"
ls -l "$deb"
dpkg -c "$deb"
lintian -i "$changes"
-1
View File
@@ -1 +0,0 @@
/* This file needs to exist, but can be empty. */
-5
View File
@@ -1,5 +0,0 @@
radicle (0.8.0-1) unstable; urgency=medium
* New release.
-- Lars Wirzenius <liw@liw.fi> Mon, 25 Sep 2023 08:34:08 +0300
-2
View File
@@ -1,2 +0,0 @@
10
-17
View File
@@ -1,17 +0,0 @@
Source: radicle
Maintainer: The Radicle Team <team@radicle.xyz>
Uploaders: Lars Wirzenius <liw@liw.fi>
Section: vcs
Priority: optional
Standards-Version: 4.6.2
Build-Depends: debhelper (>= 10~),
asciidoctor
Homepage: https://radicle.xyz/
Package: radicle
Architecture: any
Depends: ${misc:Depends}, ${shlibs:Depends}, git
Description: peer-to-peer, distributed git hosting
Radicle is a secure, decentralized and powerful alternative to code
forges such as GitHub and GitLab that preserves user sovereignty and
freedom.
-10
View File
@@ -1,10 +0,0 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: heartwood
Upstream-Contact: https://radicle.xyz/
Source: https://app.radicle.xyz/seeds/seed.radicle.xyz
Files: *
Copyright: 2019-2021, The Radicle Foundation
License: Apache-2 or Expat
License texts omitted, see LICENSE-APACHE and LICENSE-MIT in the
source tree.
-1
View File
@@ -1 +0,0 @@
*.1 usr/share/man/man1
-3
View File
@@ -1,3 +0,0 @@
radicle binary: initial-upload-closes-no-bugs
radicle binary: no-manual-page
radicle binary: unstripped-binary-or-object
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/make -f
DEB_BUILD_OPTIONS=nostrip
%:
dh $@
override_dh_auto_build:
scripts/build-man-pages.sh . *.adoc
override_dh_auto_install:
cargo install --locked --path=radicle-cli --root=debian/radicle
cargo install --locked --path=radicle-node --root=debian/radicle
cargo install --locked --path=radicle-remote-helper --root=debian/radicle
rm -f debian/*/.crates*.*
override_dh_auto_test:
echo tests are disabled, for now
-1
View File
@@ -1 +0,0 @@
3.0 (quilt)
-1
View File
@@ -1 +0,0 @@
radicle source: debian-rules-sets-DEB_BUILD_OPTIONS
-126
View File
@@ -1,126 +0,0 @@
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced
# The graph table configures how the dependency graph is constructed and thus
# which crates the checks are performed against
[graph]
targets = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# The output table provides options for how/if diagnostics are outputted
[output]
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
[advisories]
# Opt into new defaults since cargo deny 0.14.12 (2024-02-23)
version = 2
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url(s) of the advisory databases to use
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for crates that have been yanked from their source registry.
yanked = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = []
# This section is considered when running `cargo deny check licenses`
[licenses]
# Opt into new defaults since cargo deny 0.14.12 (2024-02-23)
version = 2
# List of explicitly allowed licenses
allow = [
"MIT",
"Apache-2.0",
"Unicode-DFS-2016",
"BSD-3-Clause",
"MPL-2.0"
]
# The confidence threshold for detecting a license from license text.
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
{ allow = ["GPL-3.0"], name = "radicle-surf", version = "*" },
{ allow = ["GPL-3.0"], name = "radicle-std-ext", version = "*" },
{ allow = ["GPL-3.0"], name = "radicle-git-ext", version = "*" },
{ allow = ["GPL-3.0"], name = "git-ref-format-core", version = "*" },
{ allow = ["GPL-3.0"], name = "git-ref-format-macro", version = "*" },
{ allow = ["GPL-3.0"], name = "git-ref-format", version = "*" },
]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
ignore = false
registries = []
# This section is considered when running `cargo deny check bans`.
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overridden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overridden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = []
# List of crates to deny
deny = []
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = []
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = []
# This section is considered when running `cargo deny check sources`.
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
[sources.allow-org]
# 1 or more github.com organizations to allow git sources for
github = []
# 1 or more gitlab.com organizations to allow git sources for
gitlab = []
# 1 or more bitbucket.org organizations to allow git sources for
bitbucket = []
Generated
-120
View File
@@ -1,120 +0,0 @@
{
"nodes": {
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1722017365,
"narHash": "sha256-9wYR5NZIgI+qzMDlJrUzevR31fvFQRgfjlYp50Xp3Ts=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "9d024c07ee8c18609b43436bc865abf46636e250",
"type": "github"
},
"original": {
"owner": "rustsec",
"repo": "advisory-db",
"type": "github"
}
},
"crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1722907532,
"narHash": "sha256-i/j/dMZPR7uJoQPFTa6UCahLu8kMtozYuR83lIKMERM=",
"owner": "ipetkov",
"repo": "crane",
"rev": "d0c8f4ed8571c533b31c1d68e4ebc534b790cc5c",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1722940684,
"narHash": "sha256-X8JnSq0ruRWsU4PdYuxV+8W4W66F1lnCcxIZZMWzo4E=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a3103d68517c6ad262ea27c96fc4a38ad81be7a0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"advisory-db": "advisory-db",
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1722910815,
"narHash": "sha256-v6Vk/xlABhw2QzOa6xh3Jx/IvmlbKbOazFM+bDFQlWU=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "7df2ac544c203d21b63aac23bfaec7f9b919a733",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
-242
View File
@@ -1,242 +0,0 @@
{
description = "Radicle";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/release-24.05";
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
flake-utils.url = "github:numtide/flake-utils";
advisory-db = {
url = "github:rustsec/advisory-db";
flake = false;
};
};
nixConfig = {
keepOutputs = true;
};
outputs = {
self,
nixpkgs,
crane,
flake-utils,
advisory-db,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
lib = nixpkgs.lib;
pkgs = import nixpkgs {
inherit system;
overlays = [(import rust-overlay)];
};
rustToolChain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolChain;
srcFilters = path: type:
builtins.any (suffix: lib.hasSuffix suffix path) [
".sql" # schemas
".diff" # testing
".md" # testing
".adoc" # man pages
]
||
# Default filter from crane (allow .rs files)
(craneLib.filterCargoSources path type);
src = lib.cleanSourceWith {
src = ./.;
filter = srcFilters;
};
basicArgs = {
inherit src;
pname = "Heartwood";
strictDeps = true;
};
# Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI
cargoArtifacts = craneLib.buildDepsOnly basicArgs;
# Common arguments can be set here to avoid repeating them later
commonArgs =
basicArgs
// {
inherit cargoArtifacts;
nativeBuildInputs = with pkgs; [
git
# Add additional build inputs here
];
buildInputs = lib.optionals pkgs.stdenv.buildPlatform.isDarwin (with pkgs; [
darwin.apple_sdk.frameworks.Security
]);
env =
{
RADICLE_VERSION = "nix-" + (self.shortRev or self.dirtyShortRev or "unknown");
}
// (
if self ? rev || self ? dirtyRev
then {
GIT_HEAD = self.rev or self.dirtyRev;
}
else {}
);
};
in {
# Formatter
formatter = pkgs.alejandra;
# Set of checks that are run: `nix flake check`
checks = {
# Build the crate as part of `nix flake check` for convenience
inherit (self.packages.${system}) radicle;
# Run clippy (and deny all warnings) on the crate source,
# again, reusing the dependency artifacts from above.
#
# Note that this is done as a separate derivation so that
# we can block the CI if there are issues here, but not
# prevent downstream consumers from building our crate by itself.
clippy = craneLib.cargoClippy (commonArgs
// {
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
});
doc = craneLib.cargoDoc commonArgs;
deny = craneLib.cargoDeny commonArgs;
fmt = craneLib.cargoFmt basicArgs;
audit = craneLib.cargoAudit {
inherit src advisory-db;
};
# Run tests with cargo-nextest
nextest = craneLib.cargoNextest (commonArgs
// {
partitions = 1;
partitionType = "count";
nativeBuildInputs = [
# git is required so the sandbox can access it.
pkgs.git
# Ensure that `git-remote-rad` is present for testing.
self.packages.${system}.radicle-remote-helper
];
# Ensure dev is used since we rely on env variables being
# set in tests.
env.CARGO_PROFILE = "dev";
});
};
packages = let
crate = {
name,
pages ? [],
}:
craneLib.buildPackage (commonArgs
// {
inherit (craneLib.crateNameFromCargoToml {cargoToml = src + "/" + name + "/Cargo.toml";}) pname version;
cargoExtraArgs = "-p ${name}";
doCheck = false;
nativeBuildInputs = with pkgs; [asciidoctor installShellFiles];
postInstall = ''
for page in ${lib.escapeShellArgs pages}; do
asciidoctor -d manpage -b manpage $page
installManPage ''${page::-5}
done
'';
});
crates = builtins.listToAttrs (map
({name, ...} @ package: lib.nameValuePair name (crate package))
[
{
name = "radicle-cli";
pages = [
"rad.1.adoc"
"rad-id.1.adoc"
"rad-patch.1.adoc"
];
}
{
name = "radicle-remote-helper";
pages = ["git-remote-rad.1.adoc"];
}
{
name = "radicle-node";
pages = ["radicle-node.1.adoc"];
}
]);
in
crates
// rec {
default = radicle;
radicle = pkgs.buildEnv {
name = "radicle";
paths = with crates; [
radicle-cli
radicle-node
radicle-remote-helper
];
};
radicle-full = pkgs.buildEnv {
name = "radicle-full";
paths = builtins.attrValues crates;
};
};
apps.default = flake-utils.lib.mkApp {
drv = self.packages.${system}.radicle;
};
apps.radicle-full = flake-utils.lib.mkApp {
name = "rad";
drv = self.packages.${system}.radicle-full;
};
apps.rad = flake-utils.lib.mkApp {
name = "rad";
drv = self.packages.${system}.radicle-cli;
};
apps.git-remote-rad = flake-utils.lib.mkApp {
name = "git-remote-rad";
drv = self.packages.${system}.radicle-remote-helper;
};
apps.radicle-node = flake-utils.lib.mkApp {
name = "radicle-node";
drv = self.packages.${system}.radicle-node;
};
devShells.default = craneLib.devShell {
# Extra inputs can be added here; cargo and rustc are provided by default.
packages = with pkgs; [
cargo-watch
cargo-nextest
ripgrep
rust-analyzer
sqlite
];
env.RUST_SRC_PATH = "${rustToolChain}/lib/rustlib/src/rust/library";
};
});
}
-26
View File
@@ -1,26 +0,0 @@
= git-remote-rad(1)
The Radicle Team <team@radicle.xyz>
:doctype: manpage
:revnumber: 0.8.0
:revdate: 2023-10-12
:mansource: rad {revnumber}
:manmanual: Radicle CLI Manual
== Name
git-remote-rad - Git remote helper for Radicle
== Synopsis
*git-remote-rad* *
== Description
**git-remote-rad** is a **git**(1) remote helper program to allow git
to interact with the local Radicle storage. git will use this program
automatically, when using a remote repository with the "rad" protocol.
It is never invoked directly.
== SEE ALSO ==
*git-remote-helpers*(1), *rad*(1)
+1
View File
@@ -0,0 +1 @@
{"typeName":"xyz.radicle.patch","version":1}
-170
View File
@@ -1,170 +0,0 @@
= rad-id(1)
The Radicle Team <team@radicle.xyz>
:doctype: manpage
:revnumber: 1.0.0
:revdate: 2024-04-22
:mansource: rad {revnumber}
:manmanual: Radicle CLI Manual
== Name
rad-id - Manage changes to a Radicle repository's identity.
== Synopsis
*rad id* [<option>...] +
*rad id* _list_ [<option>...] +
*rad id* _update_ [--title <string>] [--description <string>] <option>... +
*rad id* _edit_ <revision-id> [--title <string>] [--description <string>] [<option>...] +
*rad id* _show_ <revision-id> [<option>...] +
*rad id* _accept_ | _reject_ <revision-id> [<option>...] +
*rad id* _redact_ <revision-id> [<option>...]
== Description
The *rad id* command is used to manage and propose changes to the identity of a
Radicle repository. Each repository has an associated identity document that
contains metadata such as the repository name, description, and delegates. The
identity document is versioned and changes to it must be signed by a quorum of
delegates.
The identity document is stored as a _Canonical JSON_ document.
== Commands
With no arguments, *rad id* defaults to the _list_ command, showing the list of
revisions to the identity of the current repository.
=== list
Lists all revisions to the identity document.
=== update
Proposes a new revision to the identity document. Revisions have a title and a
description in addition to the proposed updated identity document, just like
source code commits.
If a title and description are not provided on the command line, you will be
prompted to enter one via your text editor.
Note that if you are the repository's only delegate, proposed changes will be
automatically accepted and included into the identity document.
*--title* _<string>_::
Set the title for the new revision.
*--description* _<string>_::
Set the description for the new revision.
*--delegate* _<did>_::
Update the identity by adding a new delegate, identified by their DID.
*--rescind* _<did>_::
Update the identity by removing a delegate identified by their DID.
*--threshold* _<num>_::
Update the identity by setting the number of delegates required to accept a
revision.
*--visibility* _<private>_ | _<public>_::
Update the identity by setting the repository visibility to private or public.
*--allow* _<did>_::
Update the identity by giving a specific peer access to a private repository.
*--payload* _<id> <key> <val>_::
Update the identity by setting metadata in one of the identity payloads.
This can be used to update a repository's project name or description, for
example. To delete a field from a payload, simply set it to *null*.
*--no-confirm*::
Don't ask for confirmation before creating the revision.
=== edit
Edit an existing revision to the identity document. The revision must still be
in the "active" state. The same options as for *update* are available. Note
that this edits a proposed revision to the identity; to edit the identity
document itself, use *update*.
=== show
Show a specific revision of the identity document.
=== accept
Accept a proposed revision to the identity document. The revision must be in
the "active" state and the caller must be a delegate.
=== reject
Reject a proposed revision to the identity document. The revision must be in
the "active" state and the caller must be a delegate.
=== redact
Redact an existing revision to the identity document. The revision must not be
in the "accepted" state and the caller must be the author of the revision.
== Options
*--repo* _<rid>_::
Specify the repository to operate on. Defaults to the current repository.
*--quiet*, *-q*::
Suppress output.
*--help*::
Print help information.
== Examples
=== Adding a delegate
To add a new delegate to a repository and update the threshold, use the
*update* command:
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" \
--delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk \
--threshold 2
This will create a new revision proposing to add the delegate identified by the
given DID and set the threshold to `2`, meaning two delegates must sign off on
future identity changes.
=== Changing repository visibility
To change a repository from public to private:
$ rad id update --visibility private
Note that this will require acceptance from a quorum of delegates to take
effect.
=== Changing a repository payload
To change a repository's name and description, this is usually done through the
*xyz.radicle.project* payload:
$ rad id update --title "Update title and description" \
--description "Improve clarity" \
--payload xyz.radicle.project name '"radicle-beans"' \
--payload xyz.radicle.project description '"Tasty Radicle beans"'
Note that the values passed to *--payload*, eg. `"radicle-beans"` must be valid
_JSON_ values. This means that strings should be double quoted, as in the
example above.
=== Removing a delegate
To remove a delegate and update the threshold, use the *--rescind* option:
$ rad id update --title "Remove Bob" \
--description "Bob is no longer a delegate" \
--rescind did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk \
--threshold 1
As with adding a delegate, this change will require approval from the remaining
delegates. Make sure you set an appropriate new threshold when removing
delegates!
-287
View File
@@ -1,287 +0,0 @@
= rad-patch(1)
The Radicle Team <team@radicle.xyz>
:doctype: manpage
:revnumber: 1.0.0
:revdate: 2024-04-22
:mansource: rad {revnumber}
:manmanual: Radicle CLI Manual
== Name
rad-patch - Manage radicle patches.
== Synopsis
*rad patch* [<option>...] +
*rad patch* _list_ [--all|--merged|--open|--archived|--draft] [<option>...] +
*rad patch* _show_ <patch-id> [<option>...] +
*rad patch* _diff_ <patch-id> [--revision <revision-id>] [<option>...] +
*rad patch* _archive_ <patch-id> [--undo] [<option>...] +
*rad patch* _update_ <patch-id> [<option>...] +
*rad patch* _checkout_ <patch-id> [<option>...] +
*rad patch* _delete_ <patch-id> [<option>...] +
*rad patch* _redact_ <revision-id> [<option>...] +
*rad patch* _ready_ <patch-id> [--undo] [<option>...] +
*rad patch* _review_ <patch-id> [<option>...] +
*rad patch* _edit_ <patch-id> [<option>...] +
*rad patch* _set_ <patch-id> [<option>...] +
*rad patch* _comment_ <revision-id> [<option>...] +
*rad patch* _label_ <patch-id> [<option>...] +
*git push rad* HEAD:refs/patches +
== Description
The Radicle *patch* command is used for managing changesets inside of Radicle
repositories.
Though many actions can be performed using *rad patch*, certain patch-related
actions use *git* directly. For example, opening a patch is typically
done using *git push*, while merging a patch is done with a combination of
*git merge* and *git push*.
To make this possible, Radicle ships with a helper program, *git-remote-rad*
which is invoked by *git* on push and fetch to and from Radicle remotes.
== Commands
With no arguments, *rad patch* defaults to the _list_ command, showing the list of
open patches for the current repository.
=== show
Shows information on the given patch.
*<patch-id>*:: The patch to show
*--patch*, *-p*:: Show the patch changes in git patch format
*--verbose*, *-v*:: Show additional information about the patch
=== diff
Outputs the patch diff, using Radicle's diffing tool.
*<patch-id>*:: The patch to diff
*--revision*, *-r <revision-id>*:: The revision to diff (default: latest)
=== edit
Edits a patch revision comment. To edit the patch title or description, pass
in the *<patch-id>*. To edit a revision comment, pass that revision's
*<revision-id>*.
*<revision-id>*::
The revision to edit.
*--message*, *-m [<string>]*::
Comment message to the patch or revision. If omitted, Radicle will prompt for
a comment string via *$EDITOR*.
=== list
List patches in the current repository. The default is *--open*.
*--all*:: List all patches, including merged and archived patches
*--archived*:: List only archived patches
*--merged*:: List only merged patches
*--open*:: List only open patches
*--draft*:: List only draft patches
*--authored*:: Show only patches that you have authored
*--author <did>*:: Show only patched where the given user is an author
(may be specified multiple times)
=== ready
Mark a patch as ready to review. This changes the state of a patch from *draft*
to *open*.
*--undo*:: Change a patch back to *draft*
=== review
Review a patch. Indicate acceptance or rejection of a patch revision along
with a comment.
*--revision*, *-r <revision-id>*:: The revision to diff (default: latest)
*--patch*, *--p*:: Review by patch hunks
*--hunk <index>*:: Only review a specific hunk
*--accept*:: Accept a patch or set of hunks
*--reject*:: Reject a patch or set of hunks
*--unified*, *-U <n>*:: Generate diffs with *<n>* lines of context instead of the usual three
*--delete*, *-d*:: Delete a review draft
*--message*, *-m [<string>]*:: Provide a comment with the review (default: prompt)
=== archive
Archive a patch.
*--undo*:: Unarchive a patch
=== set
Set the current branch upstream to a patch reference. This configures your
branch such that pushing will update the specified patch.
*<patch-id>*:: The patch to set this branch's upstream to
=== update
Updates a patch to the current repository *HEAD*. This is a low-level command
that should only be used when using *git push rad* is not possible.
*--message*, *-m [<string>]*:: Provide a comment message to the revision
*--no-message*:: Leave the revision comment message blank
=== checkout
Switch to a given patch, by creating a branch that points to the patch head.
This is essentially equivalent to *git checkout -b <name>* followed by
*rad patch set <patch-id>*. By default, the branch name includes the Patch ID.
*--revision <id>*:: Checkout the given revision of the patch
*--name <string>*:: Provide a name for the new branch
*--force*, *-f*:: If the checkout already exists, update its head
=== comment
Comment on a patch revision, optionally replying to an existing comment.
*<revision-id>*::
The patch revision to comment on. The Patch ID is also a Revision ID,
and can be used for commenting on the initial revision of the patch.
Any other Revision ID will comment on the revision specified.
*--message*, *-m <string>*::
Comment message. If omitted, Radicle will prompt for a comment string via
*$EDITOR*. Multiple messages will be concatinated with a blank line in between.
*--reply-to <comment-id>*::
Optional comment to reply to. If ommitted, the comment is a top-level comment
on the given revision.
== Opening a patch
To open a patch, we start by making changes to our working copy, typically on
a feature branch. For example:
$ git checkout -b fix/option-parsing
... edit some files ...
$ git commit -a -m "Fix option parsing"
Once our changes are ready to be proposed as a patch, we push them via *git*
to a special reference on the *rad* remote, that is used for opening patches
(*refs/patches*):
$ git push rad HEAD:refs/patches
✓ Patch 90c77f2c33b7e472e058de4a586156f8a7fec7d6 opened
...
Radicle will then open your editor, where you can edit the patch title and
description. Make sure either *EDITOR* or *VISUAL* is set in your environment
(See *environ(7)* for more details). Once you're done, simply save and exit your
editor. If successful, the patch is opened and its identifier is printed out.
You can then display the patch metadata using the *show* sub-command:
$ rad patch show 90c77f2
Note that you don't have to use the full patch identifier. An unambiguous
prefix of it also works.
Radicle can create a patch from any Git commit. Simply substitute *HEAD* with
the branch name or commit hash you wish to propose a patch for. For example:
$ git push rad d39fe32387496876fae6446daf3762aacf69d83b:refs/patches
After the patch is opened, you may notice that Radicle has set your branch
upstream to something like *rad/patches/90c77f2c33b7e472e058de4a586156f8a7fec7d6*.
This means your branch is now associated with the newly opened patch, and any
push from this branch will result in the patch being updated. See the next
section on updating a patch for more information.
=== Options
When opening a patch, various options can be specified using git push options.
This is done via the *-o* or *--push-option* flag. For example, *-o patch.draft*.
The full list of options follows:
*sync*, *no-sync*::
Whether or not to sync with the network after the patch is opened. Defaults
to _sync_.
*sync.debug*::
Show debug information about the syncing process.
*patch.draft*::
Open the patch as a _draft_. Turned off by default.
*patch.message*=_<message>_::
To prevent the editor from opening, you can specify the patch message via this
option. Multiple *patch.message* options are concatenated with a blank line
in between.
*patch.base*=_<oid>_::
The base commit onto which this patch should be merged. By default, this is
your "master" branch. When building stacked patches, it may be useful to
set this to the head of a previous patch.
For more information on push options, see *git-push(1)*.
== Updating a patch
To update a patch, we simply make our changes locally and push:
$ git commit --amend
$ git push --force
✓ Patch 90c77f2 updated to revision d0018fcc21d87c91a1ff9155aed6b4e57535566b
...
Note that this will only work if the current branch upstream is set correctly.
This happens automatically when a patch is opened from a branch without an
upstream set. In the above example, we used the *--force* option, since the
commit was amended. This is common practice when a patch has been reworked
after receiving a review.
If the branch upstream is not set to the patch reference, ie. *rad/patches/<id>*,
you can do so using `rad patch set <id>`.
As with opening a patch, you will be asked to enter a reason for updating the
patch, via your editor. Simply save and exit when you're done; or leave it
blank to skip this step.
It's also possible to change the patch _base_ during an update. Simply use the
*patch.base* push option as described in _Opening a patch_.
== Checking out a patch
When working with patches opened by peers, it's often useful to be able to
checkout the code in its own branch. With a patch checkout, you can browse the
code, run tests and even propose your own update to the patch. The *checkout*
sub-command is used to that effect:
$ rad patch checkout 90c77f2
Radicle will create a new branch if necessary and checkout the patch head. From
there, you can *git-push* to publish a patch update, or simply browse the code.
== Merging a patch
Once a patch is ready to merge, the repository maintainer simply has to use the
*git-merge(1)* command from the "master" branch and push via *git*. For
example, if some patch *26e3e56* is ready to merge, the steps would be:
$ rad patch checkout 26e3e56
✓ Switched to branch patch/26e3e56
$ git checkout master
$ git merge patch/26e3e56
$ git push rad
✓ Patch 26e3e563ddc7df8dd0c9f81274c0b3cb1b764568 merged
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..d6399c7 master -> master
In the above, we created a checkout for the patch, and merged that branch into
our master branch. Then we pushed to our *rad* remote.
== Listing patches
To list patches, run *rad patch*. By default, this will only show open patches.
To list all patches, including ones that have been merged or archived, add the
*--all* option.
-218
View File
@@ -1,218 +0,0 @@
= rad(1)
The Radicle Team <team@radicle.xyz>
:doctype: manpage
:revnumber: 1.0.0
:revdate: 2024-04-22
:mansource: rad {revnumber}
:manmanual: Radicle CLI Manual
== Name
rad - Radicle command-line tooling.
== Synopsis
*rad* *
== Description
The Radicle CLI is a set of tools designed for users of the Radicle
peer-to-peer code collaboration stack. The CLI provides the ability to manage
Radicle profiles and publish repositories to the Radicle network.
== Requirements
There are a few important requirements for running the Radicle CLI: first, a
recent version of *git* (*2.34.0* or later) must be installed. You can check
your installation by running *git --version*. Second, *ssh-agent* must be
installed and running. To bypass ssh-agent, it's possible to set the
*RAD_PASSPHRASE* environment variable. See the _Environment_ section.
== Getting started
Let's see how we can setup Radicle and clone a repository from the network.
Before we start, make sure that the *rad* binary is available on your system by
running:
$ rad
This is the base command and the output will show you all available
sub-commands.
=== Creating a profile
When using Radicle for the first time, you have to create a new Radicle
_profile_. A profile is simply a cryptographic key pair associated with some
storage space on your local device. Radicle stores everything under the Radicle
_home_ directory, which defaults to *~/.radicle* on most systems.
To create your profile, run:
$ rad auth
Follow the instructions by entering an alias and a passphrase to protect your
key pair. When you've completed the steps, your new Radicle _DID_
(Decentralized Identifier) will be displayed. This identifies your device, as
well as all the code you publish. Share it freely with collaborators. Your
alias is a non-unique name that is shared across the network to identify your
node. It can always be changed at a later time.
After running *rad auth*, you should have a new profile and key pair under your
Radicle home. The key pair is used for identifying peers across the network, as
well as signing code and other artifacts published on the network.
To avoid having to re-enter your passphrase every time a signature is required,
*rad auth* adds your secret key to *ssh-agent*.
You can always display the full path to your Radicle home directory using:
$ rad self --home
The *self* sub-command is useful for querying profile information. You can
run it without any arguments to show all of your profile information, including
your DID:
$ rad self
=== Connecting to the network
Radicle is a peer-to-peer system. This means that there are no clients or
servers; only _nodes_, and every user has to run their own. This ensures
that there is no single point of failure, and the Radicle network can keep on
existing as long as there are users to run nodes.
The Radicle node is a lightweight process that runs in the background and
manages connections with other nodes, as well as data replication. To check your
node's status, run:
$ rad node status
If the node is not running, you can start it with:
$ rad node start
This will start the node in the foreground. Simply add *--daemon* to run the
node in the background.
When daemonized, the node will log all output to a file under your Radicle home.
On most systems, this will be *~/.radicle/node/node.log*.
You can also display the latest logs at any time using:
$ rad node logs
After your node starts, it will attempt to connect to peers on the network.
To connect to a specific peer, the *connect* sub-command can be used:
For example, to connect to the *radicle.garden* community node, you can run:
$ rad node connect z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776
The argument given to *connect* is called a node _address_. It is composed of
a Node ID (NID), followed by an *@* symbol, and the _host_ name and _port_.
You can display your NID with *rad self --nid*.
To make these connections permanent, such that they are always tried on
startup, add the addresses to your configuration file at
*~/.radicle/config.json*, under the *"connect"* field.
Now that you're connected to the network, we can try to clone a repository
hosted on Radicle.
=== Cloning a repository
To clone a repository from the Radicle network, you need to be connected to a
seed which is hosting the repository. Eventually, this won't be necessary,
but for now, a direct connection with the seed you are cloning from is
required. Once the connection is established, you can run the *rad clone*
command to clone a repository, by supplying a Repository Identifier (RID).
RIDs are globally unique URNs that identify a repository on Radicle.
For example, let's clone the Radicle heartwood repository:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
If the command succeeds, you will have a checkout of the repository under
*./heartwood*, with a *rad* remote setup, which you can pull from and push to.
The *rad* remote is special in that it will always return the _authoritative_
branch of the repo, when fetched from. This is defined as the latest commit
on the repository's default branch that a threshold of delegates have published.
Under the hood, the *clone* command does a few important things:
* It subscribes to updates from this repository; this is called _tracking_.
* It creates a copy of the repository under your local storage, which provides
a replica to the network.
* It creates a Git remote for every repository _delegate_. These are usually
the repository maintainers.
* It creates a fork of the repository that is under your public key.
* It creates a *rad* remote which you can push to, to update your fork.
You can see all replicated and locally-available repositories using:
$ rad ls --all
By default, your node will subscribe to updates from _all_ peers. This
behavior can be changed by passing *--scope followed* when cloning. It can
also be changed later via the *rad seed* command.
=== Publishing a repository on Radicle
To publish an existing Git repository on the Radicle network, navigate to
a working copy and run the *rad init* command:
$ cd my-repo
$ rad init
You will be prompted to enter a repository name and default branch. Once the
repository is created, the Repository Identifier (RID) will be printed.
Share this with collaborators so that they can clone your repository.
Note that your new repository will only be replicated by nodes that you are
connected to and either have an open tracking policy, or trust your Node ID.
The *rad init* command creates a special remote in your working copy named
*rad*. Pushing to this remote publishes changes to your local storage and to
the network.
You can view information about the *rad* remote with:
$ git remote show rad
You can display the Repository ID (RID) from the root of any Git repository
by running:
$ rad .
You can also show the repository's identity payload, which contains its name,
description and default branch by running:
$ rad inspect --payload
== Files
*~/.radicle*::
The _Radicle Home_. This is the default location for Radicle configuration
files, keys, and repository storage. Can be controlled by settting the
**RAD_HOME** environment variable.
*~/.radicle/config.json*::
The default configuration file for Radicle. To display its exact
location on your system, you can use the *rad self --config* command.
== Environment
Various environment variables are used by *rad*.
*RAD_HOME*::
This sets the location of your Radicle home. If unset, your Radicle home will
be located at *~/.radicle*.
*RAD_PASSPHRASE*::
If you would like to bypass *ssh-agent*, you can store your passphrase in this
environment variable. Note that this is not secure and is equivalent to having
an unencrypted secret key.
-24
View File
@@ -1,24 +0,0 @@
[package]
name = "radicle-cli-test"
description = "Radicle CLI test library"
homepage = "https://radicle.xyz"
license = "MIT OR Apache-2.0"
version = "0.10.0"
authors = ["cloudhead <cloudhead@radicle.xyz>"]
edition = "2021"
categories = ["development-tools::testing"]
keywords = ["cli", "test", "command"]
readme = "README.md"
[dependencies]
escargot = { version = "0.5.7" }
log = { version = "0.4", features = ["std"] }
pretty_assertions = { version = "1.3.0" }
shlex = { version = "1.1.0" }
snapbox = { version = "0.4.3" }
thiserror = { version = "1" }
[dependencies.radicle]
path = "../radicle"
version = "0"
features = ["logger", "test"]
-35
View File
@@ -1,35 +0,0 @@
# radicle-cli-test
Test your CLI with the help of markdown descriptions.
## Example
Test flows are described in markdown like this example:
````` markdown
# Echoing works
When I call echo, it answers:
```
$ echo "ohai"
ohai
```
`````
Say this is placed in `kind-echo.md`, this is what the corresponding test case
would look lke:
``` rust
use std::path::Path;
use radicle_cli_test::TestFormula;
#[test]
fn kind_echo() {
TestFormula::new()
.file(Path::new("./kind-echo.md"))
.unwrap()
.run()
.unwrap();
}
```
-659
View File
@@ -1,659 +0,0 @@
#![allow(clippy::collapsible_else_if)]
use std::borrow::Cow;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync;
use std::{env, ffi, fs, io, mem};
use snapbox::cmd::{Command, OutputAssert};
use snapbox::{Assert, Substitutions};
use thiserror::Error;
/// Used to ensure the build task is only run once.
static BUILD: sync::Once = sync::Once::new();
#[derive(Error, Debug)]
pub enum Error {
#[error("parsing failed")]
Parse,
#[error("invalid file path: {0:?}")]
InvalidFilePath(String),
#[error("unknown home {0:?}")]
UnknownHome(String),
#[error("test file not found: {0:?}")]
TestNotFound(PathBuf),
#[error("i/o: {0}")]
Io(#[from] io::Error),
#[error("snapbox: {0}")]
Snapbox(#[from] snapbox::Error),
}
#[derive(Debug, PartialEq, Eq)]
enum ExitStatus {
Success,
Failure,
}
/// A test which may contain multiple assertions.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Test {
/// Human-readable context around the test. Functions as documentation.
context: Vec<String>,
/// Test assertions to run.
assertions: Vec<Assertion>,
/// Whether to check stderr's output instead of stdout.
stderr: bool,
/// Whether to expect an error status code.
fail: bool,
/// Home directory under which to run this test.
home: Option<String>,
/// Local env vars to use just for this test.
env: HashMap<String, String>,
}
/// An assertion is a command to run with an expected output.
#[derive(Debug, PartialEq, Eq)]
pub struct Assertion {
/// The test file that contains this assertion.
path: PathBuf,
/// Name of command to run, eg. `git`.
command: String,
/// Command arguments, eg. `["push"]`.
args: Vec<String>,
/// Expected output (stdout or stderr).
expected: String,
/// Expected exit status.
exit: ExitStatus,
}
#[derive(Debug, Default, PartialEq, Eq, Clone)]
pub struct Home {
name: Option<String>,
path: PathBuf,
envs: HashMap<String, String>,
}
#[derive(Debug)]
pub struct TestRun {
home: Home,
env: HashMap<String, String>,
}
impl TestRun {
fn cd(&mut self, path: PathBuf) {
self.home.path = path;
}
fn envs(&self) -> impl Iterator<Item = (String, String)> + '_ {
self.home
.envs
.iter()
.chain(self.env.iter())
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.chain(Some((
"PWD".to_owned(),
self.home.path.to_string_lossy().to_string(),
)))
}
fn path(&self) -> PathBuf {
self.home.path.clone()
}
}
#[derive(Debug)]
pub struct TestRunner<'a> {
cwd: Option<PathBuf>,
homes: HashMap<String, Home>,
formula: &'a TestFormula,
}
impl<'a> TestRunner<'a> {
fn new(formula: &'a TestFormula) -> Self {
Self {
cwd: None,
homes: formula.homes.clone(),
formula,
}
}
fn run(&mut self, test: &'a Test) -> TestRun {
let mut env = self.formula.env.clone();
env.extend(test.env.clone());
if let Some(ref h) = test.home {
if let Some(home) = self.homes.get(h) {
return TestRun {
home: home.clone(),
env,
};
} else {
panic!("TestRunner::test: home `~{h}` does not exist");
}
}
TestRun {
home: Home {
name: None,
path: self.cwd.clone().unwrap_or_else(|| self.formula.cwd.clone()),
envs: HashMap::new(),
},
env,
}
}
fn finish(&mut self, run: TestRun) {
if let Some(name) = &run.home.name {
self.homes.insert(name.clone(), run.home);
} else {
self.cwd = Some(run.home.path);
}
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct TestFormula {
/// Current working directory to run the test in.
cwd: PathBuf,
/// User homes.
homes: HashMap<String, Home>,
/// Environment to pass to the test.
env: HashMap<String, String>,
/// Tests to run.
tests: Vec<Test>,
/// Output substitutions.
subs: Substitutions,
/// Binaries path.
bins: Vec<PathBuf>,
}
impl TestFormula {
pub fn new(cwd: PathBuf) -> Self {
Self {
cwd,
env: HashMap::new(),
homes: HashMap::new(),
tests: Vec::new(),
subs: Substitutions::new(),
bins: env::var("PATH")
.map(|p| p.split(':').map(PathBuf::from).collect())
.unwrap_or_default(),
}
}
pub fn build(&mut self, binaries: &[(&str, &str)]) -> &mut Self {
let manifest = env::var("CARGO_MANIFEST_DIR").expect(
"TestFormula::build: cannot build binaries: variable `CARGO_MANIFEST_DIR` is not set",
);
let profile = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
let target_dir = env::var("CARGO_TARGET_DIR").unwrap_or("target".to_string());
let manifest = Path::new(manifest.as_str());
let bins = manifest.join(&target_dir).join(profile);
// Add the target dir to the beginning of the list we will use as `PATH`.
self.bins.insert(0, bins);
// We don't need to re-build everytime the `build` function is called. Once is enough.
BUILD.call_once(|| {
use escargot::format::Message;
use radicle::logger::env_level;
use radicle::logger::test as logger;
logger::init(env_level().unwrap_or(log::Level::Debug));
for (package, binary) in binaries {
log::debug!(target: "test", "Building binaries for package `{package}`..");
let results = escargot::CargoBuild::new()
.package(package)
.bin(binary)
.manifest_path(manifest.join("Cargo.toml"))
.target_dir(&target_dir)
.exec()
.unwrap();
for result in results {
match result {
Ok(msg) => {
if let Ok(Message::CompilerArtifact(a)) = msg.decode() {
if let Some(e) = a.executable {
log::debug!(target: "test", "Built {}", e.display());
}
}
}
Err(e) => {
log::error!(target: "test", "Error building package `{package}`: {e}");
}
}
}
}
});
self
}
pub fn env(&mut self, key: impl ToString, val: impl ToString) -> &mut Self {
self.env.insert(key.to_string(), val.to_string());
self
}
pub fn home(
&mut self,
user: impl ToString,
path: impl AsRef<Path>,
envs: impl IntoIterator<Item = (impl ToString, impl ToString)>,
) -> &mut Self {
self.homes.insert(
user.to_string(),
Home {
name: Some(user.to_string()),
path: path.as_ref().to_path_buf(),
envs: envs
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
},
);
self
}
pub fn envs<K: ToString, V: ToString>(
&mut self,
envs: impl IntoIterator<Item = (K, V)>,
) -> &mut Self {
for (k, v) in envs {
self.env.insert(k.to_string(), v.to_string());
}
self
}
pub fn file(&mut self, path: impl AsRef<Path>) -> Result<&mut Self, Error> {
let path = path.as_ref();
let contents = match fs::read(path) {
Ok(bytes) => bytes,
Err(err) if err.kind() == io::ErrorKind::NotFound => {
return Err(Error::TestNotFound(path.to_path_buf()));
}
Err(err) => return Err(err.into()),
};
self.read(path, io::Cursor::new(contents))
}
pub fn read(&mut self, path: &Path, r: impl io::BufRead) -> Result<&mut Self, Error> {
let mut test = Test::default();
let mut fenced = false; // Whether we're inside a fenced code block.
let mut file: Option<(PathBuf, String)> = None; // Path and content of file created by this test block.
for line in r.lines() {
let line = line?;
if line.starts_with("```") {
if fenced {
if let Some((ref path, ref mut content)) = file.take() {
// Write file.
let path = self.cwd.join(path);
if let Some(dir) = path.parent() {
log::debug!(target: "test", "Creating directory {}..", dir.display());
fs::create_dir_all(dir)?;
}
log::debug!(target: "test", "Writing {} bytes to {}..", content.len(), path.display());
fs::write(path, content)?;
} else {
// End existing code block.
self.tests.push(mem::take(&mut test));
}
} else {
for token in line.split_whitespace() {
if let Some(home) = token.strip_prefix('~') {
test.home = Some(home.to_owned());
} else if let Some((key, val)) = token.split_once('=') {
test.env.insert(key.to_owned(), val.to_owned());
} else if token.contains("stderr") {
test.stderr = true;
} else if token.contains("fail") {
test.fail = true;
} else if let Some(path) = token.strip_prefix("./") {
file = Some((
PathBuf::from_str(path)
.map_err(|_| Error::InvalidFilePath(token.to_owned()))?,
String::new(),
));
}
}
}
fenced = !fenced;
continue;
}
if fenced {
if let Some((_, ref mut content)) = file {
content.push_str(line.as_str());
content.push('\n');
} else if let Some(line) = line.strip_prefix('$') {
let line = line.trim();
let parts = shlex::split(line).ok_or(Error::Parse)?;
let (cmd, args) = parts.split_first().ok_or(Error::Parse)?;
test.assertions.push(Assertion {
path: path.to_path_buf(),
command: cmd.to_owned(),
args: args.to_owned(),
expected: String::new(),
exit: if test.fail {
ExitStatus::Failure
} else {
ExitStatus::Success
},
});
} else if let Some(a) = test.assertions.last_mut() {
a.expected.push_str(line.as_str());
a.expected.push('\n');
} else {
return Err(Error::Parse);
}
} else {
test.context.push(line);
}
}
Ok(self)
}
#[allow(dead_code)]
pub fn substitute(
&mut self,
value: &'static str,
other: impl Into<Cow<'static, str>>,
) -> Result<&mut Self, Error> {
self.subs.insert(value, other)?;
Ok(self)
}
/// Convert instances of '[.. ]' to '[..]' where the number of ' 's are arbitrary.
///
/// Supporting these bracket types help support using the '[..]' pattern while preserving
/// spaces important for text alignment.
fn map_spaced_brackets(s: &str) -> String {
let mut ret = String::new();
let mut pos = 0;
for c in s.chars() {
match (c, pos) {
('[', 0) => pos += 1,
(' ', 1) => continue,
('.', 1) => pos += 1,
('.', 2) => pos += 1,
('.', 3) => continue,
(' ', 3) => continue,
(']', 3) => pos = 0,
(_, _) => pos = 0,
}
ret.push(c);
}
ret
}
pub fn run(&mut self) -> Result<bool, io::Error> {
let assert = Assert::new().substitutions(self.subs.clone());
let mut runner = TestRunner::new(self);
fs::create_dir_all(&self.cwd)?;
log::debug!(target: "test", "Using PATH {:?}", self.bins);
// For each code block.
for test in &self.tests {
let mut run = runner.run(test);
// For each command.
for assertion in &test.assertions {
// Expand environment variables.
let mut args = assertion.args.clone();
for arg in &mut args {
for (k, v) in run.envs() {
*arg = arg.replace(format!("${k}").as_str(), &v);
}
}
let path = assertion
.path
.file_name()
.map(|f| f.to_string_lossy().to_string())
.unwrap_or(String::from("<none>"));
let cmd = if assertion.command == "rad" {
snapbox::cmd::cargo_bin("rad")
} else if assertion.command == "cd" {
let arg = assertion.args.first().unwrap();
let dir: PathBuf = arg.into();
let dir = run.path().join(dir);
// TODO: Add support for `..` and `/`
// TODO: Error if more than one args are given.
log::debug!(target: "test", "{path}: Running `cd {}`..", dir.display());
if !dir.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("cd: '{}' does not exist", dir.display()),
));
}
run.cd(dir);
continue;
} else {
PathBuf::from(&assertion.command)
};
log::debug!(target: "test", "{path}: Running `{}` with {:?} in `{}`..", cmd.display(), assertion.args, run.path().display());
if !run.path().exists() {
log::warn!(target: "test", "{path}: Directory {} does not exist. Creating..", run.path().display());
fs::create_dir_all(run.path())?;
}
let bins = self
.bins
.iter()
.map(|p| p.as_os_str())
.collect::<Vec<_>>()
.join(ffi::OsStr::new(":"));
let result = Command::new(cmd.clone())
.env_clear()
.env("PATH", &bins)
.env("RUST_BACKTRACE", "1")
.envs(run.envs())
.current_dir(run.path())
.args(args)
.with_assert(assert.clone())
.output();
match result {
Ok(output) => {
let assert = OutputAssert::new(output).with_assert(assert.clone());
let expected = Self::map_spaced_brackets(&assertion.expected);
let matches = if test.stderr {
assert.stderr_matches(&expected)
} else {
assert.stdout_matches(&expected)
};
match assertion.exit {
ExitStatus::Success => {
matches.success();
}
ExitStatus::Failure => {
matches.failure();
}
}
}
Err(err) => {
if err.kind() == io::ErrorKind::NotFound {
log::error!(target: "test", "{path}: Command `{}` does not exist..", cmd.display());
}
return Err(io::Error::new(
err.kind(),
format!("{path}: {err}: `{}`", cmd.display()),
));
}
}
}
runner.finish(run);
}
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse() {
let input = r#"
Let's try to track @dave and @sean:
``` RAD_HINT=true
$ rad track @dave
Tracking relationship established for @dave.
Nothing to do.
$ rad track @sean
Tracking relationship established for @sean.
Nothing to do.
```
Super, now let's move on to the next step.
``` ~alice (stderr)
$ rad sync
```
"#
.trim()
.as_bytes()
.to_owned();
let mut actual = TestFormula::new(PathBuf::new());
let path = Path::new("test.md").to_path_buf();
actual
.read(path.as_path(), io::BufReader::new(io::Cursor::new(input)))
.unwrap();
let expected = TestFormula {
homes: HashMap::new(),
cwd: PathBuf::new(),
env: HashMap::new(),
subs: Substitutions::new(),
bins: env::var("PATH")
.unwrap()
.split(':')
.map(PathBuf::from)
.collect(),
tests: vec![
Test {
context: vec![String::from("Let's try to track @dave and @sean:")],
home: None,
assertions: vec![
Assertion {
path: path.clone(),
command: String::from("rad"),
args: vec![String::from("track"), String::from("@dave")],
expected: String::from(
"Tracking relationship established for @dave.\nNothing to do.\n\n",
),
exit: ExitStatus::Success,
},
Assertion {
path: path.clone(),
command: String::from("rad"),
args: vec![String::from("track"), String::from("@sean")],
expected: String::from(
"Tracking relationship established for @sean.\nNothing to do.\n",
),
exit: ExitStatus::Success,
},
],
fail: false,
stderr: false,
env: vec![("RAD_HINT".to_owned(), "true".to_owned())]
.into_iter()
.collect(),
},
Test {
context: vec![String::from("Super, now let's move on to the next step.")],
home: Some("alice".to_owned()),
assertions: vec![Assertion {
path: path.clone(),
command: String::from("rad"),
args: vec![String::from("sync")],
expected: String::new(),
exit: ExitStatus::Success,
}],
fail: false,
stderr: true,
env: HashMap::default(),
},
],
};
assert_eq!(actual, expected);
}
#[test]
fn test_run() {
let input = r#"
Running a simple command such as `head`:
```
$ head -n 2 Cargo.toml
[package]
name = "radicle-cli-test"
```
"#
.trim()
.as_bytes()
.to_owned();
let mut formula = TestFormula::new(PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap());
formula
.read(
Path::new("test.md"),
io::BufReader::new(io::Cursor::new(input)),
)
.unwrap();
formula.run().unwrap();
}
#[test]
fn test_example_spaced_brackets() {
let input = r#"
Running a simple command such as `head`:
```
$ echo " hello"
[..]hello
$ echo " hello"
[.. ]hello
$ echo " hello"
[ ..]hello
$ echo "[bug, good-first-issue]"
[bug, good-first-issue]
$ echo "[bug, good-first-issue]"
[bug, [ .. ]-issue]
$ echo "[bug, good-first-issue]"
[bug, [ ... ]-issue]
```
"#
.trim()
.as_bytes()
.to_owned();
let mut formula = TestFormula::new(PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap());
formula
.read(
Path::new("test.md"),
io::BufReader::new(io::Cursor::new(input)),
)
.unwrap();
formula.run().unwrap();
}
}
-76
View File
@@ -1,76 +0,0 @@
[package]
name = "radicle-cli"
description = "Radicle CLI"
homepage = "https://radicle.xyz"
license = "MIT OR Apache-2.0"
version = "0.11.0"
authors = ["cloudhead <cloudhead@radicle.xyz>"]
edition = "2021"
build = "build.rs"
[[bin]]
name = "rad"
path = "src/main.rs"
[dependencies]
anyhow = { version = "1" }
chrono = { version = "0.4.26", default-features = false, features = ["clock", "std"] }
git-ref-format = { version = "0.3.0", features = ["macro"] }
lexopt = { version = "0.3.0" }
localtime = { version = "1.2.0" }
log = { version = "0.4", features = ["std"] }
nonempty = { version = "0.9.0" }
# N.b. this is required to use macros, even though it's re-exported
# through radicle
radicle-git-ext = { version = "0.8.0", features = ["serde"] }
radicle-surf = { version = "0.22.0" }
serde = { version = "1.0" }
serde_json = { version = "1" }
shlex = { version = "1.1.0" }
tempfile = { version = "3.3.0" }
thiserror = { version = "1" }
timeago = { version = "0.4.2", default-features = false }
tree-sitter = { version = "0.20.0" }
tree-sitter-highlight = { version = "0.20" }
tree-sitter-json = { version = "0.20.1" }
tree-sitter-rust = { version = "0.20" }
tree-sitter-typescript = { version = "0.20.3" }
tree-sitter-html = { version = "0.20" }
tree-sitter-css = { version = "0.20" }
tree-sitter-toml = { version = "0.20" }
tree-sitter-c = { version = "0.20" }
tree-sitter-python = { version = "0.20" }
tree-sitter-ruby = { version = "0.20" }
tree-sitter-bash = { version = "0.20" }
tree-sitter-go = { version = "0.20.0" }
tree-sitter-md = { version = "0.1.5" }
zeroize = { version = "1.1" }
[dependencies.radicle]
version = "0"
path = "../radicle"
features = ["logger"]
[dependencies.radicle-cli-test]
version = "0"
path = "../radicle-cli-test"
[dependencies.radicle-cob]
version = "0"
path = "../radicle-cob"
[dependencies.radicle-crypto]
version = "0"
path = "../radicle-crypto"
[dependencies.radicle-term]
version = "0"
path = "../radicle-term"
[dev-dependencies]
pretty_assertions = { version = "1.3.0" }
radicle = { version = "0", path = "../radicle", features = ["test"] }
radicle-node = { path = "../radicle-node", features = ["test"] }
[lints]
workspace = true
-1
View File
@@ -1 +0,0 @@
../build.rs
-33
View File
@@ -1,33 +0,0 @@
```
$ mkdir bin
$ cd bin
$ touch file.bin
```
``` ~bob
$ rad self --did
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
$ pwd
[..]/home/bob/.radicle
$ mkdir src
$ cd src
$ pwd
[..]/home/bob/.radicle/src
```
``` ~alice
$ rad self --did
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
$ pwd
[..]/home/alice/.radicle
```
``` ~bob
$ pwd
[..]/home/bob/.radicle/src
```
```
$ ls
file.bin
```
-30
View File
@@ -1,30 +0,0 @@
```
$ cd heartwood
$ git branch -r
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
rad/master
```
```
$ git ls-remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/heads/*'
145e1e69bef3ad93d14946ea212249c2fa9b9828 refs/heads/alice/1
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
``` (stderr)
$ git fetch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new branch] alice/1 -> alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
```
```
$ git branch -r
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
rad/master
```
```
$ git rev-parse alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/alice/1
145e1e69bef3ad93d14946ea212249c2fa9b9828
```
@@ -1,27 +0,0 @@
``` ~alice
$ rad id update --title "Add Bob" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -q
c036c0d89ce26aef3ad7da402157dba16b5163b4
```
``` ~bob
$ rad sync --fetch
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
``` ~alice
$ git commit -m "New changes" --allow-empty -q
$ git push rad master -o no-sync
```
``` ~alice
$ git commit --amend -m "Neue Änderungen" --allow-empty -q
```
``` ~alice (stderr)
$ git push rad master -f
✓ Canonical head updated to 9170c8795d3a78f0381a0ffafb20ea69fb0f5b6b
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ fb25886...9170c87 master -> master (forced update)
```
@@ -1,142 +0,0 @@
In this scenario we check that we can easily reset our canonical head to the
head of another delegate when there is divergence between the 3 delegates.
First we add our new delegates, Bob & Eve, to our repo, while also setting the
`threshold` to `3`:
``` ~alice
$ rad id update --title "Add Bob & Eve" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --threshold 3 --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -q
3143236b2e40338f5574ec04e935a5ab80a6868a
```
Bob and Eve will fetch the changes to ensure they hear about their delegate
responsibilities:
``` ~bob
$ rad sync --fetch
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 2 seed(s)
```
``` ~eve
$ rad sync --fetch
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 2 seed(s)
```
To demonstrate the divergence, Alice, Bob, and Eve will all create a new change,
pushing to their `rad` remote -- but they won't sync to the network just yet:
``` ~alice
$ git commit -m "Alice's commit" --allow-empty -q
$ git push rad -o no-sync
$ git ls-remote rad
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
``` ~bob
$ git add README
$ git commit -m "Bob's commit" -q
$ git push rad -o no-sync
$ git ls-remote rad
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
``` ~eve
$ git add README
$ git commit -m "Eve's commit" -q
$ git push rad -o no-sync
$ git ls-remote rad
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
Alice adds Bob and Eve as remotes and starts to notice that the `no quorum was
found` error is showing up:
``` ~alice
$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob
✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z..
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. error: no quorum was found
✓ Remote bob added
✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk
$ rad remote add did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --name eve
✓ Follow policy updated for z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z (eve)
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z.. error: no quorum was found
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. error: no quorum was found
✓ Remote eve added
✓ Remote-tracking branch eve/master created for z6Mkux1…nVhib7Z
```
Alice does indeed have Bob and Eve's references, however, a new canonical
`refs/heads/master` cannot be decided while they're out of quorum. This can be
remedied by the delegates agreeing upon which way to move forward. In this case,
Alice resets her `master` to `bob/master`:
``` ~alice
$ git merge bob/master
Merge made by the 'ort' strategy.
README | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git merge eve/master -s ours
Merge made by the 'ours' strategy.
```
She can then force push to update the canonical head to the new agreed upon
commit:
``` ~alice (stderr)
$ git push rad -f
warn: no quorum was found for `refs/heads/master`
warn: it is recommended to find a commit to agree upon
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
d09e634..0f9bd80 master -> master
```
``` ~bob
$ rad remote add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z.. error: no quorum was found
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi.. error: no quorum was found
✓ Remote alice added
✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
$ git reset --hard alice/master
HEAD is now at 0f9bd80 Merge remote-tracking branch 'eve/master'
```
When we check Bob's `rad` remote, we see that the commit for `refs/heads/master`
has changed. This is actually Eve's commit as part of Alice merging above. Now
that Alice, Bob, and Eve all have this commit as part of their history it has
become the canonical `master`.
``` ~bob (stderr)
$ git push rad
✓ Canonical head updated to 3a75f66dd0020c9a0355cc6ec21f15de989e2001
✓ Synced with 2 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
2a37862..0f9bd80 master -> master
```
Once Eve also resets to the merge commits, the canonical `master` is set to this tip.
``` ~eve
$ rad remote add did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Remote alice added
✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
$ git reset --hard alice/master
HEAD is now at 0f9bd80 Merge remote-tracking branch 'eve/master'
```
``` ~eve (stderr)
$ git push rad
✓ Canonical head updated to 0f9bd8035c04b3f73f5408e73e8454879b20800b
✓ Synced with 2 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
3a75f66..0f9bd80 master -> master
```
@@ -1,20 +0,0 @@
Finally, we can also delete branches with `git push`:
```
$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/*
145e1e69bef3ad93d14946ea212249c2fa9b9828 refs/heads/alice/1
ddcc1f164eacfd7dba41da9bff3261da3ee79fd3 refs/heads/alice/2
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad :alice/1
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
- [deleted] alice/1
```
```
$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi refs/heads/*
ddcc1f164eacfd7dba41da9bff3261da3ee79fd3 refs/heads/alice/2
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
@@ -1,80 +0,0 @@
Let's see what happens if we try to push a head which diverges from the
canonical head.
First we add a second delegate, Bob, to our repo:
``` ~alice
$ rad id update --title "Add Bob" --description "" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -q
c036c0d89ce26aef3ad7da402157dba16b5163b4
```
Then, as Bob, we commit some code on top of the canonical head:
``` ~bob
$ rad sync --fetch
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
$ rad inspect --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
$ git commit -m "Third commit" --allow-empty -q
$ git push rad
$ git branch -arv
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master f2de534 Second commit
rad/master 319a7dc Third commit
```
As Alice, we fetch that code, but commit on top of our own master, which is no
longer canonical, since Bob pushed a more recent commit, and the threshold is 1:
``` ~alice
$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --fetch --no-sync
✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added
✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk
$ git branch -arv
bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master 319a7dc Third commit
rad/master f2de534 Second commit
$ git commit -m "Third commit by Alice" --allow-empty -q
```
If we try to push now, we get an error with a hint, telling us that we need to
integrate Bob's changes before pushing ours:
``` ~alice (stderr) (fail) RAD_HINT=1
$ git push rad
hint: you are attempting to push a commit that would cause your upstream to diverge from the canonical head
hint: to integrate the remote changes, run `git pull --rebase` and try again
error: refusing to update branch to commit that is not a descendant of canonical head
error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
```
We do that, and notice that we're now able to push our code:
``` ~alice
$ git pull --rebase
$ git log --oneline
f6cff86 Third commit by Alice
319a7dc Third commit
f2de534 Second commit
08c788d Initial commit
```
``` ~alice RAD_SOCKET=/dev/null (stderr)
$ git push rad
✓ Canonical head updated to f6cff86594495e9beccfeda7c20173e55c1dd9fc
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..f6cff86 master -> master
```
One thing of note is that we can revert to an older commit as long as we are
still ahead of the other delegates.
``` ~alice
$ git reset --hard HEAD^ -q
```
``` ~alice RAD_SOCKET=/dev/null (stderr)
$ git push -f
✓ Canonical head updated to 319a7dc3b195368ded4b099f8c90bbb80addccd3
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ f6cff86...319a7dc master -> master (forced update)
```
@@ -1,60 +0,0 @@
In this scenario, we will explore being able to rollback to a previous commit.
First we add a second delegate, Bob, to our repo. We also change the threshold
to 2:
``` ~alice
$ rad id update --title "Add Bob" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --threshold 2 -q
069e7d58faa9a7473d27f5510d676af33282796f
```
Bob then syncs these changes and adds a new commit:
``` ~bob
$ rad sync --fetch
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
$ git commit -m "Third commit" --allow-empty -q
$ git push rad
$ git branch -arv
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master f2de534 Second commit
rad/master 319a7dc Third commit
```
Alice merges these changes and pushes them, which updates the canonical head:
``` ~alice
$ rad remote add did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob --fetch --no-sync
✓ Remote bob added
✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk
$ git merge bob/master
Updating f2de534..319a7dc
Fast-forward
```
``` ~alice (stderr)
$ git push rad
✓ Canonical head updated to 319a7dc3b195368ded4b099f8c90bbb80addccd3
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..319a7dc master -> master
```
Alice decides that she changes her mind about these changes and rolls back to
the previous commit:
``` ~alice
$ git reset --hard f2de534
HEAD is now at f2de534 Second commit
```
Since the canonical head is still decidable from this commit she is allowed to
push and the new canonical head becomes the previous commit again:
``` ~alice (stderr)
$ git push rad -f
✓ Canonical head updated to f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 319a7dc...f2de534 master -> master (forced update)
```
-90
View File
@@ -1,90 +0,0 @@
```
$ git checkout -b alice/1
$ git commit -m "Alice's commit" --allow-empty -s
[alice/1 87fa120] Alice's commit
```
``` (stderr) RAD_SOCKET=/dev/null
$ git push rad HEAD:alice/1
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new branch] HEAD -> alice/1
```
Make sure we can't force-push without `+`:
``` (stderr)
$ git commit --amend -m "Alice's amended commit" --allow-empty -s
```
``` (stderr) (fail)
$ git push rad HEAD:alice/1
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
! [rejected] HEAD -> alice/1 (non-fast-forward)
error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
hint: [..]
hint: [..]
hint: [..]
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
```
And that we can with `+`:
``` (stderr)
$ git push -o no-sync rad +HEAD:alice/1
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ 87fa120...145e1e6 HEAD -> alice/1 (forced update)
```
Notice that we used the `-o no-sync` push option to disable syncing after the push.
```
$ git branch -r -vv
rad/alice/1 145e1e6 Alice's amended commit
rad/master f2de534 Second commit
```
List our namespaced refs:
```
$ git ls-remote rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 'refs/heads/*'
145e1e69bef3ad93d14946ea212249c2fa9b9828 refs/heads/alice/1
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
List the canonical refs:
```
$ git ls-remote rad
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
```
```
$ rad sync --announce
✓ Synced with 1 node(s)
```
Note that it is forbidden to delete the default/canonical branch:
``` (fail) (stderr)
$ git push rad :master
error: refusing to delete default branch ref 'refs/heads/master'
error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
```
If you pass an unsupported push option, you get an error:
``` (stderr) (fail)
$ git push -o alien rad HEAD:alice/2
error: unknown push option "alien"
```
We can also push a SHA-1:
```
$ git commit -m "Something good" --allow-empty -s
[alice/1 ddcc1f1] Something good
```
``` (stderr)
$ git push -o no-sync rad ddcc1f164eacfd7dba41da9bff3261da3ee79fd3:refs/heads/alice/2
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new branch] ddcc1f164eacfd7dba41da9bff3261da3ee79fd3 -> alice/2
```
-70
View File
@@ -1,70 +0,0 @@
Alice creates an annotated tag and pushed to her `rad` remote:
``` ~alice
$ touch LICENSE
$ git add LICENSE
$ git commit -am "Add LICENSE"
[master 62d19fd] Add LICENSE
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 LICENSE
$ git tag v1.0 -a -m "Release v1.0"
```
``` ~alice (stderr)
$ git push rad v1.0 --tags
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new tag] v1.0 -> v1.0
```
Bob fetches the tag from Alice, by adding her as a remote:
``` ~bob
$ cd heartwood
$ rad remote add z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --name alice
✓ Follow policy updated for z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Remote alice added
✓ Remote-tracking branch alice/master created for z6MknSL…StBU8Vi
```
Bob is able to fetch Alice's tag into his working copy by using the
`--tags` flag:
``` ~bob (stderr)
$ git fetch alice --tags
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new tag] v1.0 -> v1.0
```
Alice forcefully creates a new version of the tag (let's say she made
a mistake):
``` ~alice
$ git commit --allow-empty -m "Release: v1.0"
[master 8260c04] Release: v1.0
$ git tag v1.0 -f -a -m "Release v1.0"
Updated tag 'v1.0' (was be18ed6)
```
``` ~alice (stderr)
$ git push rad v1.0 -f
✓ Synced with 1 node(s)
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+ be18ed6...9dbdebc v1.0 -> v1.0 (forced update)
```
We ensure that Bob is still able to fetch from Alice and get the new
update of the tag:
``` ~bob
$ rad sync -f
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
``` ~bob (stderr)
$ git fetch alice --tags -f
From rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
t [tag update] v1.0 -> v1.0
```
-17
View File
@@ -1,17 +0,0 @@
Note that aliases must not be longer than 32 bytes, or you will get an error.
There are other rules as well:
``` (fail)
$ rad auth --alias "5fad63fe6b339fa92c588d926121bea6240773a7"
✗ Error: rad auth: alias cannot be greater than 32 bytes
```
``` (fail)
$ rad auth --alias "john doe"
✗ Error: rad auth: alias cannot contain whitespace or control characters
```
``` (fail)
$ rad auth --alias ""
✗ Error: rad auth: alias cannot be empty
```
-32
View File
@@ -1,32 +0,0 @@
Initializing a new identity with `rad-auth`.
The example below is run with `RAD_PASSPHRASE` set.
```
$ rad auth --alias "alice"
Initializing your radicle 👾 identity
✓ Creating your Ed25519 keypair...
✓ Your Radicle DID is did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi. This identifies your device. Run `rad self` to show it at all times.
✓ You're all set.
✗ Hint: install ssh-agent to have it fill in your passphrase for you when signing.
To create a Radicle repository, run `rad init` from a Git repository with at least one commit.
To clone a repository, run `rad clone <rid>`. For example, `rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5` clones the Radicle 'heartwood' repository.
To get a list of all commands, run `rad`.
```
You can get the above information at all times using the `self` command:
```
$ rad self --did
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
```
You can also show your alias:
```
$ rad self --alias
alice
```
-55
View File
@@ -1,55 +0,0 @@
When using an open seeding policy, it can be useful to block individual
repositories from being seeded.
For instance, if our default policy is to seed, any unknown repository will
have its policy set to allow seeding:
```
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --policy
Repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji is being seeded with scope `all`
```
Since there is no policy specific to this repository, there's nothing to be
removed.
```
$ rad seed
No seeding policies to show.
```
But if we wanted to prevent this repository from being seeded, while
allowing all other repositories, we could use `rad block`:
```
$ rad block rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji set to 'block'
```
We can see that it is now no longer seeded:
```
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --policy
Repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji is not being seeded
```
And a 'block' policy was added:
```
$ rad seed
╭───────────────────────────────────────────────────────────╮
│ Repository Name Policy Scope │
├───────────────────────────────────────────────────────────┤
│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji block all │
╰───────────────────────────────────────────────────────────╯
```
If we want to reverse the blocking of the RID we can use `rad unblock`:
```
$ rad unblock rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ The 'block' policy for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji is removed
```
```
$ rad seed
No seeding policies to show.
```
@@ -1,23 +0,0 @@
```
$ cd heartwood
```
View the repository configuration:
```
$ cat .git/config
[core]
bare = false
repositoryformatversion = 0
filemode = true
logallrefupdates = true
[push]
default = upstream
[remote "rad"]
url = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
fetch = +refs/heads/*:refs/remotes/rad/*
pushurl = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
[branch "master"]
remote = rad
merge = refs/heads/master
```
@@ -1,25 +0,0 @@
```
$ cd heartwood
```
View the repository configuration:
```
$ cat .git/config
[core]
bare = false
repositoryformatversion = 0
filemode = true
ignorecase = true
precomposeunicode = true
logallrefupdates = true
[push]
default = upstream
[remote "rad"]
url = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
fetch = +refs/heads/*:refs/remotes/rad/*
pushurl = rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
[branch "master"]
remote = rad
merge = refs/heads/master
```
-53
View File
@@ -1,53 +0,0 @@
With the `rad checkout` command, you can create a new working copy from an
existing project.
```
$ rad checkout rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Repository checkout successful under ./heartwood
```
Let's have a look at what the command did. Navigate to the working copy:
```
$ cd heartwood
```
Check the README:
```
$ cat README
Hello World!
```
Check the repository status:
```
$ git status
On branch master
Your branch is up to date with 'rad/master'.
nothing to commit, working tree clean
```
Check the remote configuration:
```
$ git remote --verbose
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji (fetch)
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
```
List the branches:
```
$ git branch --all
* master
remotes/rad/master
```
List the references:
```
$ git show-ref
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/heads/master
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 refs/remotes/rad/master
```
-67
View File
@@ -1,67 +0,0 @@
We cannot delete a repository, since that can cause data integrity
issues. However, we can clean the storage of remotes that are not the
local peer or the repository delegates. To do this we can use the `rad
clean` command.
First let's look at what we have locally:
``` ~alice
$ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
Let's also inspect what remotes are in the repository:
``` ~alice
$ rad inspect --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi f209c9f68aa689af24220a20462e13ee9dfb2a95
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 161b775a3509c8098de67f57f750972bba015b31
```
Now let's clean the `heartwood` project:
``` ~alice
$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
Removed z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
```
Inspecting the remotes again, we see that Bob is now gone:
``` ~alice
$ rad inspect --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi f209c9f68aa689af24220a20462e13ee9dfb2a95
```
Note that Bob will be fetched again if we do not untrack his
node. Currently, there is no per repository tracking so it's not
possible to stop fetching Bob for this particular repository.
Cleaning a repository again will remove no remotes, since we're
already at the minimal set of remotes:
``` ~alice
$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
```
Since Eve did not fork the repository, and has no refs of their own,
when they run `rad clean` it will remove the project entirely:
``` ~eve
$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
Removed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
```
And attempting to clean the repository again, or any non-existent
repository, has no effect on the storage at all:
``` ~eve (fail)
$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
✗ Error: repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji was not found
```
-91
View File
@@ -1,91 +0,0 @@
```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope all
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
```
We can now have a look at the new working copy that was created from the cloned
repository:
```
$ cd heartwood
$ cat README
Hello World!
```
Let's check that we have Bob and Alice's namespaces in storage:
```
$ rad inspect --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
   ├── cobs
   │   └── xyz.radicle.id
   │      └── [...]
   ├── heads
   │   └── master
   └── rad
      ├── id
      └── sigrefs
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
└── refs
   ├── heads
   │   └── master
   └── rad
      └── sigrefs
```
We can then setup a git remote for `bob`:
```
$ rad remote add z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --name bob --no-sync --fetch
✓ Remote bob added
✓ Remote-tracking branch bob/master created for z6Mkt67…v4N1tRk
$ git branch --remotes
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
bob/master
rad/master
```
We can also create our own fork just by pushing:
``` (stderr)
$ git push -o no-sync rad master
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
* [new branch] master -> master
```
```
$ rad inspect --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
   ├── cobs
   │   └── xyz.radicle.id
   │      └── [...]
   ├── heads
   │   └── master
   └── rad
      ├── id
      └── sigrefs
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
└── refs
   ├── heads
   │   └── master
   └── rad
      └── sigrefs
z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
└── refs
   ├── heads
   │   └── master
   └── rad
      └── sigrefs
```
-21
View File
@@ -1,21 +0,0 @@
If we're not connecting to seed nodes when cloning, the `clone` command will
automatically connect to the necessary seeds.
```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
✓ Connecting to z6Mkt67…v4N1tRk@[..]
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Connecting to z6MknSL…StBU8Vi@[..]
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
```
@@ -1,27 +0,0 @@
We can specify where a repository gets cloned into on our filesystem
by specifying the directory in the `rad clone` command:
```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed Developer/Radicle
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Creating checkout in ./Developer/Radicle..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/Developer/Radicle/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd [..]/Developer/Radicle` to go to the repository directory.
```
Note that attempting to clone into a directory that already exists,
and is not empty, will fail:
``` (fail)
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed Developer/Radicle
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✗ Error: the directory path "Developer/Radicle" already exists
```
@@ -1,32 +0,0 @@
Eve knows about three seeds.
```
$ rad node routing
╭─────────────────────────────────────────────────────╮
│ RID NID │
├─────────────────────────────────────────────────────┤
│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6MknSL…StBU8Vi │
│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6MksFq…bS9wzpT │
│ rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji z6Mkt67…v4N1tRk │
╰─────────────────────────────────────────────────────╯
```
When she tries to clone, one of those will fail to fetch. But the clone command
still returns successfully.
```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --timeout 3
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. error: failed to perform fetch handshake
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✗ Connecting to z6MksFq…bS9wzpT@[..].. error: connection reset
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
```
@@ -1,7 +0,0 @@
Trying to clone a repository that is not in our routing table returns an error:
``` (fail)
$ rad clone rad:zVNuptPuk5XauitpCWSNVCXGGfXW --scope followed
✓ Seeding policy updated for rad:zVNuptPuk5XauitpCWSNVCXGGfXW with scope 'followed'
✗ Error: no seeds found for rad:zVNuptPuk5XauitpCWSNVCXGGfXW
```
-74
View File
@@ -1,74 +0,0 @@
To create a local copy of a repository on the radicle network, we use the
`clone` command, followed by the identifier or *RID* of the repository:
```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
```
We can now have a look at the new working copy that was created from the cloned
repository:
```
$ cd heartwood
$ ls
README
$ cat README
Hello World!
$ git status
On branch master
Your branch is up to date with 'rad/master'.
nothing to commit, working tree clean
```
Let's check that the remote tracking branch was setup correctly:
```
$ git branch --remotes
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master
rad/master
```
The first branch is ours, and the second points to the repository delegate.
We can also take a look at the remotes:
```
$ git remote -v
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (fetch)
alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (push)
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji (fetch)
rad rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (push)
```
Let's check the last commit!
```
$ git log -n 1
commit f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
Author: anonymous <anonymous@radicle.xyz>
Date: Mon Jan 1 14:39:16 2018 +0000
Second commit
```
Cloned repositories show up in `rad ls`:
```
$ rad ls --seeded
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
-141
View File
@@ -1,141 +0,0 @@
Handle arbitrary COBs.
First create an issue.
```
$ rad issue open --title "flux capacitor underpowered" --description "Flux capacitor power requirements exceed current supply" --no-announce
╭─────────────────────────────────────────────────────────╮
│ Title flux capacitor underpowered │
│ Issue d87dcfe8c2b3200e78b128d9b959cfdf7063fefe │
│ Author alice (you) │
│ Status open │
│ │
│ Flux capacitor power requirements exceed current supply │
╰─────────────────────────────────────────────────────────╯
```
The issue is now listed under our project.
```
$ rad issue list
╭──────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │
├──────────────────────────────────────────────────────────────────────────────────────────┤
│ ● d87dcfe flux capacitor underpowered alice (you) now │
╰──────────────────────────────────────────────────────────────────────────────────────────╯
```
Let's create a patch, too.
```
$ git checkout -b flux-capacitor-power
$ touch REQUIREMENTS
$ git add REQUIREMENTS
$ git commit -v -m "Define power requirements"
[flux-capacitor-power 3e674d1] Define power requirements
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 REQUIREMENTS
$ git push rad -o patch.message="Define power requirements" -o patch.message="See details." HEAD:refs/patches
```
Patch can be listed.
```
$ rad patch
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● aa45913 Define power requirements alice (you) - 3e674d1 +0 -0 now │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
Both issue and patch COBs can be listed.
```
$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue
d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch
aa45913e757cacd46972733bddee5472c78fa32a
```
We can look at the issue COB.
```
$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
resource 0656c217f917c3e06234771e9ecae53aba5e173e
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
"body": "Flux capacitor power requirements exceed current supply",
"type": "comment"
}
{
"title": "flux capacitor underpowered",
"type": "edit"
}
```
We can look at the patch COB too.
```
$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object aa45913e757cacd46972733bddee5472c78fa32a
commit aa45913e757cacd46972733bddee5472c78fa32a
resource 0656c217f917c3e06234771e9ecae53aba5e173e
rel 3e674d1a1df90807e934f9ae5da2591dd6848a33
rel f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
"base": "f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354",
"description": "See details.",
"oid": "3e674d1a1df90807e934f9ae5da2591dd6848a33",
"type": "revision"
}
{
"target": "delegates",
"title": "Define power requirements",
"type": "edit"
}
```
Finally let's updated the issue and see the `parent` header:
```
$ rad issue label d87dcfe --add bug --no-announce
$ rad cob log --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
commit abec0a9f3c945594c4e78d24d8ec679e56b22b79
resource 0656c217f917c3e06234771e9ecae53aba5e173e
parent d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
"labels": [
"bug"
],
"type": "label"
}
commit d87dcfe8c2b3200e78b128d9b959cfdf7063fefe
resource 0656c217f917c3e06234771e9ecae53aba5e173e
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
"body": "Flux capacitor power requirements exceed current supply",
"type": "comment"
}
{
"title": "flux capacitor underpowered",
"type": "edit"
}
```
-184
View File
@@ -1,184 +0,0 @@
Well known COBs, for example issues and patches, can not only be showed via porcelain commands such as
`rad issue show` and `rad patch show`, but also using the plumbing command `rad cob show`.
While humans likely prefer to use `rad issue show` and `rad patch show`, this command makes integration
with other software components easier.
First create an issue.
```
$ rad issue open --title "spice harvester broken" --description "Fremen have attacked, maybe we went too far?" --no-announce
╭──────────────────────────────────────────────────╮
│ Title spice harvester broken │
│ Issue 9de644864342d7a505eb8d58d1ef20e5bb05de2e │
│ Author alice (you) │
│ Status open │
│ │
│ Fremen have attacked, maybe we went too far? │
╰──────────────────────────────────────────────────╯
```
The issue is now listed under our project.
```
$ rad issue list
╭─────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Labels Assignees Opened │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ ● 9de6448 spice harvester broken alice (you) now │
╰─────────────────────────────────────────────────────────────────────────────────────╯
```
Let's create a patch, too.
```
$ git checkout -b spice-harvester-broken
$ touch TREATY.md
$ git add TREATY.md
$ git commit -v -m "Start drafting peace treaty"
[spice-harvester-broken 575ed68] Start drafting peace treaty
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 TREATY.md
$ git push rad -o patch.message="Start drafting peace treaty" -o patch.message="See details." HEAD:refs/patches
```
Patch can be listed.
```
$ rad patch
╭───────────────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Reviews Head + - Updated │
├───────────────────────────────────────────────────────────────────────────────────────────┤
│ ● d1f7f86 Start drafting peace treaty alice (you) - 575ed68 +0 -0 now │
╰───────────────────────────────────────────────────────────────────────────────────────────╯
```
Both issue and patch COBs can be listed.
```
$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue
9de644864342d7a505eb8d58d1ef20e5bb05de2e
$ rad cob list --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch
d1f7f869fde9fac19c1779c4c2e77e8361333f91
```
We can show the issue COB.
```
$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object 9de644864342d7a505eb8d58d1ef20e5bb05de2e
{
"assignees": [],
"title": "spice harvester broken",
"state": {
"status": "open"
},
"labels": [],
"thread": {
"comments": {
"9de644864342d7a505eb8d58d1ef20e5bb05de2e": {
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"reactions": [],
"resolved": false,
"body": "Fremen have attacked, maybe we went too far?",
"edits": [
{
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"timestamp": 1671125284000,
"body": "Fremen have attacked, maybe we went too far?",
"embeds": []
}
]
}
},
"timeline": [
"9de644864342d7a505eb8d58d1ef20e5bb05de2e"
]
}
}
```
We can show the patch COB too.
```
$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.patch --object d1f7f869fde9fac19c1779c4c2e77e8361333f91
{
"title": "Start drafting peace treaty",
"author": {
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
},
"state": {
"status": "open"
},
"target": "delegates",
"labels": [],
"merges": {},
"revisions": {
"d1f7f869fde9fac19c1779c4c2e77e8361333f91": {
"id": "d1f7f869fde9fac19c1779c4c2e77e8361333f91",
"author": {
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
},
"description": [
{
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"timestamp": 1671125284000,
"body": "See details.",
"embeds": []
}
],
"base": "f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354",
"oid": "575ed68c716d6aae81ea6b718fd9ac66a8eae532",
"discussion": {
"comments": {},
"timeline": []
},
"reviews": {},
"timestamp": 1671125284000,
"resolves": [],
"reactions": []
}
},
"assignees": [],
"timeline": [
"d1f7f869fde9fac19c1779c4c2e77e8361333f91"
],
"reviews": {}
}
```
Finally let's update the issue and see the output of `rad cob show` also changes.
```
$ rad issue label 9de6448 --add bug --no-announce
$ rad cob show --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --type xyz.radicle.issue --object 9de644864342d7a505eb8d58d1ef20e5bb05de2e
{
"assignees": [],
"title": "spice harvester broken",
"state": {
"status": "open"
},
"labels": [
"bug"
],
"thread": {
"comments": {
"9de644864342d7a505eb8d58d1ef20e5bb05de2e": {
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"reactions": [],
"resolved": false,
"body": "Fremen have attacked, maybe we went too far?",
"edits": [
{
"author": "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"timestamp": 1671125284000,
"body": "Fremen have attacked, maybe we went too far?",
"embeds": []
}
]
}
},
"timeline": [
"9de644864342d7a505eb8d58d1ef20e5bb05de2e"
]
}
}
```
-70
View File
@@ -1,70 +0,0 @@
The `rad config` command is used to manage the local user configuration.
In its simplest form, `rad config` prints the current configuration.
```
$ rad config
{
"publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path",
"preferredSeeds": [
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776",
"z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz@seed.radicle.xyz:8776"
],
"web": {
"pinned": {
"repositories": []
}
},
"cli": {
"hints": true
},
"node": {
"alias": "alice",
"listen": [],
"peers": {
"type": "dynamic"
},
"connect": [],
"externalAddresses": [],
"network": "main",
"log": "INFO",
"relay": "auto",
"limits": {
"routingMaxSize": 1000,
"routingMaxAge": 604800,
"gossipMaxAge": 1209600,
"fetchConcurrency": 1,
"maxOpenFiles": 4096,
"rate": {
"inbound": {
"fillRate": 5.0,
"capacity": 1024
},
"outbound": {
"fillRate": 10.0,
"capacity": 2048
}
},
"connection": {
"inbound": 128,
"outbound": 16
}
},
"workers": 8,
"seedingPolicy": {
"default": "block"
}
}
}
```
You can also get any value in the configuration by path, eg.
```
$ rad config get node.alias
alice
$ rad config get preferredSeeds
z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776
z6MksmpU5b1dS7oaqF2bHXhQi1DWy2hB7Mh9CuN7y1DN6QSz@seed.radicle.xyz:8776
$ rad config get node.limits.routingMaxSize
1000
```
-159
View File
@@ -1,159 +0,0 @@
Exploring `rad diff`.
``` ./main.c
#include <stdio.h>
int main(void) {
printf("Hello World!\n");
return 0;
}
```
```
$ ls
README
main.c
```
```
$ git mv README README.md
$ git add main.c
$ git commit -m "Make changes"
[master 5f771e0] Make changes
2 files changed, 6 insertions(+)
rename README => README.md (100%)
create mode 100644 main.c
```
```
$ rad diff HEAD^ HEAD
╭────────────────────────────────────────────╮
│ README -> README.md ❲moved❳ │
╰────────────────────────────────────────────╯
╭────────────────────────────────────────────╮
│ main.c +6 ❲created❳ │
├────────────────────────────────────────────┤
│ @@ -0,0 +1,6 @@ │
│ 1 + #include <stdio.h> │
│ 2 + │
│ 3 + int main(void) { │
│ 4 + printf("Hello World!/n"); │
│ 5 + return 0; │
│ 6 + } │
╰────────────────────────────────────────────╯
```
```
$ sed -i 's/Hello World/Hello Radicle/' main.c
$ rad diff
╭──────────────────────────────────────────────╮
│ main.c -1 +1 │
├──────────────────────────────────────────────┤
│ @@ -1,6 +1,6 @@ │
│ 1 1 #include <stdio.h> │
│ 2 2 │
│ 3 3 int main(void) { │
│ 4 - printf("Hello World!/n"); │
│ 4 + printf("Hello Radicle!/n"); │
│ 5 5 return 0; │
│ 6 6 } │
╰──────────────────────────────────────────────╯
```
```
$ git add main.c
$ rad diff
$ rad diff --staged
╭──────────────────────────────────────────────╮
│ main.c -1 +1 │
├──────────────────────────────────────────────┤
│ @@ -1,6 +1,6 @@ │
│ 1 1 #include <stdio.h> │
│ 2 2 │
│ 3 3 int main(void) { │
│ 4 - printf("Hello World!/n"); │
│ 4 + printf("Hello Radicle!/n"); │
│ 5 5 return 0; │
│ 6 6 } │
╰──────────────────────────────────────────────╯
```
```
$ git rm -f -q main.c
$ rad diff --staged
╭────────────────────────────────────────────╮
│ main.c -6 ❲deleted❳ │
├────────────────────────────────────────────┤
│ @@ -1,6 +0,0 @@ │
│ 1 - #include <stdio.h> │
│ 2 - │
│ 3 - int main(void) { │
│ 4 - printf("Hello World!/n"); │
│ 5 - return 0; │
│ 6 - } │
╰────────────────────────────────────────────╯
```
For now, copies are not detected.
```
$ git reset --hard master -q
$ mkdir docs
$ cp README.md docs/README.md
$ git add docs
$ rad diff --staged
╭─────────────────────────────╮
│ docs/README.md +1 ❲created❳ │
├─────────────────────────────┤
│ @@ -0,0 +1,1 @@ │
│ 1 + Hello World! │
╰─────────────────────────────╯
$ git reset
$ git checkout .
```
Empty file.
```
$ touch EMPTY
$ git add EMPTY
$ rad diff --staged
╭─────────────────╮
│ EMPTY ❲created❳ │
╰─────────────────╯
$ git reset
$ git checkout .
```
File mode change.
```
$ chmod +x README.md
$ rad diff
╭───────────────────────────────────────────╮
│ README.md 100644 -> 100755 ❲mode changed❳ │
╰───────────────────────────────────────────╯
$ git reset -q
$ git checkout .
```
Binary file.
```
$ touch file.bin
$ truncate -s 8 file.bin
$ git add file.bin
$ rad diff --staged
╭─────────────────────────────╮
│ file.bin ❲binary❳ ❲created❳ │
╰─────────────────────────────╯
```
-27
View File
@@ -1,27 +0,0 @@
Using `rad clone` is useful if we want to create and fetch a project
that exists on Radicle, but perhaps we're in a scenario where we may
already have an existing Git repository and so a full clone is not
necessary.
Instead, we want to fetch the project from the network into our local
storage. In this scenario, we know that the project is
`rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji`. In order to fetch it, we first
have to update our seeding policy for the project.
```
$ rad seed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-fetch
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
```
Now that the project is seeding we can fetch it and we will have it in
our local storage. Note that the `seed` command can also be told to fetch
by passing the `--fetch` option.
```
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
However, we don't have a local fork of the project. We can follow this
up with [rad-fork](rad-fork.md).
-52
View File
@@ -1,52 +0,0 @@
If we have fetched a project, then we do not have a fork of the
repository in the storage, i.e. there is no ref hierarchy for our
NID. This is demonstrated below where our NID is
`z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk`:
```
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
   ├── cobs
   │   └── xyz.radicle.id
   │      └── [...]
   ├── heads
   │   └── master
   └── rad
      ├── id
      └── sigrefs
```
To remedy this, we can use the `rad fork` command for the project we
wish to fork:
```
$ rad fork rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Forked repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
```
Now, if we `rad inspect` the project's refs again we will see that we
have a copy of the main set of refs:
```
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
   ├── cobs
   │   └── xyz.radicle.id
   │      └── [...]
   ├── heads
   │   └── master
   └── rad
      ├── id
      └── sigrefs
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
└── refs
   ├── heads
   │   └── master
   └── rad
      └── sigrefs
```
We are now able to setup a remote in our own working copy of the
project and push to our own fork.
@@ -1,189 +0,0 @@
It's common that in projects where there are already multiple
delegates that one of those delegates meets someone that they want to
bring into the project. So let's see how Alice and Bob end up inviting
Eve to the project.
First, we'll start off with Alice adding Bob. It's necessary for Bob
to have a fork of the project and Alice must be aware of the fork:
``` ~bob
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
$ cd ./heartwood
$ git push rad master
```
If Alice wants to ensure that both her and Bob need to agree on merges
to the default branch, she must set the `threshold` to `2` when adding
Bob as a delegate:
``` ~alice (fails)
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
✗ Error: failed to verify delegates for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✗ Error: a threshold of 2 delegates cannot be met, found 1 delegate(s) and the following delegates are missing [did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk]
✗ Hint: run `rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk` to follow this missing peer
✗ Hint: run `rad sync -f` to attempt to fetch the newly followed peers
✗ Error: fatal: refusing to update identity document
```
We can see that `a threshold of 2 delegates cannot be met` when Alice
attempts to do this. This is because she requires Bob's default branch
to ensure that the threshold can be met and the canonical version of
the default branch (`refs/heads/<default branch>` at the top-level of
the storage) can be updated.
So, instead Alice needs to first follow Bob and fetch his references:
``` ~alice
$ rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --alias bob
✓ Follow policy updated for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
$ rad sync
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Fetched repository from 2 seed(s)
✓ Synced with 1 node(s)
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
069e7d58faa9a7473d27f5510d676af33282796f
$ rad sync
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
```
Bob can confirm that he was made a delegate by fetching the update:
``` ~bob
$ rad sync
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Fetched repository from 2 seed(s)
✓ Synced with 1 node(s)
$ rad inspect --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
```
Later, Bob meets Eve at a conference and she wants to take part in the
project. For Bob to propose Eve, similar steps need to happen as
between Alice and Bob. Eve first needs to setup a fork:
``` ~eve
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --scope followed
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed'
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Remote bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk added
✓ Remote-tracking branch bob@z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk/master created for z6Mkt67…v4N1tRk
✓ Repository successfully cloned under [..]/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
$ cd ./heartwood
$ git push rad master
```
Bob then adds Eve as a delegate:
``` ~bob
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --description "" --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --no-confirm -q
3cd3c7f9900de0fcb19705856a7cc339a38fb0b3
$ rad sync
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
```
Notice how there was no need to follow Eve right away in this case?
This is because Bob can meet the threshold of 2 without Eve, he
has Alice and his default reference.
Since there are two delegates when Bob adds Eve, Alice needs to accept
the change to meet a quorum of votes (`votes >= (delegates / 2) + 1`):
``` ~alice
$ rad sync
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetched repository from 2 seed(s)
✓ Nothing to announce, already in sync with 3 node(s) (see `rad sync status`)
$ rad id list
╭────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├────────────────────────────────────────────────────────────────────────────────┤
│ ● 3cd3c7f Add Eve bob z6Mkt67…v4N1tRk active now │
│ ● 069e7d5 Add Bob alice (you) accepted now │
│ ● 0656c21 Initial revision alice (you) accepted now │
╰────────────────────────────────────────────────────────────────────────────────╯
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 6acd3b370839318d96dbfff43948bab2bcdd3681
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk c40018821dc1b41cad75e91e0c9d00827e815324
$ rad id accept 3cd3c7f
✓ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 accepted
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Eve │
│ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 │
│ Blob 74581605d1f75396c331487a10ca61c4815ed685 │
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ State accepted │
│ Quorum yes │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
│ ✓ did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob │
╰────────────────────────────────────────────────────────────────────────╯
```
At this point, when Alice runs `rad sync`, she will fetch Eve's fork
since she has become a delegate:
``` ~alice
$ rad sync --timeout 3
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 1f716870f890be0c13fdd0af9f527af849fec792
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk c40018821dc1b41cad75e91e0c9d00827e815324
z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z 95cd447c57de8d232c6154f5dba0451aa593520e
```
Since the network is eventually consistent, if Eve decides to `sync`
(this could also happen through a transient announcement), then we can
see that both seeds are `synced`:
``` ~eve
$ rad sync
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp..
✓ Fetched repository from 2 seed(s)
✓ Synced with 3 node(s)
$ rad sync status
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ● Node Address Status Tip Timestamp │
├─────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ● eve (you) 95cd447 now │
│ ● bob z6Mkt67…v4N1tRk synced 95cd447 now │
│ ● distrustful z6MkvVv…Z1Ct4tD distrustful.radicle.xyz:8776 synced 95cd447 now │
│ ● seed z6MkuPZ…xEuaPUp seed.radicle.xyz:8776 synced 95cd447 now │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
```
-104
View File
@@ -1,104 +0,0 @@
First let's add Bob as a delegate, and sync the changes to Bob:
``` ~alice
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2 -q
0ca42d376bd566631083c8913cf86bec722da392
```
``` ~bob
$ cd heartwood
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
One thing that can happen is that two delegates propose a revision at the same
time:
``` ~alice
$ rad id update --title "Edit project name" --description "" --payload "xyz.radicle.project" "name" '"heart"' -q
12d7300d1bbba84e4e5760c8c61999bf5fefb81a
```
``` ~bob
$ rad id update --title "Edit project name" --description "" --payload "xyz.radicle.project" "name" '"wood"' -q
89b2623e7f2ddf5748661b15b9975ab0b4ee17ab
```
When Alice syncs with Bob, she notices the problem: there are two active
revisions.
``` ~alice
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Fetched repository from 1 seed(s)
$ rad id list
╭─────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├─────────────────────────────────────────────────────────────────────────────────┤
│ ● 89b2623 Edit project name bob z6Mkt67…v4N1tRk active now │
│ ● 12d7300 Edit project name alice (you) active now │
│ ● 0ca42d3 Add Bob alice (you) accepted now │
│ ● 0656c21 Initial revision alice (you) accepted now │
╰─────────────────────────────────────────────────────────────────────────────────╯
```
This isn't a problem as long as we don't try to accept both. So let's accept
Bob's:
``` ~alice
$ rad id accept 89b2623 -q
$ rad id list
╭─────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├─────────────────────────────────────────────────────────────────────────────────┤
│ ● 89b2623 Edit project name bob z6Mkt67…v4N1tRk accepted now │
│ ● 12d7300 Edit project name alice (you) stale now │
│ ● 0ca42d3 Add Bob alice (you) accepted now │
│ ● 0656c21 Initial revision alice (you) accepted now │
╰─────────────────────────────────────────────────────────────────────────────────╯
```
Doing so voided the other conflicting revision, and it can no longer be
accepted now.
``` ~bob
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
``` ~bob (fail)
$ rad id accept 12d7300 -q
✗ Error: cannot vote on revision that is stale
$ rad id reject 12d7300 -q
✗ Error: cannot vote on revision that is stale
```
``` ~bob
$ rad id show 12d7300
╭────────────────────────────────────────────────────────────────────────╮
│ Title Edit project name │
│ Revision 12d7300d1bbba84e4e5760c8c61999bf5fefb81a │
│ Blob e93aa3e3c5c448bacd3537a81daf1437eccd046a │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State stale │
│ Quorum no │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice │
│ ? did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob (you) │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,14 +1,14 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
- "name": "heartwood"
+ "name": "heart"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
"threshold": 2
}
```
@@ -1,202 +0,0 @@
``` ~alice
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --description "" --threshold 2 --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm -q
069e7d58faa9a7473d27f5510d676af33282796f
```
``` ~bob
$ rad watch --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --node z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -r 'refs/rad/sigrefs' -t 95cd447c57de8d232c6154f5dba0451aa593520e -i 500 --timeout 5000
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
$ rad id --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
╭────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├────────────────────────────────────────────────────────────────────────────────┤
│ ● 069e7d5 Add Bob alice z6MknSL…StBU8Vi accepted now │
│ ● 0656c21 Initial revision alice z6MknSL…StBU8Vi accepted now │
╰────────────────────────────────────────────────────────────────────────────────╯
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi [..]
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [..]
z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z [..]
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Eve" --description "" --delegate did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z --no-confirm
✓ Identity revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Eve │
│ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 │
│ Blob 74581605d1f75396c331487a10ca61c4815ed685 │
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ State active │
│ Quorum no │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob (you) │
│ ? did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,14 +1,15 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
- "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
+ "did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
],
"threshold": 2
}
```
``` ~alice
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Fetched repository from 2 seed(s)
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
$ rad id accept 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
✓ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 accepted
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Eve │
│ Revision 3cd3c7f9900de0fcb19705856a7cc339a38fb0b3 │
│ Blob 74581605d1f75396c331487a10ca61c4815ed685 │
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
│ State accepted │
│ Quorum yes │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
│ ✓ did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob │
╰────────────────────────────────────────────────────────────────────────╯
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --delegates
did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (alice)
did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (bob)
did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z (eve)
```
``` ~alice
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Make private" --description "" --visibility private --no-confirm -q
e6bf10593b78384eb2b281cbb18a605668a6d1f7
```
We can list all revisions:
``` ~alice
$ rad id list
╭────────────────────────────────────────────────────────────────────────────────╮
│ ● ID Title Author Status Created │
├────────────────────────────────────────────────────────────────────────────────┤
│ ● e6bf105 Make private alice (you) active now │
│ ● 3cd3c7f Add Eve bob z6Mkt67…v4N1tRk accepted now │
│ ● 069e7d5 Add Bob alice (you) accepted now │
│ ● 0656c21 Initial revision alice (you) accepted now │
╰────────────────────────────────────────────────────────────────────────────────╯
```
Despite being a delegate, Bob can't edit or redact Alice's revision:
``` ~bob (fail)
$ rad id redact e6bf10593b78384eb2b281cbb18a605668a6d1f7
[..]
```
``` ~bob (fail)
$ rad id edit --title "Boo!" --description "Boo!" e6bf10593b78384eb2b281cbb18a605668a6d1f7
[..]
```
Alice can edit:
``` ~alice
$ rad id edit --title "Make private" --description "Privacy is cool." e6bf10593b78384eb2b281cbb18a605668a6d1f7
✓ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 edited
$ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
╭────────────────────────────────────────────────────────────────────────╮
│ Title Make private │
│ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 │
│ Blob c533865b2846ca6c5b4436ec6872257293380c3b │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State active │
│ Quorum no │
│ │
│ Privacy is cool. │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
│ ? did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob │
│ ? did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z eve │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,15 +1,18 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
"did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
],
- "threshold": 2
+ "threshold": 2,
+ "visibility": {
+ "type": "private"
+ }
}
```
And she can redact her revision:
``` ~alice
$ rad id redact e6bf10593b78384eb2b281cbb18a605668a6d1f7
✓ Revision e6bf10593b78384eb2b281cbb18a605668a6d1f7 redacted
```
``` ~alice (fail)
$ rad id show e6bf10593b78384eb2b281cbb18a605668a6d1f7
✗ Error: revision `e6bf10593b78384eb2b281cbb18a605668a6d1f7` not found
```
Finally, Alice can also propose to remove Bob:
``` ~alice
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Remove Bob" --description "" --rescind did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm
✓ Identity revision 8ba242a80bc1181f41f9ea7a19286038c7948994 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Remove Bob │
│ Revision 8ba242a80bc1181f41f9ea7a19286038c7948994 │
│ Blob 254d62de237117e7d7b9ceff85c47f5e3b610c1e │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State active │
│ Quorum no │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
│ ? did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk bob │
│ ? did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z eve │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,15 +1,14 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
- "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
"did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
],
"threshold": 2
}
```
-100
View File
@@ -1,100 +0,0 @@
When we are working with a private repository, we can modify the list of peers
we allow by using the `rad id` command with its `--allow` and `--disallow`
options. Both options can be specified multiple times in the same command line call:
Here we will add Bob and Eve's DIDs to the `allow`list:
```
$ rad id update --title "Allow Bob & Eve" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --allow did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -q
...
$ rad inspect --identity
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "radicle heartwood protocol & stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1,
"visibility": {
"type": "private",
"allow": [
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk",
"did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z"
]
}
}
```
To remove a peer's DID, we can use the `--disallow` option. Let's remove both of them again:
```
$ rad id update --title "Remove allow list" --disallow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --disallow did:key:z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
...
$ rad inspect --identity
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "radicle heartwood protocol & stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1,
"visibility": {
"type": "private"
}
}
```
Note that using both `--disallow` and `--allow` with the same DID will result in
an error:
``` (fails)
$ rad id update --title "Remove allow list" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --disallow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
✗ Error: `--allow` and `--disallow` must not overlap: ["did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"]
```
Allowing or disallowing the same peer twice will result in a message saying that
the document is already up to date:
```
$ rad id update --title "Allow Bob" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
...
```
```
$ rad id update --title "Allow Bob" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
Nothing to do. The document is up to date. See `rad inspect --identity`.
```
If we attempt to change the list while also changing the repository to `public`,
then the command will fail since there is no longer an allow list to work with:
``` (fails)
$ rad id update --visibility public --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
✗ Error: `--allow` and `--disallow` cannot be used with `--visibility public`
```
Let's change the repository to `public`:
```
$ rad id update --title "IPO" --visibility public -q
...
```
Now, if we attempt to change the `allow` list we also get an error with a
helpful hint:
``` (fails)
$ rad id update --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
✗ Error: `--allow` and `--disallow` should only be used for private repositories
✗ Hint: use `--visibility private` to make the repository private, or perhaps you meant to use `--delegate`/`--rescind`
```
@@ -1,48 +0,0 @@
In some cases, a peer can create references, which includes `rad/sigrefs`,
without having pushed the canonical default branch. For example, Bob can create
an issue in the repository:
``` ~bob
$ rad issue open --title "Add Bob as a delegate" --description "We agreed to add me as a delegate, so I am creating an issue to track that work" --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
╭──────────────────────────────────────────────────────────────╮
│ Title Add Bob as a delegate │
│ Issue f12d512c51d30429f7916db038ae0360e2e938c2 │
│ Author bob (you) │
│ Status open │
│ │
│ We agreed to add me as a delegate, so I am creating an issue │
│ to track that work │
╰──────────────────────────────────────────────────────────────╯
✓ Synced with 1 node(s)
```
and if we inspect Alice's references, then we will see the following:
``` ~alice
$ rad inspect --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
   ├── cobs
   │   └── xyz.radicle.id
   │      └── 0656c217f917c3e06234771e9ecae53aba5e173e
   ├── heads
   │   └── master
   └── rad
      ├── id
      └── sigrefs
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
└── refs
   ├── cobs
   │   └── xyz.radicle.issue
   │      └── f12d512c51d30429f7916db038ae0360e2e938c2
   └── rad
      └── sigrefs
```
Despite not having the canonical branch, Alice should still be able to add Bob
as a delegate, since a threshold of 1 can still be reached:
``` ~alice
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
7be665f9fccba97abb21b2fa85a6fd3181c72858
```
-200
View File
@@ -1,200 +0,0 @@
In the attempt below, to update the identity, we can see that `a
threshold of 2 delegates cannot be met` when Alice attempts to do
this. This is because she requires Bob's default branch to ensure that
the threshold can be met and the canonical version of the default
branch (`refs/heads/<default branch>` at the top-level of the storage)
can be updated.
``` ~alice (fail)
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2
✗ Error: failed to verify delegates for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✗ Error: the threshold of 2 delegates cannot be met..
✗ Error: the delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk is missing
✗ Hint: run `rad follow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk` to follow this missing peer
✗ Error: fatal: refusing to update identity document
```
Instead, Alice can simply keep the `threshold` as `1` and still add Bob as a delegate:
``` ~alice
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
✓ Identity revision 7be665f9fccba97abb21b2fa85a6fd3181c72858 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Bob │
│ Revision 7be665f9fccba97abb21b2fa85a6fd3181c72858 │
│ Blob 93d3009787e5d8a481dffc4dd248ea46af592466 │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
│ │
│ Add Bob as a delegate │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,13 +1,14 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
- "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
"threshold": 1
}
```
Alice can still make changes to her working copy, change the canonical
head, and make patches -- as we can see below:
``` ~alice
$ touch REQUIREMENTS
$ git add REQUIREMENTS
$ git commit -v -m "Define power requirements"
[master 3e674d1] Define power requirements
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 REQUIREMENTS
```
``` ~alice (stderr) RAD_SOCKET=/dev/null
$ git push rad master
✓ Canonical head updated to 3e674d1a1df90807e934f9ae5da2591dd6848a33
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
f2de534..3e674d1 master -> master
```
``` ~alice
$ rad sync -a
✓ Synced with 1 node(s)
```
``` ~alice
$ git checkout -b add-readme
$ touch README.md
$ git add README.md
$ git commit -v -m "Add README file"
[add-readme 964513c] Add README file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
```
``` ~alice (stderr) RAD_SOCKET=/dev/null
$ git push rad HEAD:refs/patches
✓ Patch b09b2aa0ee055671c811e9ad4ba73eed211ebaa3 opened
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
Any other seeds can also still fetch changes from Alice without any
errors:
``` ~seed
$ rad sync rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji -f
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
We can also inspect the repository to ensure all the data is
consistent with the change made to the identity.
The identity has the new delegate, while the `threshold` is still `1`
``` ~alice
$ rad inspect --identity
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
"threshold": 1
}
```
Alice only sees her refs, since she has not synced with Bob's
references yet:
``` ~alice
$ rad inspect --refs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
└── refs
   ├── cobs
   │   ├── xyz.radicle.id
   │   │   └── 0656c217f917c3e06234771e9ecae53aba5e173e
   │   └── xyz.radicle.patch
   │      └── b09b2aa0ee055671c811e9ad4ba73eed211ebaa3
   ├── heads
   │   ├── master
   │   └── patches
   │      └── b09b2aa0ee055671c811e9ad4ba73eed211ebaa3
   └── rad
      ├── id
      └── sigrefs
```
Similarly, she still does not have Bob's `rad/sigrefs`:
``` ~alice
$ rad inspect --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7ffed605e4871bb0640ee1538181640e239b182c
```
And she can still list the project, without any worries:
``` ~alice
$ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public 3e674d1 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
Once Bob clones the repository and creates a fork, i.e. creates a
branch to `refs/heads/master` for this project, she can then use `rad
sync` and fetch his references:
``` ~bob
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [..]/bob/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 0 issues · 1 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
```
``` ~bob
$ cd heartwood
$ rad fork
✓ Forked repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji for z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
```
``` ~alice
$ rad sync -f
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Fetched repository from 2 seed(s)
$ rad inspect --sigrefs
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7ffed605e4871bb0640ee1538181640e239b182c
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk cc068d93ee77dc134518d7d0fbe55b39804baf53
```
@@ -1,47 +0,0 @@
The payloads in the identity document are extensible and arbitrary fields can be
added. Here we will add an emoji alias for the heartwood project:
```
$ rad id update --title "Add emoji alias" --description "Adding alias field" --payload xyz.radicle.project alias '"❤️🪵"'
✓ Identity revision 05100d3f0a73b9373681677158615a53ba51940e created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add emoji alias │
│ Revision 05100d3f0a73b9373681677158615a53ba51940e │
│ Blob a0f421c928dcfc6eca129fc2ea1f50877de7dc20 │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
│ │
│ Adding alias field │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,13 +1,14 @@
{
"payload": {
"xyz.radicle.project": {
+ "alias": "❤️🪵",
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1
}
```
We can see that the project payload still loads by using `rad ls` which will
attempt to deserialize the payload:
```
$ rad ls
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
@@ -1,78 +0,0 @@
Let's add a payload field and then delete it.
```
$ rad id update --title "Add field" --description "Add a new 'web' field" --payload xyz.radicle.project web '"https://acme.example"'
✓ Identity revision a8a9fee6c4f83578ab132d375f1da0c81282bef3 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add field │
│ Revision a8a9fee6c4f83578ab132d375f1da0c81282bef3 │
│ Blob fbe268d13e60f1f3a1972e0ccd592f3cdecf08b5 │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
│ │
│ Add a new 'web' field │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,13 +1,14 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
- "name": "heartwood"
+ "name": "heartwood",
+ "web": "https://acme.example"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1
}
```
Now let's delete it by setting it to `null`.
```
$ rad id update --title "Delete field" --description "Delete 'web'" --payload xyz.radicle.project web null
✓ Identity revision d373c35876833105f8aafed8b610660b5737cd67 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Delete field │
│ Revision d373c35876833105f8aafed8b610660b5737cd67 │
│ Blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
│ │
│ Delete 'web' │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,14 +1,13 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
- "name": "heartwood",
- "web": "https://acme.example"
+ "name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1
}
```
Note that we cannot delete mandatory fields:
``` (fails)
$ rad id update --title "Delete default branch" --payload xyz.radicle.project defaultBranch null
✗ Error: failed to verify `xyz.radicle.project`, json: missing field `defaultBranch`
```
-182
View File
@@ -1,182 +0,0 @@
At some point in the lifetime of a Radicle project you may want to
collaborate with someone else allowing them to become a project
maintainer. This requires adding them as a `delegate` and possibly
editing the `threshold` for passing new changes to the identity of the
project.
For cases where `threshold > 1`, it is necessary to gather a quorum of
signatures to update the Radicle identity. To do this, we use the `rad id`
command. For now, since we are the only delegate, and `treshold` is `1`, we
can update the identity ourselves.
Let's add Bob as a delegate using their DID,
`did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk`, and update the
threshold to `2`.
```
$ rad id update --title "Add Bob" --description "Add Bob as a delegate" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --threshold 2
✓ Identity revision 0ca42d376bd566631083c8913cf86bec722da392 created
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Bob │
│ Revision 0ca42d376bd566631083c8913cf86bec722da392 │
│ Blob 053541ba7b90534b35dd8718e0ceaa408979b02b │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
│ │
│ Add Bob as a delegate │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,13 +1,14 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
- "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
- "threshold": 1
+ "threshold": 2
}
```
Before moving on, let's take a few notes on this output. The first
thing we'll notice is that the difference between the current identity
document and the proposed changes are shown. Specifically, we changed
the delegates and threshold:
"delegates": [
- "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
...
- "threshold": 1
+ "threshold": 2
Next we have the number of signatures from delegates, which includes our own:
✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
Finally, we can see whether the `Quorum` was reached:
Quorum yes
Since the threshold was previously `1`, this change is now in effect. We
can verify that by listing the current identity document:
```
$ rad inspect --identity
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
"threshold": 2
}
```
We can also look at the document's COB directly:
```
$ rad cob log --object 0656c217f917c3e06234771e9ecae53aba5e173e --type xyz.radicle.id --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
commit 0ca42d376bd566631083c8913cf86bec722da392
parent 0656c217f917c3e06234771e9ecae53aba5e173e
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
"blob": "053541ba7b90534b35dd8718e0ceaa408979b02b",
"description": "Add Bob as a delegate",
"parent": "0656c217f917c3e06234771e9ecae53aba5e173e",
"signature": "z3AyzixN2eWLtRfQWowtBXwWyRH3iJ8oJ25W6KFYFw5ANLntbzfavge15muNU6AVAUkxSxQvgg9yh2gupbUecavQY",
"title": "Add Bob",
"type": "revision"
}
commit 0656c217f917c3e06234771e9ecae53aba5e173e
author z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
date Thu, 15 Dec 2022 17:28:04 +0000
{
"blob": "d96f425412c9f8ad5d9a9a05c9831d0728e2338d",
"parent": null,
"signature": "z5nGqUvrmfiSyLjNCHWTWYvVMcPUZcvo9TxPKzEKXYBdSgUzbrqf1cYsmpGgbQvYunnsrLSsubEmxZaRdKM4quqQR",
"title": "Initial revision",
"type": "revision"
}
```
We can use `rad id show` to show the changes of an accepted update:
```
$ rad id show 0ca42d376bd566631083c8913cf86bec722da392
╭────────────────────────────────────────────────────────────────────────╮
│ Title Add Bob │
│ Revision 0ca42d376bd566631083c8913cf86bec722da392 │
│ Blob 053541ba7b90534b35dd8718e0ceaa408979b02b │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ State accepted │
│ Quorum yes │
│ │
│ Add Bob as a delegate │
├────────────────────────────────────────────────────────────────────────┤
│ ✓ did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi alice (you) │
╰────────────────────────────────────────────────────────────────────────╯
@@ -1,13 +1,14 @@
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "Radicle Heartwood Protocol & Stack",
"name": "heartwood"
}
},
"delegates": [
- "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
+ "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi",
+ "did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
],
- "threshold": 1
+ "threshold": 2
}
```
Note that once a revision is accepted, it can't be edited, redacted or otherwise
acted upon:
``` (fail)
$ rad id redact 0ca42d376bd566631083c8913cf86bec722da392
✗ Error: [..]
```
``` (fail)
$ rad id reject 0ca42d376bd566631083c8913cf86bec722da392
✗ Error: [..]
```
``` (fail)
$ rad id accept 0ca42d376bd566631083c8913cf86bec722da392
✗ Error: [..]
```
If no updates are specified then we are told that our command had no effect:
```
$ rad id update --title "Update canonical branch" --description "Update the canonical branch to `main`"
Nothing to do. The document is up to date. See `rad inspect --identity`.
```
-140
View File
@@ -1,140 +0,0 @@
``` ~alice
$ cd heartwood
$ rad inbox
Your inbox is empty.
```
``` ~bob
$ cd heartwood
$ rad issue open --title "No license file" --description "..." -q
✓ Synced with 1 node(s)
$ git commit -m "Change copyright" --allow-empty -q
$ git push rad HEAD:bob/copy
$ cd ..
$ cd radicle-git
$ git commit -m "Change copyright" --allow-empty -q
$ git push rad -o patch.message="Copyright fixes" HEAD:refs/patches
```
``` ~alice
$ rad inbox --sort-by id
╭──────────────────────────────────────────────────────────────────────╮
│ heartwood │
├──────────────────────────────────────────────────────────────────────┤
│ 001 ● [ ... ] No license file issue open bob now │
│ 002 ● bob/copy Change copyright branch created bob now │
╰──────────────────────────────────────────────────────────────────────╯
```
``` ~alice
$ rad inbox --all --sort-by id
╭────────────────────────────────────────────────────────────────╮
│ radicle-git │
├────────────────────────────────────────────────────────────────┤
│ 003 ● [ ... ] Copyright fixes patch open bob now │
╰────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────────────────────────────╮
│ heartwood │
├──────────────────────────────────────────────────────────────────────┤
│ 001 ● [ ... ] No license file issue open bob now │
│ 002 ● bob/copy Change copyright branch created bob now │
╰──────────────────────────────────────────────────────────────────────╯
```
``` ~alice
$ rad inbox show 2
commit 141c9073066e3910f1dfe356904a0120542e1cc9
Author: radicle <radicle@localhost>
Date: Thu Dec 15 17:28:04 2022 +0000
Change copyright
commit f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
Author: anonymous <anonymous@radicle.xyz>
Date: Mon Jan 1 14:39:16 2018 +0000
Second commit
commit 08c788dd1be6315de09e3fe09b5b1b7a2b8711d9
Author: anonymous <anonymous@radicle.xyz>
Date: Mon Jan 1 14:39:16 2018 +0000
Initial commit
```
``` ~alice
$ rad inbox list --sort-by id
╭──────────────────────────────────────────────────────────────────────╮
│ heartwood │
├──────────────────────────────────────────────────────────────────────┤
│ 001 ● [ ... ] No license file issue open bob now │
│ 002 bob/copy Change copyright branch created bob now │
╰──────────────────────────────────────────────────────────────────────╯
```
``` ~alice
$ rad inbox show 1
╭──────────────────────────────────────────────────╮
│ Title No license file │
│ Issue [ ... ] │
│ Author bob z6Mkt67…v4N1tRk │
│ Status open │
│ │
│ ... │
╰──────────────────────────────────────────────────╯
```
``` ~alice
$ rad inbox clear 1 2
✓ Cleared 2 item(s) from your inbox
$ rad inbox
Your inbox is empty.
$ rad inbox --all
╭────────────────────────────────────────────────────────────────╮
│ radicle-git │
├────────────────────────────────────────────────────────────────┤
│ 003 ● [ ... ] Copyright fixes patch open bob now │
╰────────────────────────────────────────────────────────────────╯
```
``` ~alice
$ rad inbox clear --all
✓ Cleared 1 item(s) from your inbox
```
``` ~alice
$ rad inbox clear --all
Your inbox is empty.
```
Now let's do an identity update.
``` ~alice
$ rad id update --title "Modify description" --description "Use website" --payload xyz.radicle.project description '"https://radicle.xyz"' -q
[..]
$ rad sync -a
✓ Synced with 1 node(s)
```
``` ~bob
$ rad inbox --all
╭──────────────────────────────────────────────────────────────────────╮
│ heartwood │
├──────────────────────────────────────────────────────────────────────┤
│ 001 ● [ ... ] Modify description id accepted alice now │
╰──────────────────────────────────────────────────────────────────────╯
$ rad inbox show 1
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "https://radicle.xyz",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1
}
```
-40
View File
@@ -1,40 +0,0 @@
Let's clone a regular repository via plain Git:
```
$ git clone $URL heartwood
$ cd heartwood
$ git rev-parse HEAD
f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354
```
We can see it's not a Radicle working copy:
``` (fail)
$ rad .
✗ Error: Current directory is not a Radicle repository
```
Let's pick an existing repository:
```
$ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
```
And initialize this working copy as that existing repository:
```
$ rad init --existing rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Initialized existing repository rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji in [..]/heartwood/..
```
We can confirm that the working copy is initialized:
```
$ rad .
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
$ git remote show rad
* remote rad
Fetch URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
Push URL: rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
HEAD branch: (unknown)
Remote branch:
master new (next fetch will store in remotes/rad)
Local ref configured for 'git push':
master pushes to master (up to date)
```
-22
View File
@@ -1,22 +0,0 @@
If you try to `init` from a directory that doesn't contain a Git repository,
it will fail:
``` (fail)
$ rad init
✗ Error: a Git repository was not found at the given path
```
Ok so let's initialize one.
```
$ git init -q
```
Now we try again.
``` (fail)
$ rad init
✗ Error: repository head must point to a commit
```
Looks like we need a commit.
-29
View File
@@ -1,29 +0,0 @@
If we initialize a public repository without seeding it, it won't be advertized:
```
$ rad init --name heartwood --description "radicle heartwood protocol & stack" --no-confirm --public --no-seed
Initializing public radicle 👾 repository in [..]
✓ Repository heartwood created.
Your Repository ID (RID) is rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK.
You can show it any time by running `rad .` from this directory.
Your repository will be announced to the network when you start your node.
You can start your node with `rad node start`.
To push changes, run `git push`.
```
```
$ rad node inventory
```
If we then seed it, it becomes advertized in our inventory:
```
$ rad seed rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
✓ Inventory updated with rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
✓ Seeding policy updated for rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK with scope 'all'
```
```
$ rad node inventory
rad:zhbMU4DUXrzB8xT6qAJh6yZ7bFMK
```
@@ -1,53 +0,0 @@
Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
Alice allows Bob to fetch it, and Bob, without the updated identity document
is able to fetch it by specifiying Alice as a seed.
``` ~alice
$ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
...
$ rad inspect --identity
{
"payload": {
"xyz.radicle.project": {
"defaultBranch": "master",
"description": "radicle heartwood protocol & stack",
"name": "heartwood"
}
},
"delegates": [
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
],
"threshold": 1,
"visibility": {
"type": "private",
"allow": [
"did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"
]
}
}
```
``` ~bob
$ rad ls --all --private
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi
✓ Repository successfully cloned under [...]/.radicle/heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ radicle heartwood protocol & stack │
│ 0 issues · 0 patches │
╰────────────────────────────────────╯
Run `cd ./heartwood` to go to the repository directory.
```
We can also use `rad seed` to seed and fetch without creating a checkout.
``` ~bob
$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --from z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
✓ Seeding policy exists for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
```
@@ -1,44 +0,0 @@
Given a private repo `rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu` belonging to Alice,
Bob tries to fetch it, and even though he's connected to Alice, it fails.
``` ~bob
$ rad ls
```
``` ~bob (fail)
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. error: failed to perform fetch handshake
✗ Error: repository rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu not found
```
She allows Bob to view the repository. And when she syncs, one node (Bob) gets
the refs.
``` ~alice
$ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
...
$ rad sync --announce --timeout 3
✓ Synced with 1 node(s)
```
Bob can now fetch the private repo without specifying a seed, because he knows
that Alice has the repo after she announced her refs:
``` ~bob
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
$ rad ls --private --all
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ heartwood rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu private f2de534 radicle heartwood protocol & stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
Note that since we don't have our own fork of this repo, omitting the `--all` flag shows nothing:
``` ~bob
$ rad ls --private
Nothing to show.
```
@@ -1,37 +0,0 @@
Let's say we initialize a private repository and specify that we don't want it
to be seeded. This means that the repo will be available locally, to us,
and even if other peers know about it, they won't be able to fetch it
from us.
```
$ rad init --name heartwood --description "radicle heartwood protocol & stack" --no-confirm --private --no-seed
Initializing private radicle 👾 repository in [..]
✓ Repository heartwood created.
Your Repository ID (RID) is rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu.
You can show it any time by running `rad .` from this directory.
You have created a private repository.
This repository will only be visible to you, and to peers you explicitly allow.
To make it public, run `rad publish`.
To push changes, run `git push`.
```
```
$ rad seed
No seeding policies to show.
```
We can decide to seed it later, so that others can fetch it from us, given
that they are part of the allow list:
```
$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
```
But it still won't show up in our inventory, since it's private:
```
$ rad node inventory
```
@@ -1,48 +0,0 @@
Alice allows Bob to fetch this repo, but doesn't announce it, which means
that Bob needs to know to fetch it from Alice.
``` ~alice
$ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q
[..]
```
First, Bob seeds the repo.
``` ~bob
$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --no-fetch
[..]
```
If Bob just tries to fetch it without specifying seeds, he gets an error:
``` ~bob
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch
✗ Error: no seeds found for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu
```
He has to specify a seed that isn't in his routing table:
``` ~bob
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s)
```
``` ~bob
$ rad ls --private --all
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Name RID Visibility Head Description │
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ heartwood rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu private f2de534 radicle heartwood protocol & stack │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
Note that if multiple seeds are specified, the command succeeds as long as one
seed succeeds.
``` ~bob
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --seed z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MkwPU…Ug2mVvx.. error: peer is not connected; cannot initiate fetch
✓ Fetched repository from 1 seed(s)
```

Some files were not shown because too many files have changed in this diff Show More