diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index aaec9ecc9..9c3046b35 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -1210,7 +1210,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin let settings = &self.settings; if settings.user_name().is_empty() || settings.user_email().is_empty() { writeln!( - ui.warning(), + ui.warning_no_heading(), r#"Name and email not configured. Until configured, your commits will be created with the empty identity, and can't be pushed to remotes. To configure, run: jj config set --user user.name "Some One" jj config set --user user.email "someone@example.com""# @@ -1596,7 +1596,7 @@ pub fn print_checkout_stats( } if stats.skipped_files != 0 { writeln!( - ui.warning(), + ui.warning_no_heading(), "{} of those updates were skipped because there were conflicting changes in the \ working copy.", stats.skipped_files @@ -1739,7 +1739,10 @@ fn load_template_aliases( .map_err(|e| e.to_string()) .and_then(|v| aliases_map.insert(&decl, v).map_err(|e| e.to_string())); if let Err(s) = r { - writeln!(ui.warning(), r#"Failed to load "{TABLE_KEY}.{decl}": {s}"#)?; + writeln!( + ui.warning_no_heading(), + r#"Failed to load "{TABLE_KEY}.{decl}": {s}"# + )?; } } } @@ -2549,7 +2552,7 @@ impl CliRunner { let new_string_args = expand_args(ui, &self.app, env::args_os(), &config).ok(); if new_string_args.as_ref() != Some(&string_args) { writeln!( - ui.warning(), + ui.warning_no_heading(), "Command aliases cannot be loaded from -R/--repository path" )?; } diff --git a/cli/src/commands/branch.rs b/cli/src/commands/branch.rs index afaaaeb2e..9272fe529 100644 --- a/cli/src/commands/branch.rs +++ b/cli/src/commands/branch.rs @@ -258,8 +258,8 @@ fn cmd_branch_create( if branch_names.len() > 1 { writeln!( - ui.warning(), - "warning: Creating multiple branches: {}", + ui.warning_with_heading("warning: "), + "Creating multiple branches: {}", branch_names.join(", "), )?; } @@ -321,8 +321,8 @@ fn cmd_branch_rename( .any(|(_, remote_ref)| remote_ref.is_tracking()) { writeln!( - ui.warning(), - "Warning: Branch {old_branch} has tracking remote branches which were not renamed." + ui.warning_with_heading("Warning: "), + "Branch {old_branch} has tracking remote branches which were not renamed." )?; writeln!( ui.hint_with_heading("Hint: "), @@ -370,8 +370,8 @@ fn cmd_branch_set( if branch_names.len() > 1 { writeln!( - ui.warning(), - "warning: Updating multiple branches: {}", + ui.warning_with_heading("warning: "), + "Updating multiple branches: {}", branch_names.join(", "), )?; } @@ -487,7 +487,7 @@ fn cmd_branch_delete( let view = workspace_command.repo().view(); if !args.glob.is_empty() { writeln!( - ui.warning(), + ui.warning_no_heading(), "--glob has been deprecated. Please prefix the pattern with `glob:` instead." )?; } @@ -514,7 +514,7 @@ fn cmd_branch_forget( let view = workspace_command.repo().view(); if !args.glob.is_empty() { writeln!( - ui.warning(), + ui.warning_no_heading(), "--glob has been deprecated. Please prefix the pattern with `glob:` instead." )?; } @@ -541,7 +541,10 @@ fn cmd_branch_track( let mut names = Vec::new(); for (name, remote_ref) in find_remote_branches(view, &args.names)? { if remote_ref.is_tracking() { - writeln!(ui.warning(), "Remote branch already tracked: {name}")?; + writeln!( + ui.warning_no_heading(), + "Remote branch already tracked: {name}" + )?; } else { names.push(name); } @@ -574,11 +577,14 @@ fn cmd_branch_untrack( if name.remote == git::REMOTE_NAME_FOR_LOCAL_GIT_REPO { // This restriction can be lifted if we want to support untracked @git branches. writeln!( - ui.warning(), + ui.warning_no_heading(), "Git-tracking branch cannot be untracked: {name}" )?; } else if !remote_ref.is_tracking() { - writeln!(ui.warning(), "Remote branch not tracked yet: {name}")?; + writeln!( + ui.warning_no_heading(), + "Remote branch not tracked yet: {name}" + )?; } else { names.push(name); } diff --git a/cli/src/commands/checkout.rs b/cli/src/commands/checkout.rs index fe2fa902b..588f59c29 100644 --- a/cli/src/commands/checkout.rs +++ b/cli/src/commands/checkout.rs @@ -43,12 +43,12 @@ pub(crate) fn cmd_checkout( args: &CheckoutArgs, ) -> Result<(), CommandError> { writeln!( - ui.warning(), - "warning: `jj checkout` is deprecated; use `jj new` instead, which is equivalent" + ui.warning_with_heading("warning: "), + "`jj checkout` is deprecated; use `jj new` instead, which is equivalent" )?; writeln!( - ui.warning(), - "warning: `jj checkout` will be removed in a future version, and this will be a hard error" + ui.warning_with_heading("warning: "), + "`jj checkout` will be removed in a future version, and this will be a hard error" )?; let mut workspace_command = command.workspace_helper(ui)?; let target = workspace_command.resolve_single_rev(&args.revision)?; diff --git a/cli/src/commands/commit.rs b/cli/src/commands/commit.rs index 7bc79afa6..8d54ad5da 100644 --- a/cli/src/commands/commit.rs +++ b/cli/src/commands/commit.rs @@ -78,7 +78,7 @@ new working-copy commit. let middle_tree = tx.repo().store().get_root_tree(&tree_id)?; if !args.paths.is_empty() && middle_tree.id() == base_tree.id() { writeln!( - ui.warning(), + ui.warning_no_heading(), "The given paths do not match any file: {}", args.paths.join(" ") )?; diff --git a/cli/src/commands/config.rs b/cli/src/commands/config.rs index 4d1ce287e..366ce64e0 100644 --- a/cli/src/commands/config.rs +++ b/cli/src/commands/config.rs @@ -257,9 +257,9 @@ pub(crate) fn cmd_config_list( if !wrote_values { // Note to stderr explaining why output is empty. if let Some(name) = &args.name { - writeln!(ui.warning(), "No matching config key for {name}")?; + writeln!(ui.warning_no_heading(), "No matching config key for {name}")?; } else { - writeln!(ui.warning(), "No config to list")?; + writeln!(ui.warning_no_heading(), "No config to list")?; } } Ok(()) diff --git a/cli/src/commands/git.rs b/cli/src/commands/git.rs index 95668c2b3..ac74b84fb 100644 --- a/cli/src/commands/git.rs +++ b/cli/src/commands/git.rs @@ -703,7 +703,7 @@ fn cmd_git_clone( }; if let Err(err) = clean_up_dirs() { writeln!( - ui.warning(), + ui.warning_no_heading(), "Failed to clean up {}: {}", canonical_wc_path.display(), err @@ -1058,7 +1058,7 @@ struct RejectedBranchUpdateReason { impl RejectedBranchUpdateReason { fn print(&self, ui: &Ui) -> io::Result<()> { - writeln!(ui.warning(), "{}", self.message)?; + writeln!(ui.warning_no_heading(), "{}", self.message)?; if let Some(hint) = &self.hint { writeln!(ui.hint_with_heading("Hint: "), "{hint}")?; } @@ -1197,7 +1197,7 @@ fn find_branches_targeted_by_revisions<'a>( revision_commit_ids.extend(current_branches_revset.iter()); if revision_commit_ids.is_empty() { writeln!( - ui.warning(), + ui.warning_no_heading(), "No branches found in the default push revset: \ remote_branches(remote={remote_name})..@" )?; @@ -1212,7 +1212,7 @@ fn find_branches_targeted_by_revisions<'a>( if commit_ids.peek().is_none() { let rev_str: &str = rev_str; writeln!( - ui.warning(), + ui.warning_no_heading(), "No branches point to the specified revisions: {rev_str}" )?; } diff --git a/cli/src/commands/init.rs b/cli/src/commands/init.rs index 6f442a3fe..1d334f29f 100644 --- a/cli/src/commands/init.rs +++ b/cli/src/commands/init.rs @@ -62,8 +62,8 @@ pub(crate) fn cmd_init( if args.git || args.git_repo.is_some() { git::git_init(ui, command, &wc_path, colocate, args.git_repo.as_deref())?; writeln!( - ui.warning(), - "warning: `--git` and `--git-repo` are deprecated. + ui.warning_with_heading("warning: "), + "`--git` and `--git-repo` are deprecated. Use `jj git init` instead" )?; } else { diff --git a/cli/src/commands/log.rs b/cli/src/commands/log.rs index 555a917b8..f13863b1c 100644 --- a/cli/src/commands/log.rs +++ b/cli/src/commands/log.rs @@ -249,18 +249,18 @@ pub(crate) fn cmd_log( if only_path == "." && workspace_command.parse_file_path(only_path)?.is_root() { // For users of e.g. Mercurial, where `.` indicates the current commit. writeln!( - ui.warning(), - "warning: The argument {only_path:?} is being interpreted as a path, but this is \ - often not useful because all non-empty commits touch '.'. If you meant to show \ - the working copy commit, pass -r '@' instead." + ui.warning_with_heading("warning: "), + "The argument {only_path:?} is being interpreted as a path, but this is often not \ + useful because all non-empty commits touch '.'. If you meant to show the \ + working copy commit, pass -r '@' instead." )?; } else if revset.is_empty() && revset::parse(only_path, &workspace_command.revset_parse_context()).is_ok() { writeln!( - ui.warning(), - "warning: The argument {only_path:?} is being interpreted as a path. To specify a \ - revset, pass -r {only_path:?} instead." + ui.warning_with_heading("warning: "), + "The argument {only_path:?} is being interpreted as a path. To specify a revset, \ + pass -r {only_path:?} instead." )?; } } diff --git a/cli/src/commands/merge.rs b/cli/src/commands/merge.rs index 8bd0b9d99..4fa797f3e 100644 --- a/cli/src/commands/merge.rs +++ b/cli/src/commands/merge.rs @@ -26,12 +26,12 @@ pub(crate) fn cmd_merge( args: &new::NewArgs, ) -> Result<(), CommandError> { writeln!( - ui.warning(), - "warning: `jj merge` is deprecated; use `jj new` instead, which is equivalent" + ui.warning_with_heading("warning: "), + "`jj merge` is deprecated; use `jj new` instead, which is equivalent" )?; writeln!( - ui.warning(), - "warning: `jj merge` will be removed in a future version, and this will be a hard error" + ui.warning_with_heading("warning: "), + "`jj merge` will be removed in a future version, and this will be a hard error" )?; if args.revisions.len() < 2 { return Err(cli_error("Merge requires at least two revisions")); diff --git a/cli/src/commands/move.rs b/cli/src/commands/move.rs index 0759655be..f81051475 100644 --- a/cli/src/commands/move.rs +++ b/cli/src/commands/move.rs @@ -63,12 +63,12 @@ pub(crate) fn cmd_move( args: &MoveArgs, ) -> Result<(), CommandError> { writeln!( - ui.warning(), - "warning: `jj move` is deprecated; use `jj squash` instead, which is equivalent" + ui.warning_with_heading("warning: "), + "`jj move` is deprecated; use `jj squash` instead, which is equivalent" )?; writeln!( - ui.warning(), - "warning: `jj move` will be removed in a future version, and this will be a hard error" + ui.warning_with_heading("warning: "), + "`jj move` will be removed in a future version, and this will be a hard error" )?; let mut workspace_command = command.workspace_helper(ui)?; let source = workspace_command.resolve_single_rev(args.from.as_deref().unwrap_or("@"))?; diff --git a/cli/src/commands/operation.rs b/cli/src/commands/operation.rs index 0ea25d3db..8f05b4215 100644 --- a/cli/src/commands/operation.rs +++ b/cli/src/commands/operation.rs @@ -383,7 +383,7 @@ fn cmd_op_abandon( let old_op_id = locked_ws.locked_wc().old_operation_id(); if old_op_id != current_head_op.id() { writeln!( - ui.warning(), + ui.warning_no_heading(), "The working copy operation {} is not updated because it differs from the repo {}.", short_operation_hash(old_op_id), short_operation_hash(current_head_op.id()), diff --git a/cli/src/commands/split.rs b/cli/src/commands/split.rs index 66589e510..3d2cfbf8d 100644 --- a/cli/src/commands/split.rs +++ b/cli/src/commands/split.rs @@ -94,7 +94,7 @@ don't make any changes, then the operation will be aborted. if selected_tree_id == base_tree.id() { // The user selected nothing, so the first commit will be empty. writeln!( - ui.warning(), + ui.warning_no_heading(), "The given paths do not match any file: {}", args.paths.join(" ") )?; diff --git a/cli/src/commands/squash.rs b/cli/src/commands/squash.rs index 1409398ce..5776a7258 100644 --- a/cli/src/commands/squash.rs +++ b/cli/src/commands/squash.rs @@ -199,9 +199,9 @@ from the source will be moved into the destination. .is_ok() { writeln!( - ui.warning(), - "warning: The argument {only_path:?} is being interpreted as a path. To \ - specify a revset, pass -r {only_path:?} instead." + ui.warning_with_heading("warning: "), + "The argument {only_path:?} is being interpreted as a path. To specify a \ + revset, pass -r {only_path:?} instead." )?; } } diff --git a/cli/src/commands/util.rs b/cli/src/commands/util.rs index b0f1a7033..c7b9ba184 100644 --- a/cli/src/commands/util.rs +++ b/cli/src/commands/util.rs @@ -130,9 +130,9 @@ fn cmd_util_completion( let mut app = command.app().clone(); let warn = |shell| { writeln!( - ui.warning(), - "Warning: `jj util completion --{shell}` will be removed in a future version, and \ - this will be a hard error" + ui.warning_with_heading("Warning: "), + "`jj util completion --{shell}` will be removed in a future version, and this will be \ + a hard error" )?; writeln!( ui.hint_with_heading("Hint: "), diff --git a/cli/src/config/colors.toml b/cli/src/config/colors.toml index 75ec17a9a..c9a7260be 100644 --- a/cli/src/config/colors.toml +++ b/cli/src/config/colors.toml @@ -2,6 +2,7 @@ "error" = "red" "warning" = "yellow" "hint" = "cyan" +"warning heading" = { bold = true } "hint heading" = { bold = true } "conflict_description" = "yellow" diff --git a/cli/src/git_util.rs b/cli/src/git_util.rs index 14e14d71c..1704fb4f6 100644 --- a/cli/src/git_util.rs +++ b/cli/src/git_util.rs @@ -392,7 +392,7 @@ pub fn print_failed_git_export( failed_branches: &[FailedRefExport], ) -> Result<(), std::io::Error> { if !failed_branches.is_empty() { - writeln!(ui.warning(), "Failed to export some branches:")?; + writeln!(ui.warning_no_heading(), "Failed to export some branches:")?; let mut formatter = ui.stderr_formatter(); for FailedRefExport { name, reason } in failed_branches { write!(formatter, " ")?; diff --git a/cli/src/merge_tools/external.rs b/cli/src/merge_tools/external.rs index 535e1ddd0..5b50578af 100644 --- a/cli/src/merge_tools/external.rs +++ b/cli/src/merge_tools/external.rs @@ -324,7 +324,12 @@ pub fn generate_diff( let exit_status = child.wait().map_err(ExternalToolError::Io)?; tracing::info!(?cmd, ?exit_status, "The external diff generator exited:"); if !exit_status.success() { - writeln!(ui.warning(), "{}", format_tool_aborted(&exit_status)).ok(); + writeln!( + ui.warning_no_heading(), + "{}", + format_tool_aborted(&exit_status) + ) + .ok(); } copy_result.map_err(ExternalToolError::Io)?; Ok(()) diff --git a/cli/src/revset_util.rs b/cli/src/revset_util.rs index 5a228377b..24c9e311f 100644 --- a/cli/src/revset_util.rs +++ b/cli/src/revset_util.rs @@ -62,7 +62,10 @@ pub fn load_revset_aliases( .map_err(|e| e.to_string()) .and_then(|v| aliases_map.insert(&decl, v).map_err(|e| e.to_string())); if let Err(s) = r { - writeln!(ui.warning(), r#"Failed to load "{TABLE_KEY}.{decl}": {s}"#)?; + writeln!( + ui.warning_no_heading(), + r#"Failed to load "{TABLE_KEY}.{decl}": {s}"# + )?; } } } diff --git a/cli/src/ui.rs b/cli/src/ui.rs index 81dba91b5..f69357f12 100644 --- a/cli/src/ui.rs +++ b/cli/src/ui.rs @@ -292,7 +292,7 @@ impl Ui { Err(e) => { // The pager executable couldn't be found or couldn't be run writeln!( - self.warning(), + self.warning_no_heading(), "Failed to spawn pager '{name}': {e}. Consider using the `:builtin` \ pager.", name = self.pager_cmd.split_name(), @@ -386,10 +386,19 @@ impl Ui { HeadingLabeledWriter::new(self.stderr_formatter(), "hint", heading) } - pub fn warning(&self) -> LabeledWriter, &'static str> { + /// Writer to print warning without the "Warning: " heading. + pub fn warning_no_heading(&self) -> LabeledWriter, &'static str> { LabeledWriter::new(self.stderr_formatter(), "warning") } + /// Writer to print warning with the given heading. + pub fn warning_with_heading( + &self, + heading: H, + ) -> HeadingLabeledWriter, &'static str, H> { + HeadingLabeledWriter::new(self.stderr_formatter(), "warning", heading) + } + pub fn error(&self) -> LabeledWriter, &'static str> { LabeledWriter::new(self.stderr_formatter(), "error") } @@ -474,7 +483,7 @@ impl Ui { return Ok(choice); } - writeln!(self.warning(), "unrecognized response")?; + writeln!(self.warning_no_heading(), "unrecognized response")?; } } diff --git a/cli/tests/test_global_opts.rs b/cli/tests/test_global_opts.rs index c8937827a..3c0e01652 100644 --- a/cli/tests/test_global_opts.rs +++ b/cli/tests/test_global_opts.rs @@ -388,6 +388,8 @@ fn test_color_config() { #[test] fn test_color_ui_messages() { 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"); test_env.add_config("ui.color = 'always'"); // hint and error @@ -397,6 +399,12 @@ fn test_color_ui_messages() { Run `jj config set --user ui.default-command log` to disable this message. Error: There is no jj repo in "." "###); + + // warning + let (_stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["log", "@"]); + insta::assert_snapshot!(stderr, @r###" + warning: The argument "@" is being interpreted as a path. To specify a revset, pass -r "@" instead. + "###); } #[test]