cli: Hints system
Enable hints by default, but allow users to turn them off via configuration. Add a hint when pushing while offline.
This commit is contained in:
parent
7acd025d7f
commit
8e76ce03d5
|
|
@ -42,7 +42,7 @@ $ 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
|
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:
|
integrate Bob's changes before pushing ours:
|
||||||
|
|
||||||
``` ~alice (stderr) (fail)
|
``` ~alice (stderr) (fail) RAD_HINT=1
|
||||||
$ git push rad
|
$ git push rad
|
||||||
hint: you are attempting to push a commit that would cause your upstream to diverge from the canonical head
|
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
|
hint: to integrate the remote changes, run `git pull --rebase` and try again
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use serde_json::Value;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
|
|
||||||
|
use radicle::cli;
|
||||||
use radicle::cob::issue::Issues;
|
use radicle::cob::issue::Issues;
|
||||||
use radicle::cob::patch::{MergeTarget, Patches};
|
use radicle::cob::patch::{MergeTarget, Patches};
|
||||||
use radicle::crypto::ssh::keystore::MemorySigner;
|
use radicle::crypto::ssh::keystore::MemorySigner;
|
||||||
|
|
@ -70,6 +71,7 @@ pub fn profile(home: &Path, seed: [u8; 32]) -> radicle::Profile {
|
||||||
public_key: keypair.pk.into(),
|
public_key: keypair.pk.into(),
|
||||||
config: profile::Config {
|
config: profile::Config {
|
||||||
node: node::Config::new(alias),
|
node: node::Config::new(alias),
|
||||||
|
cli: cli::Config::default(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ use radicle::crypto::{KeyPair, Seed, Signer};
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::git::refname;
|
use radicle::git::refname;
|
||||||
use radicle::identity::{Id, Visibility};
|
use radicle::identity::{Id, Visibility};
|
||||||
use radicle::node;
|
|
||||||
use radicle::node::address::Book;
|
use radicle::node::address::Book;
|
||||||
use radicle::node::routing;
|
use radicle::node::routing;
|
||||||
use radicle::node::routing::Store;
|
use radicle::node::routing::Store;
|
||||||
|
|
@ -32,6 +31,7 @@ use radicle::rad;
|
||||||
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
|
use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _};
|
||||||
use radicle::test::fixtures;
|
use radicle::test::fixtures;
|
||||||
use radicle::Storage;
|
use radicle::Storage;
|
||||||
|
use radicle::{cli, node};
|
||||||
|
|
||||||
use crate::node::NodeId;
|
use crate::node::NodeId;
|
||||||
use crate::service::Event;
|
use crate::service::Event;
|
||||||
|
|
@ -111,7 +111,12 @@ impl Environment {
|
||||||
let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32]));
|
let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32]));
|
||||||
let tracking_db = home.node().join(TRACKING_DB_FILE);
|
let tracking_db = home.node().join(TRACKING_DB_FILE);
|
||||||
let alias = Alias::from_str(alias).unwrap();
|
let alias = Alias::from_str(alias).unwrap();
|
||||||
let config = profile::Config::init(alias.clone(), &home.config()).unwrap();
|
let config = profile::Config {
|
||||||
|
node: node::Config::new(alias.clone()),
|
||||||
|
cli: cli::Config { hints: false },
|
||||||
|
};
|
||||||
|
config.write(&home.config()).unwrap();
|
||||||
|
|
||||||
let storage = Storage::open(
|
let storage = Storage::open(
|
||||||
home.storage(),
|
home.storage(),
|
||||||
git::UserInfo {
|
git::UserInfo {
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,7 @@ pub fn run(
|
||||||
let signer = profile.signer()?;
|
let signer = profile.signer()?;
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
let mut ok = HashSet::new();
|
let mut ok = HashSet::new();
|
||||||
|
let hints = profile.hints();
|
||||||
|
|
||||||
assert_eq!(signer.public_key(), &nid);
|
assert_eq!(signer.public_key(), &nid);
|
||||||
|
|
||||||
|
|
@ -242,14 +243,16 @@ pub fn run(
|
||||||
// Not a rollback.
|
// Not a rollback.
|
||||||
&& !rollback
|
&& !rollback
|
||||||
{
|
{
|
||||||
eprintln!(
|
if hints {
|
||||||
"hint: you are attempting to push a commit that would \
|
eprintln!(
|
||||||
cause your upstream to diverge from the canonical head"
|
"hint: you are attempting to push a commit that would \
|
||||||
);
|
cause your upstream to diverge from the canonical head"
|
||||||
eprintln!(
|
);
|
||||||
"hint: to integrate the remote changes, run `git pull --rebase` \
|
eprintln!(
|
||||||
and try again"
|
"hint: to integrate the remote changes, run `git pull --rebase` \
|
||||||
|
and try again"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return Err(Error::HeadsDiverge(head.into(), *canonical_oid));
|
return Err(Error::HeadsDiverge(head.into(), *canonical_oid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -283,6 +286,9 @@ pub fn run(
|
||||||
if node.is_running() {
|
if node.is_running() {
|
||||||
// Nb. allow this to fail. The push to local storage was still successful.
|
// Nb. allow this to fail. The push to local storage was still successful.
|
||||||
sync(stored.id, node).ok();
|
sync(stored.id, node).ok();
|
||||||
|
} else if hints {
|
||||||
|
eprintln!("hint: offline push, your node is not running");
|
||||||
|
eprintln!("hint: to sync with the network, run `rad node start`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/// CLI configuration.
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Config {
|
||||||
|
/// Whether to show hints or not in the CLI.
|
||||||
|
#[serde(default)]
|
||||||
|
pub hints: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { hints: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ extern crate amplify;
|
||||||
extern crate radicle_git_ext as git_ext;
|
extern crate radicle_git_ext as git_ext;
|
||||||
|
|
||||||
mod canonical;
|
mod canonical;
|
||||||
|
pub mod cli;
|
||||||
pub mod cob;
|
pub mod cob;
|
||||||
pub mod collections;
|
pub mod collections;
|
||||||
pub mod git;
|
pub mod git;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::prelude::Did;
|
||||||
use crate::prelude::NodeId;
|
use crate::prelude::NodeId;
|
||||||
use crate::storage::git::transport;
|
use crate::storage::git::transport;
|
||||||
use crate::storage::git::Storage;
|
use crate::storage::git::Storage;
|
||||||
use crate::{git, node};
|
use crate::{cli, git, node};
|
||||||
|
|
||||||
/// Environment variables used by radicle.
|
/// Environment variables used by radicle.
|
||||||
pub mod env {
|
pub mod env {
|
||||||
|
|
@ -39,6 +39,13 @@ pub mod env {
|
||||||
pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE";
|
pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE";
|
||||||
/// RNG seed. Must be convertible to a `u64`.
|
/// RNG seed. Must be convertible to a `u64`.
|
||||||
pub const RAD_RNG_SEED: &str = "RAD_RNG_SEED";
|
pub const RAD_RNG_SEED: &str = "RAD_RNG_SEED";
|
||||||
|
/// Show radicle hints.
|
||||||
|
pub const RAD_HINT: &str = "RAD_HINT";
|
||||||
|
|
||||||
|
/// Whether or not to show hints.
|
||||||
|
pub fn hints() -> bool {
|
||||||
|
var(RAD_HINT).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the configured pager program from the environment.
|
/// Get the configured pager program from the environment.
|
||||||
pub fn pager() -> Option<String> {
|
pub fn pager() -> Option<String> {
|
||||||
|
|
@ -107,7 +114,10 @@ pub enum ConfigError {
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
/// Node configuration.
|
||||||
pub node: node::Config,
|
pub node: node::Config,
|
||||||
|
/// CLI configuration.
|
||||||
|
pub cli: cli::Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|
@ -115,17 +125,9 @@ impl Config {
|
||||||
pub fn init(alias: Alias, path: &Path) -> io::Result<Self> {
|
pub fn init(alias: Alias, path: &Path) -> io::Result<Self> {
|
||||||
let cfg = Self {
|
let cfg = Self {
|
||||||
node: node::Config::new(alias),
|
node: node::Config::new(alias),
|
||||||
|
cli: cli::Config::default(),
|
||||||
};
|
};
|
||||||
let mut file = fs::OpenOptions::new()
|
cfg.write(path)?;
|
||||||
.create_new(true)
|
|
||||||
.write(true)
|
|
||||||
.open(path)?;
|
|
||||||
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
|
|
||||||
let mut serializer = serde_json::Serializer::with_formatter(&file, formatter);
|
|
||||||
|
|
||||||
cfg.serialize(&mut serializer)?;
|
|
||||||
file.write_all(b"\n")?;
|
|
||||||
file.sync_all()?;
|
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
}
|
}
|
||||||
|
|
@ -145,11 +147,28 @@ impl Config {
|
||||||
};
|
};
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
node: node::Config::new(alias),
|
node: node::Config::new(alias),
|
||||||
|
cli: cli::Config::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write configuration to disk.
|
||||||
|
pub fn write(&self, path: &Path) -> Result<(), io::Error> {
|
||||||
|
let mut file = fs::OpenOptions::new()
|
||||||
|
.create_new(true)
|
||||||
|
.write(true)
|
||||||
|
.open(path)?;
|
||||||
|
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
|
||||||
|
let mut serializer = serde_json::Serializer::with_formatter(&file, formatter);
|
||||||
|
|
||||||
|
self.serialize(&mut serializer)?;
|
||||||
|
file.write_all(b"\n")?;
|
||||||
|
file.sync_all()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the user alias.
|
/// Get the user alias.
|
||||||
pub fn alias(&self) -> &Alias {
|
pub fn alias(&self) -> &Alias {
|
||||||
&self.node.alias
|
&self.node.alias
|
||||||
|
|
@ -226,6 +245,13 @@ impl Profile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hints(&self) -> bool {
|
||||||
|
if env::hints() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
self.config.cli.hints
|
||||||
|
}
|
||||||
|
|
||||||
pub fn did(&self) -> Did {
|
pub fn did(&self) -> Did {
|
||||||
Did::from(self.public_key)
|
Did::from(self.public_key)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue