cli: add a global --quiet flag, which silences status messages

Note that `jj resolve` already had its own `--quiet` flag. The output
with `--quiet` for that command got a lot quieter with the global
`--quiet` also taking effect. That seems reasonable to me.
This commit is contained in:
Martin von Zweigbergk 2024-03-30 10:18:56 -07:00 committed by Martin von Zweigbergk
parent 4962d9af3b
commit adf3b50209
6 changed files with 50 additions and 23 deletions

View file

@ -58,6 +58,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `--all` is now named `--all-remotes` for `jj branch list`
* There is a new global `--quiet` flag to silence commands' non-primary output.
### Fixed bugs
## [0.15.1] - 2024-03-06

View file

@ -2137,6 +2137,16 @@ pub struct EarlyArgs {
/// When to colorize output (always, never, auto)
#[arg(long, value_name = "WHEN", global = true)]
pub color: Option<ColorChoice>,
/// Silence non-primary command output
///
/// For example, `jj files` will still list files, but it won't tell you if
/// the working copy was snapshotted or if descendants were rebased.
///
/// Warnings and errors will still be printed.
#[arg(long, global = true, action = ArgAction::SetTrue)]
// Parsing with ignore_errors will crash if this is bool, so use
// Option<bool>.
pub quiet: Option<bool>,
/// Disable the pager
#[arg(long, value_name = "WHEN", global = true, action = ArgAction::SetTrue)]
// Parsing with ignore_errors will crash if this is bool, so use
@ -2306,6 +2316,9 @@ fn handle_early_args(
if let Some(choice) = args.color {
args.config_toml.push(format!(r#"ui.color="{choice}""#));
}
if args.quiet.unwrap_or_default() {
args.config_toml.push(r#"ui.quiet=true"#.to_string());
}
if args.no_pager.unwrap_or_default() {
args.config_toml.push(r#"ui.paginate="never""#.to_owned());
}

View file

@ -163,6 +163,7 @@ impl Write for UiStderr<'_> {
pub struct Ui {
color: bool,
quiet: bool,
pager_cmd: CommandNameAndArgs,
paginate: PaginationChoice,
progress_indicator: bool,
@ -222,6 +223,10 @@ fn use_color(choice: ColorChoice) -> bool {
}
}
fn be_quiet(config: &config::Config) -> bool {
config.get_bool("ui.quiet").unwrap_or_default()
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
pub enum PaginationChoice {
@ -245,6 +250,7 @@ fn pager_setting(config: &config::Config) -> Result<CommandNameAndArgs, CommandE
impl Ui {
pub fn with_config(config: &config::Config) -> Result<Ui, CommandError> {
let color = use_color(color_setting(config));
let quiet = be_quiet(config);
// Sanitize ANSI escape codes if we're printing to a terminal. Doesn't affect
// ANSI escape codes that originate from the formatter itself.
let sanitize = io::stdout().is_terminal();
@ -252,6 +258,7 @@ impl Ui {
let progress_indicator = progress_indicator_setting(config);
Ok(Ui {
color,
quiet,
formatter_factory,
pager_cmd: pager_setting(config)?,
paginate: pagination_setting(config)?,
@ -262,6 +269,7 @@ impl Ui {
pub fn reset(&mut self, config: &config::Config) -> Result<(), CommandError> {
self.color = use_color(color_setting(config));
self.quiet = be_quiet(config);
self.paginate = pagination_setting(config)?;
self.pager_cmd = pager_setting(config)?;
self.progress_indicator = progress_indicator_setting(config);
@ -375,14 +383,17 @@ impl Ui {
/// Writer to print an update that's not part of the command's main output.
pub fn status(&self) -> Box<dyn Write + '_> {
Box::new(self.stderr())
if self.quiet {
Box::new(std::io::sink())
} else {
Box::new(self.stderr())
}
}
/// A formatter to print an update that's not part of the command's main
/// output. Returns `None` if `--quiet` was requested.
// TODO: Actually support `--quiet`
pub fn status_formatter(&self) -> Option<Box<dyn Formatter + '_>> {
Some(self.stderr_formatter())
(!self.quiet).then(|| self.stderr_formatter())
}
/// Writer to print hint with the default "Hint: " heading.
@ -394,7 +405,7 @@ impl Ui {
/// Writer to print hint without the "Hint: " heading.
pub fn hint_no_heading(&self) -> Option<LabeledWriter<Box<dyn Formatter + '_>, &'static str>> {
Some(LabeledWriter::new(self.stderr_formatter(), "hint"))
(!self.quiet).then(|| LabeledWriter::new(self.stderr_formatter(), "hint"))
}
/// Writer to print hint with the given heading.
@ -402,11 +413,7 @@ impl Ui {
&self,
heading: H,
) -> Option<HeadingLabeledWriter<Box<dyn Formatter + '_>, &'static str, H>> {
Some(HeadingLabeledWriter::new(
self.stderr_formatter(),
"hint",
heading,
))
(!self.quiet).then(|| HeadingLabeledWriter::new(self.stderr_formatter(), "hint", heading))
}
/// Writer to print warning with the default "Warning: " heading.

View file

@ -159,6 +159,10 @@ To get started, see the tutorial at https://github.com/martinvonz/jj/blob/main/d
Possible values: `true`, `false`
* `--color <WHEN>` — When to colorize output (always, never, auto)
* `--quiet` — Silence non-primary command output
Possible values: `true`, `false`
* `--no-pager` — Disable the pager
Possible values: `true`, `false`

View file

@ -441,6 +441,19 @@ fn test_color_ui_messages() {
"###);
}
#[test]
fn test_quiet() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
let repo_path = test_env.env_root().join("repo");
// Can skip message about new working copy with `--quiet`
std::fs::write(repo_path.join("file1"), "contents").unwrap();
let (_stdout, stderr) =
test_env.jj_cmd_ok(&repo_path, &["--quiet", "describe", "-m=new description"]);
insta::assert_snapshot!(stderr, @"");
}
#[test]
fn test_early_args() {
// Test that help output parses early args
@ -535,6 +548,7 @@ fn test_help() {
--at-operation <AT_OPERATION> Operation to load the repo at [default: @] [aliases: at-op]
--debug Enable debug logging
--color <WHEN> When to colorize output (always, never, auto)
--quiet Silence non-primary command output
--no-pager Disable the pager
--config-toml <TOML> Additional configuration options (can be repeated)
"###);

View file

@ -728,20 +728,7 @@ fn test_multiple_conflicts() {
std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap();
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["resolve", "--quiet", "another_file"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Resolving conflicts in: another_file
New conflicts appeared in these commits:
vruxwmqv 3c438f88 conflict | (conflict) conflict
To resolve the conflicts, start by updating to it:
jj new vruxwmqvtpmx
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you may want inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.
Working copy now at: vruxwmqv 3c438f88 conflict | (conflict) conflict
Parent commit : zsuskuln de7553ef a | a
Parent commit : royxmykx f68bc2f0 b | b
Added 0 files, modified 1 files, removed 0 files
"###);
insta::assert_snapshot!(stderr, @"");
// For the rest of the test, we call `jj resolve` several times in a row to
// resolve each conflict in the order it chooses.