diff --git a/radicle-cli/examples/git/git-push-diverge.md b/radicle-cli/examples/git/git-push-diverge.md index 256478a4..f5a2d44e 100644 --- a/radicle-cli/examples/git/git-push-diverge.md +++ b/radicle-cli/examples/git/git-push-diverge.md @@ -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 integrate Bob's changes before pushing ours: -``` ~alice (stderr) (fail) +``` ~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 diff --git a/radicle-httpd/src/test.rs b/radicle-httpd/src/test.rs index e1001be7..9dc2af82 100644 --- a/radicle-httpd/src/test.rs +++ b/radicle-httpd/src/test.rs @@ -11,6 +11,7 @@ use serde_json::Value; use time::OffsetDateTime; use tower::ServiceExt; +use radicle::cli; use radicle::cob::issue::Issues; use radicle::cob::patch::{MergeTarget, Patches}; 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(), config: profile::Config { node: node::Config::new(alias), + cli: cli::Config::default(), }, } } diff --git a/radicle-node/src/test/environment.rs b/radicle-node/src/test/environment.rs index 6ef2b883..58d20a0b 100644 --- a/radicle-node/src/test/environment.rs +++ b/radicle-node/src/test/environment.rs @@ -18,7 +18,6 @@ use radicle::crypto::{KeyPair, Seed, Signer}; use radicle::git; use radicle::git::refname; use radicle::identity::{Id, Visibility}; -use radicle::node; use radicle::node::address::Book; use radicle::node::routing; use radicle::node::routing::Store; @@ -32,6 +31,7 @@ use radicle::rad; use radicle::storage::{ReadStorage as _, RemoteRepository as _, SignRepository as _}; use radicle::test::fixtures; use radicle::Storage; +use radicle::{cli, node}; use crate::node::NodeId; use crate::service::Event; @@ -111,7 +111,12 @@ impl Environment { let keypair = KeyPair::from_seed(Seed::from([!(self.users as u8); 32])); let tracking_db = home.node().join(TRACKING_DB_FILE); 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( home.storage(), git::UserInfo { diff --git a/radicle-remote-helper/src/push.rs b/radicle-remote-helper/src/push.rs index db3c4c31..813c7b1b 100644 --- a/radicle-remote-helper/src/push.rs +++ b/radicle-remote-helper/src/push.rs @@ -159,6 +159,7 @@ pub fn run( let signer = profile.signer()?; let mut line = String::new(); let mut ok = HashSet::new(); + let hints = profile.hints(); assert_eq!(signer.public_key(), &nid); @@ -242,14 +243,16 @@ pub fn run( // Not a rollback. && !rollback { - eprintln!( - "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` \ - and try again" + if hints { + eprintln!( + "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` \ + and try again" ); + } return Err(Error::HeadsDiverge(head.into(), *canonical_oid)); } } @@ -283,6 +286,9 @@ pub fn run( if node.is_running() { // Nb. allow this to fail. The push to local storage was still successful. 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`"); } } } diff --git a/radicle/src/cli.rs b/radicle/src/cli.rs new file mode 100644 index 00000000..a1dc54c4 --- /dev/null +++ b/radicle/src/cli.rs @@ -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 } + } +} diff --git a/radicle/src/lib.rs b/radicle/src/lib.rs index 07cf1889..82f73b0e 100644 --- a/radicle/src/lib.rs +++ b/radicle/src/lib.rs @@ -9,6 +9,7 @@ extern crate amplify; extern crate radicle_git_ext as git_ext; mod canonical; +pub mod cli; pub mod cob; pub mod collections; pub mod git; diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index aed40fe9..dd05e0ac 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -25,7 +25,7 @@ use crate::prelude::Did; use crate::prelude::NodeId; use crate::storage::git::transport; use crate::storage::git::Storage; -use crate::{git, node}; +use crate::{cli, git, node}; /// Environment variables used by radicle. pub mod env { @@ -39,6 +39,13 @@ pub mod env { pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE"; /// RNG seed. Must be convertible to a `u64`. 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. pub fn pager() -> Option { @@ -107,7 +114,10 @@ pub enum ConfigError { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct Config { + /// Node configuration. pub node: node::Config, + /// CLI configuration. + pub cli: cli::Config, } impl Config { @@ -115,17 +125,9 @@ impl Config { pub fn init(alias: Alias, path: &Path) -> io::Result { let cfg = Self { node: node::Config::new(alias), + cli: cli::Config::default(), }; - 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); - - cfg.serialize(&mut serializer)?; - file.write_all(b"\n")?; - file.sync_all()?; + cfg.write(path)?; Ok(cfg) } @@ -145,11 +147,28 @@ impl Config { }; Ok(Config { 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. pub fn alias(&self) -> &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 { Did::from(self.public_key) }