cli: rework `rad rm` as `rad clean`
Removing a repository completely can result in data corruption for users, since a user's `rad/sigrefs` are the ground truth of their state, and any fork of them results in errors in the protocol. Instead of `rad rm`, we replace it with `rad clean` that removes all remotes other than the local peer's namespace and the delegates'. The delegates are also necessary to keep, since the repository's identity is dependent on them. Note that the remotes that were cleaned will be populated again upon fetching, as long as they are tracked. Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
parent
d23120ca8b
commit
0fb1128bd3
|
|
@ -0,0 +1,58 @@
|
|||
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:
|
||||
|
||||
```
|
||||
$ 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:
|
||||
|
||||
```
|
||||
$ rad inspect --sigrefs
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi f209c9f68aa689af24220a20462e13ee9dfb2a95
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 161b775a3509c8098de67f57f750972bba015b31
|
||||
```
|
||||
|
||||
Now let's clean the `heartwood` project:
|
||||
|
||||
```
|
||||
$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
|
||||
Removed z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||
✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
```
|
||||
|
||||
Inspecting the remotes again, we see that Bob is now gone:
|
||||
|
||||
```
|
||||
$ 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:
|
||||
|
||||
```
|
||||
$ rad clean rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
|
||||
✓ Successfully cleaned rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
```
|
||||
|
||||
And attempting to clean a non-existent repository has no effect on the
|
||||
storage at all:
|
||||
|
||||
``` (fail)
|
||||
$ rad clean rad:z42hL2jL4XNk6K8oHQaSWdeadbeef --no-confirm
|
||||
✗ Error: repository rad:z42hL2jL4XNk6K8oHQaSWdeadbeef was not found
|
||||
```
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
To delete a repository from local storage, we use the `rad rm` command.
|
||||
First let's look at what we have locally:
|
||||
|
||||
```
|
||||
$ rad ls
|
||||
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Name RID Visibility Head Description │
|
||||
├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ heartwood rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji public f2de534 Radicle Heartwood Protocol & Stack │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Now let's delete the `heartwood` project:
|
||||
|
||||
```
|
||||
$ rad rm rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --no-confirm
|
||||
✓ Untracked rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
✓ Successfully removed 'rad' remote
|
||||
✓ Successfully removed rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from storage
|
||||
```
|
||||
|
||||
We can check our repositories again to see if it was deleted:
|
||||
|
||||
```
|
||||
$ rad ls
|
||||
```
|
||||
|
||||
Attempting to remove a repository that doesn't exist gives us an error message:
|
||||
|
||||
``` (fail)
|
||||
$ rad rm rad:z2Jk1mNqyX7AjT4K83jJW9vQoHn4f
|
||||
✗ Error: repository rad:z2Jk1mNqyX7AjT4K83jJW9vQoHn4f was not found
|
||||
```
|
||||
|
|
@ -4,6 +4,8 @@ pub mod rad_assign;
|
|||
pub mod rad_auth;
|
||||
#[path = "commands/checkout.rs"]
|
||||
pub mod rad_checkout;
|
||||
#[path = "commands/clean.rs"]
|
||||
pub mod rad_clean;
|
||||
#[path = "commands/clone.rs"]
|
||||
pub mod rad_clone;
|
||||
#[path = "commands/cob.rs"]
|
||||
|
|
@ -38,8 +40,6 @@ pub mod rad_publish;
|
|||
pub mod rad_remote;
|
||||
#[path = "commands/review.rs"]
|
||||
pub mod rad_review;
|
||||
#[path = "commands/rm.rs"]
|
||||
pub mod rad_rm;
|
||||
#[path = "commands/self.rs"]
|
||||
pub mod rad_self;
|
||||
#[path = "commands/sync.rs"]
|
||||
|
|
|
|||
|
|
@ -3,26 +3,26 @@ use std::ffi::OsString;
|
|||
use anyhow::anyhow;
|
||||
|
||||
use radicle::identity::Id;
|
||||
use radicle::node;
|
||||
use radicle::node::Handle as _;
|
||||
use radicle::storage;
|
||||
use radicle::storage::WriteStorage;
|
||||
use radicle::Profile;
|
||||
|
||||
use crate::git;
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
|
||||
pub const HELP: Help = Help {
|
||||
name: "rm",
|
||||
description: "Remove a project",
|
||||
name: "clean",
|
||||
description: "Clean a project",
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
usage: r#"
|
||||
Usage
|
||||
|
||||
rad rm <rid> [<option>...]
|
||||
rad clean <rid> [<option>...]
|
||||
|
||||
Removes a repository from storage. The repository is also untracked, if possible.
|
||||
Removes all remotes from a repository, as long as they are not the
|
||||
local operator or a delegate of the repository.
|
||||
|
||||
Note that remotes will still be fetched as long as they are
|
||||
tracked and/or the tracking scope is "all".
|
||||
|
||||
Options
|
||||
|
||||
|
|
@ -61,7 +61,8 @@ impl Args for Options {
|
|||
|
||||
Ok((
|
||||
Options {
|
||||
rid: id.ok_or_else(|| anyhow!("an RID must be provided; see `rad rm --help`"))?,
|
||||
rid: id
|
||||
.ok_or_else(|| anyhow!("an RID must be provided; see `rad clean --help`"))?,
|
||||
confirm,
|
||||
},
|
||||
vec![],
|
||||
|
|
@ -80,48 +81,11 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
if !options.confirm || term::confirm(format!("Remove {rid}?")) {
|
||||
untrack(&rid, &profile)?;
|
||||
remove_remote(&rid)?;
|
||||
storage.remove(rid)?;
|
||||
term::success!("Successfully removed {rid} from storage");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn untrack(rid: &Id, profile: &Profile) -> anyhow::Result<()> {
|
||||
let mut node = radicle::Node::new(profile.socket());
|
||||
|
||||
let result = if node.is_running() {
|
||||
node.untrack_repo(*rid).map_err(anyhow::Error::from)
|
||||
} else {
|
||||
let mut store =
|
||||
node::tracking::store::Config::open(profile.home.node().join(node::TRACKING_DB_FILE))?;
|
||||
store.untrack_repo(rid).map_err(anyhow::Error::from)
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
term::warning(format!("Failed to untrack repository: {e}"));
|
||||
term::warning("Make sure to untrack this repository when your node is running");
|
||||
} else {
|
||||
term::success!("Untracked {rid}")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_remote(rid: &Id) -> anyhow::Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
if let Err(e) = git::Repository::open(cwd)
|
||||
.map_err(|err| err.into())
|
||||
.and_then(|repo| git::remove_remote(&repo, rid))
|
||||
{
|
||||
term::warning(format!(
|
||||
"Attempted to remove 'rad' remote from working copy: {e}"
|
||||
));
|
||||
term::warning("In case a working copy exists, make sure to `git remote remove rad`");
|
||||
} else {
|
||||
term::success!("Successfully removed 'rad' remote");
|
||||
let cleaned = storage.clean(rid)?;
|
||||
for remote in cleaned {
|
||||
term::info!("Removed {remote}");
|
||||
}
|
||||
term::success!("Successfully cleaned {rid}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -28,7 +28,7 @@ const COMMANDS: &[Help] = &[
|
|||
rad_patch::HELP,
|
||||
rad_path::HELP,
|
||||
rad_review::HELP,
|
||||
rad_rm::HELP,
|
||||
rad_clean::HELP,
|
||||
rad_self::HELP,
|
||||
rad_label::HELP,
|
||||
rad_track::HELP,
|
||||
|
|
|
|||
|
|
@ -215,8 +215,12 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
|
|||
args.to_vec(),
|
||||
);
|
||||
}
|
||||
"rm" => {
|
||||
term::run_command_args::<rad_rm::Options, _>(rad_rm::HELP, rad_rm::run, args.to_vec());
|
||||
"clean" => {
|
||||
term::run_command_args::<rad_clean::Options, _>(
|
||||
rad_clean::HELP,
|
||||
rad_clean::run,
|
||||
args.to_vec(),
|
||||
);
|
||||
}
|
||||
"self" => {
|
||||
term::run_command_args::<rad_self::Options, _>(
|
||||
|
|
|
|||
|
|
@ -623,17 +623,47 @@ fn rad_review_by_hunk() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn rad_rm() {
|
||||
fn rad_clean() {
|
||||
let mut environment = Environment::new();
|
||||
let profile = environment.profile("alice");
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
let home = &profile.home;
|
||||
let alice = environment.node(Config::test(Alias::new("alice")));
|
||||
let bob = environment.node(Config::test(Alias::new("bob")));
|
||||
let working = environment.tmp().join("working");
|
||||
|
||||
// Setup a test repository.
|
||||
fixtures::repository(working.path());
|
||||
// Setup a test project.
|
||||
let acme = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
|
||||
fixtures::repository(working.join("acme"));
|
||||
test(
|
||||
"examples/rad-init.md",
|
||||
working.join("acme"),
|
||||
Some(&alice.home),
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test("examples/rad-init.md", working.path(), Some(home), []).unwrap();
|
||||
test("examples/rad-rm.md", working.path(), Some(home), []).unwrap();
|
||||
let mut alice = alice.spawn();
|
||||
let mut bob = bob.spawn();
|
||||
alice.handle.track_repo(acme, Scope::All).unwrap();
|
||||
|
||||
bob.connect(&alice).converge([&alice]);
|
||||
|
||||
test(
|
||||
"examples/rad-clone.md",
|
||||
working.join("bob"),
|
||||
Some(&bob.home),
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bob.has_inventory_of(&acme, &alice.id);
|
||||
alice.has_inventory_of(&acme, &bob.id);
|
||||
|
||||
test(
|
||||
"examples/rad-clean.md",
|
||||
working.join("acme"),
|
||||
Some(&alice.home),
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue