rhg: Add a `rhg.on-unsupported` configuration key
authorSimon Sapin <simon.sapin@octobus.net>
Mon, 01 Mar 2021 16:18:42 +0100
changeset 46666 33f2d56acc73
parent 46665 7284b524b441
child 46667 93e9f448273c
rhg: Add a `rhg.on-unsupported` configuration key For now the two values are: * `abort-silent`: silently exit with code 252, the previous default behavior * `abort`: print an error message about what feature is not supported, then exit with code 252. Now the default. Differential Revision: https://phab.mercurial-scm.org/D10091
rust/rhg/src/commands/cat.rs
rust/rhg/src/error.rs
rust/rhg/src/main.rs
tests/test-rhg.t
--- a/rust/rhg/src/commands/cat.rs	Mon Mar 01 13:51:35 2021 +0100
+++ b/rust/rhg/src/commands/cat.rs	Mon Mar 01 16:18:42 2021 +0100
@@ -60,6 +60,8 @@
             invocation.ui.write_stdout(&data)?;
             Ok(())
         }
-        None => Err(CommandError::Unimplemented.into()),
+        None => Err(CommandError::unsupported(
+            "`rhg cat` without `--rev` / `-r`",
+        )),
     }
 }
--- a/rust/rhg/src/error.rs	Mon Mar 01 13:51:35 2021 +0100
+++ b/rust/rhg/src/error.rs	Mon Mar 01 16:18:42 2021 +0100
@@ -15,12 +15,11 @@
     /// Exit with an error message and "standard" failure exit code.
     Abort { message: Vec<u8> },
 
-    /// A mercurial capability as not been implemented.
-    ///
-    /// There is no error message printed in this case.
-    /// Instead, we exit with a specic status code and a wrapper script may
-    /// fallback to Python-based Mercurial.
-    Unimplemented,
+    /// Encountered something (such as a CLI argument, repository layout, …)
+    /// not supported by this version of `rhg`. Depending on configuration
+    /// `rhg` may attempt to silently fall back to Python-based `hg`, which
+    /// may or may not support this feature.
+    UnsupportedFeature { message: Vec<u8> },
 }
 
 impl CommandError {
@@ -32,20 +31,28 @@
             message: utf8_to_local(message.as_ref()).into(),
         }
     }
+
+    pub fn unsupported(message: impl AsRef<str>) -> Self {
+        CommandError::UnsupportedFeature {
+            message: utf8_to_local(message.as_ref()).into(),
+        }
+    }
 }
 
 /// For now we don’t differenciate between invalid CLI args and valid for `hg`
 /// but not supported yet by `rhg`.
 impl From<clap::Error> for CommandError {
-    fn from(_: clap::Error) -> Self {
-        CommandError::Unimplemented
+    fn from(error: clap::Error) -> Self {
+        CommandError::unsupported(error.to_string())
     }
 }
 
 impl From<HgError> for CommandError {
     fn from(error: HgError) -> Self {
         match error {
-            HgError::UnsupportedFeature(_) => CommandError::Unimplemented,
+            HgError::UnsupportedFeature(message) => {
+                CommandError::unsupported(message)
+            }
             _ => CommandError::abort(error.to_string()),
         }
     }
--- a/rust/rhg/src/main.rs	Mon Mar 01 13:51:35 2021 +0100
+++ b/rust/rhg/src/main.rs	Mon Mar 01 16:18:42 2021 +0100
@@ -84,8 +84,15 @@
     let ui = ui::Ui::new();
 
     let early_args = EarlyArgs::parse(std::env::args_os());
-    let non_repo_config = Config::load(early_args.config)
-        .unwrap_or_else(|error| exit(&ui, Err(error.into())));
+    let non_repo_config =
+        Config::load(early_args.config).unwrap_or_else(|error| {
+            // Normally this is decided based on config, but we don’t have that
+            // available. As of this writing config loading never returns an
+            // "unsupported" error but that is not enforced by the type system.
+            let on_unsupported = OnUnsupported::Abort;
+
+            exit(&ui, on_unsupported, Err(error.into()))
+        });
 
     let repo_path = early_args.repo.as_deref().map(get_path_from_bytes);
     let repo_result = match Repo::find(&non_repo_config, repo_path) {
@@ -94,7 +101,11 @@
             // Not finding a repo is not fatal yet, if `-R` was not given
             Err(NoRepoInCwdError { cwd: at })
         }
-        Err(error) => exit(&ui, Err(error.into())),
+        Err(error) => exit(
+            &ui,
+            OnUnsupported::from_config(&non_repo_config),
+            Err(error.into()),
+        ),
     };
 
     let config = if let Ok(repo) = &repo_result {
@@ -109,7 +120,7 @@
         repo_result.as_ref(),
         config,
     );
-    exit(&ui, result)
+    exit(&ui, OnUnsupported::from_config(config), result)
 }
 
 fn exit_code(result: &Result<(), CommandError>) -> i32 {
@@ -119,16 +130,37 @@
 
         // Exit with a specific code and no error message to let a potential
         // wrapper script fallback to Python-based Mercurial.
-        Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED,
+        Err(CommandError::UnsupportedFeature { .. }) => {
+            exitcode::UNIMPLEMENTED
+        }
     }
 }
 
-fn exit(ui: &Ui, result: Result<(), CommandError>) -> ! {
-    if let Err(CommandError::Abort { message }) = &result {
-        if !message.is_empty() {
-            // Ignore errors when writing to stderr, we’re already exiting
-            // with failure code so there’s not much more we can do.
-            let _ = ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
+fn exit(
+    ui: &Ui,
+    on_unsupported: OnUnsupported,
+    result: Result<(), CommandError>,
+) -> ! {
+    match &result {
+        Ok(_) => {}
+        Err(CommandError::Abort { message }) => {
+            if !message.is_empty() {
+                // Ignore errors when writing to stderr, we’re already exiting
+                // with failure code so there’s not much more we can do.
+                let _ =
+                    ui.write_stderr(&format_bytes!(b"abort: {}\n", message));
+            }
+        }
+        Err(CommandError::UnsupportedFeature { message }) => {
+            match on_unsupported {
+                OnUnsupported::Abort => {
+                    let _ = ui.write_stderr(&format_bytes!(
+                        b"unsupported feature: {}\n",
+                        message
+                    ));
+                }
+                OnUnsupported::AbortSilent => {}
+            }
         }
     }
     std::process::exit(exit_code(&result))
@@ -226,3 +258,29 @@
         Self { config, repo }
     }
 }
+
+/// What to do when encountering some unsupported feature.
+///
+/// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
+enum OnUnsupported {
+    /// Print an error message describing what feature is not supported,
+    /// and exit with code 252.
+    Abort,
+    /// Silently exit with code 252.
+    AbortSilent,
+}
+
+impl OnUnsupported {
+    fn from_config(config: &Config) -> Self {
+        let default = OnUnsupported::Abort;
+        match config.get(b"rhg", b"on-unsupported") {
+            Some(b"abort") => OnUnsupported::Abort,
+            Some(b"abort-silent") => OnUnsupported::AbortSilent,
+            None => default,
+            Some(_) => {
+                // TODO: warn about unknown config value
+                default
+            }
+        }
+    }
+}
--- a/tests/test-rhg.t	Mon Mar 01 13:51:35 2021 +0100
+++ b/tests/test-rhg.t	Mon Mar 01 16:18:42 2021 +0100
@@ -12,6 +12,15 @@
 
 Unimplemented command
   $ rhg unimplemented-command
+  unsupported feature: error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context
+  
+  USAGE:
+      rhg [OPTIONS] <SUBCOMMAND>
+  
+  For more information try --help
+  
+  [252]
+  $ rhg unimplemented-command --config rhg.on-unsupported=abort-silent
   [252]
 
 Finding root
@@ -153,12 +162,15 @@
 
   $ echo indoor-pool >> .hg/requires
   $ rhg files
+  unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
   [252]
 
   $ rhg cat -r 1 copy_of_original
+  unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
   [252]
 
   $ rhg debugrequirements
+  unsupported feature: repository requires feature unknown to this Mercurial: indoor-pool
   [252]
 
   $ echo -e '\xFF' >> .hg/requires