cli: add a Ui::status() helper for writing non-error to stderr

This clarifies that status messages are not errors, and allows us to
implement a global `--quiet` flag for silencing status messages.
This commit is contained in:
Martin von Zweigbergk 2024-02-14 05:52:56 -08:00 committed by Martin von Zweigbergk
parent 2dc95bb18b
commit aeaab8aad3
22 changed files with 103 additions and 101 deletions

View file

@ -48,7 +48,7 @@ fn run_custom_command(
.write()?; .write()?;
tx.finish(ui, "Frobnicate")?; tx.finish(ui, "Frobnicate")?;
writeln!( writeln!(
ui.stderr(), ui.status(),
"Frobnicated revision: {}", "Frobnicated revision: {}",
workspace_command.format_commit_summary(&new_commit) workspace_command.format_commit_summary(&new_commit)
)?; )?;

View file

@ -327,7 +327,7 @@ impl CommandHelper {
repo_loader.op_store(), repo_loader.op_store(),
|op_heads| { |op_heads| {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Concurrent modification detected, resolving automatically.", "Concurrent modification detected, resolving automatically.",
)?; )?;
let base_repo = repo_loader.load_at(&op_heads[0])?; let base_repo = repo_loader.load_at(&op_heads[0])?;
@ -339,7 +339,7 @@ impl CommandHelper {
let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?; let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Rebased {num_rebased} descendant commits onto commits rewritten \ "Rebased {num_rebased} descendant commits onto commits rewritten \
by other operation" by other operation"
)?; )?;
@ -531,7 +531,7 @@ impl WorkspaceCommandHelper {
locked_ws.finish(self.user_repo.repo.op_id().clone())?; locked_ws.finish(self.user_repo.repo.op_id().clone())?;
if old_git_head.is_present() { if old_git_head.is_present() {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Reset the working copy parent to the new Git HEAD." "Reset the working copy parent to the new Git HEAD."
)?; )?;
} else { } else {
@ -570,13 +570,13 @@ impl WorkspaceCommandHelper {
let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?; let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Rebased {num_rebased} descendant commits off of commits rewritten from git" "Rebased {num_rebased} descendant commits off of commits rewritten from git"
)?; )?;
} }
self.finish_transaction(ui, tx, "import git refs")?; self.finish_transaction(ui, tx, "import git refs")?;
writeln!( writeln!(
ui.stderr(), ui.status(),
"Done importing changes from the underlying Git repo." "Done importing changes from the underlying Git repo."
)?; )?;
Ok(()) Ok(())
@ -1117,7 +1117,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
let num_rebased = mut_repo.rebase_descendants(&self.settings)?; let num_rebased = mut_repo.rebase_descendants(&self.settings)?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Rebased {num_rebased} descendant commits onto updated working copy" "Rebased {num_rebased} descendant commits onto updated working copy"
)?; )?;
} }
@ -1147,7 +1147,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
new_commit, new_commit,
)?; )?;
if Some(new_commit) != maybe_old_commit { if Some(new_commit) != maybe_old_commit {
let mut formatter = ui.stderr_formatter(); let mut formatter = ui.status();
let template = self.commit_summary_template(); let template = self.commit_summary_template();
write!(formatter, "Working copy now at: ")?; write!(formatter, "Working copy now at: ")?;
formatter.with_label("working_copy", |fmt| template.format(new_commit, fmt))?; formatter.with_label("working_copy", |fmt| template.format(new_commit, fmt))?;
@ -1177,12 +1177,12 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
description: impl Into<String>, description: impl Into<String>,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
if !tx.mut_repo().has_changes() { if !tx.mut_repo().has_changes() {
writeln!(ui.stderr(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
return Ok(()); return Ok(());
} }
let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?; let num_rebased = tx.mut_repo().rebase_descendants(&self.settings)?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
let old_repo = tx.base_repo().clone(); let old_repo = tx.base_repo().clone();
@ -1279,7 +1279,7 @@ See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-workin
.retain(|change_id, _commits| !removed_conflicts_by_change_id.contains_key(change_id)); .retain(|change_id, _commits| !removed_conflicts_by_change_id.contains_key(change_id));
// TODO: Also report new divergence and maybe resolved divergence // TODO: Also report new divergence and maybe resolved divergence
let mut fmt = ui.stderr_formatter(); let mut fmt = ui.status();
let template = self.commit_summary_template(); let template = self.commit_summary_template();
if !resolved_conflicts_by_change_id.is_empty() { if !resolved_conflicts_by_change_id.is_empty() {
writeln!( writeln!(
@ -1597,7 +1597,7 @@ pub fn print_checkout_stats(
) -> Result<(), std::io::Error> { ) -> Result<(), std::io::Error> {
if stats.added_files > 0 || stats.updated_files > 0 || stats.removed_files > 0 { if stats.added_files > 0 || stats.updated_files > 0 || stats.removed_files > 0 {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Added {} files, modified {} files, removed {} files", "Added {} files, modified {} files, removed {} files",
stats.added_files, stats.added_files,
stats.updated_files, stats.updated_files,
@ -1642,7 +1642,7 @@ pub fn print_trackable_remote_branches(ui: &Ui, view: &View) -> io::Result<()> {
ui.hint_default(), ui.hint_default(),
"The following remote branches aren't associated with the existing local branches:" "The following remote branches aren't associated with the existing local branches:"
)?; )?;
let mut formatter = ui.stderr_formatter(); let mut formatter = ui.status();
for full_name in &remote_branch_names { for full_name in &remote_branch_names {
write!(formatter, " ")?; write!(formatter, " ")?;
writeln!(formatter.labeled("branch"), "{full_name}")?; writeln!(formatter.labeled("branch"), "{full_name}")?;

View file

@ -55,7 +55,7 @@ pub(crate) fn cmd_abandon(
.evaluate_to_commits()? .evaluate_to_commits()?
.try_collect()?; .try_collect()?;
if to_abandon.is_empty() { if to_abandon.is_empty() {
writeln!(ui.stderr(), "No revisions to abandon.")?; writeln!(ui.status(), "No revisions to abandon.")?;
return Ok(()); return Ok(());
} }
workspace_command.check_rewritable(&to_abandon)?; workspace_command.check_rewritable(&to_abandon)?;
@ -67,12 +67,12 @@ pub(crate) fn cmd_abandon(
let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?; let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?;
if to_abandon.len() == 1 { if to_abandon.len() == 1 {
write!(ui.stderr(), "Abandoned commit ")?; write!(ui.status(), "Abandoned commit ")?;
tx.base_workspace_helper() tx.base_workspace_helper()
.write_commit_summary(ui.stderr_formatter().as_mut(), &to_abandon[0])?; .write_commit_summary(ui.status().as_mut(), &to_abandon[0])?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
} else if !args.summary { } else if !args.summary {
let mut formatter = ui.stderr_formatter(); let mut formatter = ui.status();
let template = tx.base_workspace_helper().commit_summary_template(); let template = tx.base_workspace_helper().commit_summary_template();
writeln!(formatter, "Abandoned the following commits:")?; writeln!(formatter, "Abandoned the following commits:")?;
for commit in &to_abandon { for commit in &to_abandon {
@ -81,11 +81,11 @@ pub(crate) fn cmd_abandon(
writeln!(formatter)?; writeln!(formatter)?;
} }
} else { } else {
writeln!(ui.stderr(), "Abandoned {} commits.", &to_abandon.len())?; writeln!(ui.status(), "Abandoned {} commits.", &to_abandon.len())?;
} }
if num_rebased > 0 { if num_rebased > 0 {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Rebased {num_rebased} descendant commits onto parents of abandoned commits" "Rebased {num_rebased} descendant commits onto parents of abandoned commits"
)?; )?;
} }

View file

@ -116,7 +116,7 @@ where
let result = routine(); let result = routine();
let after = Instant::now(); let after = Instant::now();
writeln!( writeln!(
ui.stderr(), ui.status(),
"First run took {:?} and produced: {:?}", "First run took {:?} and produced: {:?}",
after.duration_since(before), after.duration_since(before),
result result
@ -203,7 +203,7 @@ fn bench_revset<M: Measurement>(
group: &mut BenchmarkGroup<M>, group: &mut BenchmarkGroup<M>,
revset: &str, revset: &str,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
writeln!(ui.stderr(), "----------Testing revset: {revset}----------")?; writeln!(ui.status(), "----------Testing revset: {revset}----------")?;
let expression = revset::optimize(workspace_command.parse_revset(revset)?.expression().clone()); let expression = revset::optimize(workspace_command.parse_revset(revset)?.expression().clone());
// Time both evaluation and iteration. // Time both evaluation and iteration.
let routine = |workspace_command: &WorkspaceCommandHelper, expression: Rc<RevsetExpression>| { let routine = |workspace_command: &WorkspaceCommandHelper, expression: Rc<RevsetExpression>| {
@ -220,7 +220,7 @@ fn bench_revset<M: Measurement>(
let result = routine(workspace_command, expression.clone()); let result = routine(workspace_command, expression.clone());
let after = Instant::now(); let after = Instant::now();
writeln!( writeln!(
ui.stderr(), ui.status(),
"First run took {:?} and produced {result} commits", "First run took {:?} and produced {result} commits",
after.duration_since(before), after.duration_since(before),
)?; )?;

View file

@ -497,7 +497,7 @@ fn cmd_branch_delete(
} }
tx.finish(ui, format!("delete {}", make_branch_term(&names)))?; tx.finish(ui, format!("delete {}", make_branch_term(&names)))?;
if names.len() > 1 { if names.len() > 1 {
writeln!(ui.stderr(), "Deleted {} branches.", names.len())?; writeln!(ui.status(), "Deleted {} branches.", names.len())?;
} }
Ok(()) Ok(())
} }
@ -523,7 +523,7 @@ fn cmd_branch_forget(
} }
tx.finish(ui, format!("forget {}", make_branch_term(&names)))?; tx.finish(ui, format!("forget {}", make_branch_term(&names)))?;
if names.len() > 1 { if names.len() > 1 {
writeln!(ui.stderr(), "Forgot {} branches.", names.len())?; writeln!(ui.status(), "Forgot {} branches.", names.len())?;
} }
Ok(()) Ok(())
} }
@ -554,7 +554,7 @@ fn cmd_branch_track(
tx.finish(ui, format!("track remote {}", make_branch_term(&names)))?; tx.finish(ui, format!("track remote {}", make_branch_term(&names)))?;
if names.len() > 1 { if names.len() > 1 {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Started tracking {} remote branches.", "Started tracking {} remote branches.",
names.len() names.len()
)?; )?;
@ -594,7 +594,7 @@ fn cmd_branch_untrack(
tx.finish(ui, format!("untrack remote {}", make_branch_term(&names)))?; tx.finish(ui, format!("untrack remote {}", make_branch_term(&names)))?;
if names.len() > 1 { if names.len() > 1 {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Stopped tracking {} remote branches.", "Stopped tracking {} remote branches.",
names.len() names.len()
)?; )?;

View file

@ -259,7 +259,7 @@ fn cmd_debug_reindex(
.build_index_at_operation(&op, repo_loader.store()) .build_index_at_operation(&op, repo_loader.store())
.map_err(internal_error)?; .map_err(internal_error)?;
writeln!( writeln!(
ui.stderr(), ui.status(),
"Finished indexing {:?} commits.", "Finished indexing {:?} commits.",
default_index.as_composite().stats().num_commits default_index.as_composite().stats().num_commits
)?; )?;
@ -358,7 +358,7 @@ fn cmd_debug_watchman(
}; };
locked_local_wc.reset_watchman()?; locked_local_wc.reset_watchman()?;
locked_ws.finish(repo.op_id().clone())?; locked_ws.finish(repo.op_id().clone())?;
writeln!(ui.stderr(), "Reset Watchman clock")?; writeln!(ui.status(), "Reset Watchman clock")?;
} }
} }
Ok(()) Ok(())

View file

@ -83,7 +83,7 @@ pub(crate) fn cmd_describe(
edit_description(workspace_command.repo(), &template, command.settings())? edit_description(workspace_command.repo(), &template, command.settings())?
}; };
if description == *commit.description() && !args.reset_author { if description == *commit.description() && !args.reset_author {
writeln!(ui.stderr(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
} else { } else {
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let mut commit_builder = tx let mut commit_builder = tx

View file

@ -97,7 +97,7 @@ don't make any changes, then the operation will be aborted.",
let tree = target_commit.tree()?; let tree = target_commit.tree()?;
let tree_id = diff_editor.edit(&base_tree, &tree, &EverythingMatcher, Some(&instructions))?; let tree_id = diff_editor.edit(&base_tree, &tree, &EverythingMatcher, Some(&instructions))?;
if tree_id == *target_commit.tree_id() { if tree_id == *target_commit.tree_id() {
writeln!(ui.stderr(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
} else { } else {
let mut_repo = tx.mut_repo(); let mut_repo = tx.mut_repo();
let new_commit = mut_repo let new_commit = mut_repo
@ -107,11 +107,11 @@ don't make any changes, then the operation will be aborted.",
// rebase_descendants early; otherwise `new_commit` would always have // rebase_descendants early; otherwise `new_commit` would always have
// a conflicted change id at this point. // a conflicted change id at this point.
let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?; let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?;
write!(ui.stderr(), "Created ")?; write!(ui.status(), "Created ")?;
tx.write_commit_summary(ui.stderr_formatter().as_mut(), &new_commit)?; tx.write_commit_summary(ui.status().as_mut(), &new_commit)?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
tx.finish(ui, format!("edit commit {}", target_commit.id().hex()))?; tx.finish(ui, format!("edit commit {}", target_commit.id().hex()))?;
} }

View file

@ -47,7 +47,7 @@ pub(crate) fn cmd_duplicate(
.evaluate_to_commit_ids()? .evaluate_to_commit_ids()?
.collect(); // in reverse topological order .collect(); // in reverse topological order
if to_duplicate.is_empty() { if to_duplicate.is_empty() {
writeln!(ui.stderr(), "No revisions to duplicate.")?; writeln!(ui.status(), "No revisions to duplicate.")?;
return Ok(()); return Ok(());
} }
if to_duplicate.last() == Some(workspace_command.repo().store().root_commit_id()) { if to_duplicate.last() == Some(workspace_command.repo().store().root_commit_id()) {
@ -78,9 +78,9 @@ pub(crate) fn cmd_duplicate(
} }
for (old_id, new_commit) in &duplicated_old_to_new { for (old_id, new_commit) in &duplicated_old_to_new {
write!(ui.stderr(), "Duplicated {} as ", short_commit_hash(old_id))?; write!(ui.status(), "Duplicated {} as ", short_commit_hash(old_id))?;
tx.write_commit_summary(ui.stderr_formatter().as_mut(), new_commit)?; tx.write_commit_summary(ui.status().as_mut(), new_commit)?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
} }
tx.finish(ui, format!("duplicate {} commit(s)", to_duplicate.len()))?; tx.finish(ui, format!("duplicate {} commit(s)", to_duplicate.len()))?;
Ok(()) Ok(())

View file

@ -46,7 +46,7 @@ pub(crate) fn cmd_edit(
let new_commit = workspace_command.resolve_single_rev(&args.revision)?; let new_commit = workspace_command.resolve_single_rev(&args.revision)?;
workspace_command.check_rewritable([&new_commit])?; workspace_command.check_rewritable([&new_commit])?;
if workspace_command.get_wc_commit_id() == Some(new_commit.id()) { if workspace_command.get_wc_commit_id() == Some(new_commit.id()) {
writeln!(ui.stderr(), "Already editing that commit")?; writeln!(ui.status(), "Already editing that commit")?;
} else { } else {
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
tx.edit(&new_commit)?; tx.edit(&new_commit)?;

View file

@ -482,7 +482,7 @@ fn init_git_refs(
} }
let repo = tx.commit("import git refs"); let repo = tx.commit("import git refs");
writeln!( writeln!(
ui.stderr(), ui.status(),
"Done importing changes from the underlying Git repo." "Done importing changes from the underlying Git repo."
)?; )?;
Ok(repo) Ok(repo)
@ -509,7 +509,7 @@ fn cmd_git_init(
let relative_wc_path = file_util::relative_path(cwd, &wc_path); let relative_wc_path = file_util::relative_path(cwd, &wc_path);
writeln!( writeln!(
ui.stderr(), ui.status(),
r#"Initialized repo in "{}""#, r#"Initialized repo in "{}""#,
relative_wc_path.display() relative_wc_path.display()
)?; )?;
@ -747,7 +747,7 @@ fn do_git_clone(
}; };
let git_repo = get_git_repo(repo.store())?; let git_repo = get_git_repo(repo.store())?;
writeln!( writeln!(
ui.stderr(), ui.status(),
r#"Fetching into new repo in "{}""#, r#"Fetching into new repo in "{}""#,
wc_path.display() wc_path.display()
)?; )?;
@ -857,7 +857,7 @@ fn cmd_git_push(
match classify_branch_update(branch_name, &remote, targets) { match classify_branch_update(branch_name, &remote, targets) {
Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)), Ok(Some(update)) => branch_updates.push((branch_name.to_owned(), update)),
Ok(None) => writeln!( Ok(None) => writeln!(
ui.stderr(), ui.status(),
"Branch {branch_name}@{remote} already matches {branch_name}", "Branch {branch_name}@{remote} already matches {branch_name}",
)?, )?,
Err(reason) => return Err(reason.into()), Err(reason) => return Err(reason.into()),
@ -896,7 +896,7 @@ fn cmd_git_push(
); );
} }
if branch_updates.is_empty() { if branch_updates.is_empty() {
writeln!(ui.stderr(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
return Ok(()); return Ok(());
} }
@ -958,20 +958,20 @@ fn cmd_git_push(
} }
} }
writeln!(ui.stderr(), "Branch changes to push to {}:", &remote)?; writeln!(ui.status(), "Branch changes to push to {}:", &remote)?;
for (branch_name, update) in &branch_updates { for (branch_name, update) in &branch_updates {
match (&update.old_target, &update.new_target) { match (&update.old_target, &update.new_target) {
(Some(old_target), Some(new_target)) => { (Some(old_target), Some(new_target)) => {
if force_pushed_branches.contains(branch_name) { if force_pushed_branches.contains(branch_name) {
writeln!( writeln!(
ui.stderr(), ui.status(),
" Force branch {branch_name} from {} to {}", " Force branch {branch_name} from {} to {}",
short_commit_hash(old_target), short_commit_hash(old_target),
short_commit_hash(new_target) short_commit_hash(new_target)
)?; )?;
} else { } else {
writeln!( writeln!(
ui.stderr(), ui.status(),
" Move branch {branch_name} from {} to {}", " Move branch {branch_name} from {} to {}",
short_commit_hash(old_target), short_commit_hash(old_target),
short_commit_hash(new_target) short_commit_hash(new_target)
@ -980,14 +980,14 @@ fn cmd_git_push(
} }
(Some(old_target), None) => { (Some(old_target), None) => {
writeln!( writeln!(
ui.stderr(), ui.status(),
" Delete branch {branch_name} from {}", " Delete branch {branch_name} from {}",
short_commit_hash(old_target) short_commit_hash(old_target)
)?; )?;
} }
(None, Some(new_target)) => { (None, Some(new_target)) => {
writeln!( writeln!(
ui.stderr(), ui.status(),
" Add branch {branch_name} to {}", " Add branch {branch_name} to {}",
short_commit_hash(new_target) short_commit_hash(new_target)
)?; )?;
@ -999,7 +999,7 @@ fn cmd_git_push(
} }
if args.dry_run { if args.dry_run {
writeln!(ui.stderr(), "Dry-run requested, not pushing.")?; writeln!(ui.status(), "Dry-run requested, not pushing.")?;
return Ok(()); return Ok(());
} }
@ -1129,7 +1129,7 @@ fn update_change_branches(
} }
if view.get_local_branch(&branch_name).is_absent() { if view.get_local_branch(&branch_name).is_absent() {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Creating branch {} for revision {}", "Creating branch {} for revision {}",
branch_name, branch_name,
change_str.deref() change_str.deref()
@ -1268,7 +1268,7 @@ fn cmd_git_submodule_print_gitmodules(
let gitmodules_path = RepoPath::from_internal_string(".gitmodules"); let gitmodules_path = RepoPath::from_internal_string(".gitmodules");
let mut gitmodules_file = match tree.path_value(gitmodules_path).into_resolved() { let mut gitmodules_file = match tree.path_value(gitmodules_path).into_resolved() {
Ok(None) => { Ok(None) => {
writeln!(ui.stderr(), "No submodules!")?; writeln!(ui.status(), "No submodules!")?;
return Ok(()); return Ok(());
} }
Ok(Some(TreeValue::File { id, .. })) => repo.store().read_file(gitmodules_path, &id)?, Ok(Some(TreeValue::File { id, .. })) => repo.store().read_file(gitmodules_path, &id)?,

View file

@ -79,7 +79,7 @@ Set `ui.allow-init-native` to allow initializing a repo with the native backend.
let relative_wc_path = file_util::relative_path(cwd, &wc_path); let relative_wc_path = file_util::relative_path(cwd, &wc_path);
writeln!( writeln!(
ui.stderr(), ui.status(),
"Initialized repo in \"{}\"", "Initialized repo in \"{}\"",
relative_wc_path.display() relative_wc_path.display()
)?; )?;

View file

@ -189,15 +189,15 @@ Please use `jj new 'all:x|y'` instead of `jj new --allow-large-revsets x y`.",
} }
num_rebased += tx.mut_repo().rebase_descendants(command.settings())?; num_rebased += tx.mut_repo().rebase_descendants(command.settings())?;
if args.no_edit { if args.no_edit {
write!(ui.stderr(), "Created new commit ")?; write!(ui.status(), "Created new commit ")?;
tx.write_commit_summary(ui.stderr_formatter().as_mut(), &new_commit)?; tx.write_commit_summary(ui.status().as_mut(), &new_commit)?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
} else { } else {
tx.edit(&new_commit).unwrap(); tx.edit(&new_commit).unwrap();
// The description of the new commit will be printed by tx.finish() // The description of the new commit will be printed by tx.finish()
} }
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
tx.finish(ui, "new empty commit")?; tx.finish(ui, "new empty commit")?;
Ok(()) Ok(())

View file

@ -364,11 +364,11 @@ fn cmd_op_abandon(
)?; )?;
let [new_head_id]: [OperationId; 1] = stats.new_head_ids.try_into().unwrap(); let [new_head_id]: [OperationId; 1] = stats.new_head_ids.try_into().unwrap();
if current_head_op.id() == &new_head_id { if current_head_op.id() == &new_head_id {
writeln!(ui.stderr(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
return Ok(()); return Ok(());
} }
writeln!( writeln!(
ui.stderr(), ui.status(),
"Abandoned {} operations and reparented {} descendant operations.", "Abandoned {} operations and reparented {} descendant operations.",
stats.unreachable_count, stats.unreachable_count,
stats.rewritten_count, stats.rewritten_count,

View file

@ -318,7 +318,7 @@ fn rebase_descendants(
let num_rebased = old_commits.len() let num_rebased = old_commits.len()
+ tx.mut_repo() + tx.mut_repo()
.rebase_descendants_with_options(settings, rebase_options)?; .rebase_descendants_with_options(settings, rebase_options)?;
writeln!(ui.stderr(), "Rebased {num_rebased} commits")?; writeln!(ui.status(), "Rebased {num_rebased} commits")?;
let tx_message = if old_commits.len() == 1 { let tx_message = if old_commits.len() == 1 {
format!( format!(
"rebase commit {} and descendants", "rebase commit {} and descendants",
@ -447,9 +447,9 @@ fn rebase_revision(
// longer have any children; they have all been rebased and the originals // longer have any children; they have all been rebased and the originals
// have been abandoned. // have been abandoned.
let skipped_commit_rebase = if old_commit.parents() == new_parents { let skipped_commit_rebase = if old_commit.parents() == new_parents {
write!(ui.stderr(), "Skipping rebase of commit ")?; write!(ui.status(), "Skipping rebase of commit ")?;
tx.write_commit_summary(ui.stderr_formatter().as_mut(), &old_commit)?; tx.write_commit_summary(ui.status().as_mut(), &old_commit)?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
true true
} else { } else {
rebase_commit(settings, tx.mut_repo(), &old_commit, &new_parents)?; rebase_commit(settings, tx.mut_repo(), &old_commit, &new_parents)?;
@ -460,12 +460,12 @@ fn rebase_revision(
if num_rebased_descendants > 0 { if num_rebased_descendants > 0 {
if skipped_commit_rebase { if skipped_commit_rebase {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Rebased {num_rebased_descendants} descendant commits onto parent of commit" "Rebased {num_rebased_descendants} descendant commits onto parent of commit"
)?; )?;
} else { } else {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Also rebased {num_rebased_descendants} descendant commits onto parent of rebased \ "Also rebased {num_rebased_descendants} descendant commits onto parent of rebased \
commit" commit"
)?; )?;
@ -500,7 +500,7 @@ fn log_skipped_rebase_commits_message(
workspace_command: &WorkspaceCommandHelper, workspace_command: &WorkspaceCommandHelper,
commits: &[&Commit], commits: &[&Commit],
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let mut fmt = ui.stderr_formatter(); let mut fmt = ui.status();
let template = workspace_command.commit_summary_template(); let template = workspace_command.commit_summary_template();
if commits.len() == 1 { if commits.len() == 1 {
write!(fmt, "Skipping rebase of commit ")?; write!(fmt, "Skipping rebase of commit ")?;

View file

@ -99,7 +99,7 @@ pub(crate) fn cmd_resolve(
workspace_command.check_rewritable([&commit])?; workspace_command.check_rewritable([&commit])?;
let merge_editor = workspace_command.merge_editor(ui, args.tool.as_deref())?; let merge_editor = workspace_command.merge_editor(ui, args.tool.as_deref())?;
writeln!( writeln!(
ui.stderr(), ui.status(),
"Resolving conflicts in: {}", "Resolving conflicts in: {}",
workspace_command.format_file_path(repo_path) workspace_command.format_file_path(repo_path)
)?; )?;
@ -120,14 +120,10 @@ pub(crate) fn cmd_resolve(
let new_conflicts = new_tree.conflicts().collect_vec(); let new_conflicts = new_tree.conflicts().collect_vec();
if !new_conflicts.is_empty() { if !new_conflicts.is_empty() {
writeln!( writeln!(
ui.stderr(), ui.status(),
"After this operation, some files at this revision still have conflicts:" "After this operation, some files at this revision still have conflicts:"
)?; )?;
print_conflicted_paths( print_conflicted_paths(&new_conflicts, ui.status().as_mut(), &workspace_command)?;
&new_conflicts,
ui.stderr_formatter().as_mut(),
&workspace_command,
)?;
} }
}; };
Ok(()) Ok(())

View file

@ -101,7 +101,7 @@ pub(crate) fn cmd_restore(
let to_tree = to_commit.tree()?; let to_tree = to_commit.tree()?;
let new_tree_id = restore_tree(&from_tree, &to_tree, matcher.as_ref())?; let new_tree_id = restore_tree(&from_tree, &to_tree, matcher.as_ref())?;
if &new_tree_id == to_commit.tree_id() { if &new_tree_id == to_commit.tree_id() {
writeln!(ui.stderr(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
} else { } else {
let mut tx = workspace_command.start_transaction(); let mut tx = workspace_command.start_transaction();
let mut_repo = tx.mut_repo(); let mut_repo = tx.mut_repo();
@ -112,11 +112,11 @@ pub(crate) fn cmd_restore(
// rebase_descendants early; otherwise `new_commit` would always have // rebase_descendants early; otherwise `new_commit` would always have
// a conflicted change id at this point. // a conflicted change id at this point.
let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?; let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?;
write!(ui.stderr(), "Created ")?; write!(ui.status(), "Created ")?;
tx.write_commit_summary(ui.stderr_formatter().as_mut(), &new_commit)?; tx.write_commit_summary(ui.status().as_mut(), &new_commit)?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
tx.finish(ui, format!("restore into commit {}", to_commit.id().hex()))?; tx.finish(ui, format!("restore into commit {}", to_commit.id().hex()))?;
} }

View file

@ -88,7 +88,7 @@ don't make any changes, then the operation will be aborted.
diff_selector.select(&base_tree, &end_tree, matcher.as_ref(), Some(&instructions))?; diff_selector.select(&base_tree, &end_tree, matcher.as_ref(), Some(&instructions))?;
if &selected_tree_id == commit.tree_id() && diff_selector.is_interactive() { if &selected_tree_id == commit.tree_id() && diff_selector.is_interactive() {
// The user selected everything from the original commit. // The user selected everything from the original commit.
writeln!(ui.stderr(), "Nothing changed.")?; writeln!(ui.status(), "Nothing changed.")?;
return Ok(()); return Ok(());
} }
if selected_tree_id == base_tree.id() { if selected_tree_id == base_tree.id() {
@ -150,13 +150,13 @@ don't make any changes, then the operation will be aborted.
.set_rewritten_commit(commit.id().clone(), second_commit.id().clone()); .set_rewritten_commit(commit.id().clone(), second_commit.id().clone());
let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?; let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
write!(ui.stderr(), "First part: ")?; write!(ui.status(), "First part: ")?;
tx.write_commit_summary(ui.stderr_formatter().as_mut(), &first_commit)?; tx.write_commit_summary(ui.status().as_mut(), &first_commit)?;
write!(ui.stderr(), "\nSecond part: ")?; write!(ui.status(), "\nSecond part: ")?;
tx.write_commit_summary(ui.stderr_formatter().as_mut(), &second_commit)?; tx.write_commit_summary(ui.status().as_mut(), &second_commit)?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
tx.finish(ui, format!("split commit {}", commit.id().hex()))?; tx.finish(ui, format!("split commit {}", commit.id().hex()))?;
Ok(()) Ok(())
} }

View file

@ -100,7 +100,7 @@ Make sure they're ignored, then try again.",
} }
let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?; let num_rebased = tx.mut_repo().rebase_descendants(command.settings())?;
if num_rebased > 0 { if num_rebased > 0 {
writeln!(ui.stderr(), "Rebased {num_rebased} descendant commits")?; writeln!(ui.status(), "Rebased {num_rebased} descendant commits")?;
} }
let repo = tx.commit("untrack paths"); let repo = tx.commit("untrack paths");
locked_ws.finish(repo.op_id().clone())?; locked_ws.finish(repo.op_id().clone())?;

View file

@ -166,7 +166,7 @@ fn cmd_workspace_add(
workspace_id, workspace_id,
)?; )?;
writeln!( writeln!(
ui.stderr(), ui.status(),
"Created workspace in \"{}\"", "Created workspace in \"{}\"",
file_util::relative_path(old_workspace_command.workspace_root(), &destination_path) file_util::relative_path(old_workspace_command.workspace_root(), &destination_path)
.display() .display()
@ -329,7 +329,7 @@ fn create_and_check_out_recovery_commit(
locked_workspace.finish(repo.op_id().clone())?; locked_workspace.finish(repo.op_id().clone())?;
writeln!( writeln!(
ui.stderr(), ui.status(),
"Created and checked out recovery commit {}", "Created and checked out recovery commit {}",
short_commit_hash(new_commit.id()) short_commit_hash(new_commit.id())
)?; )?;
@ -357,7 +357,7 @@ fn for_stale_working_copy(
), ),
Err(e @ OpStoreError::ObjectNotFound { .. }) => { Err(e @ OpStoreError::ObjectNotFound { .. }) => {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Failed to read working copy's current operation; attempting recovery. Error \ "Failed to read working copy's current operation; attempting recovery. Error \
message from read attempt: {e}" message from read attempt: {e}"
)?; )?;
@ -401,7 +401,7 @@ fn cmd_workspace_update_stale(
match check_stale_working_copy(locked_ws.locked_wc(), &desired_wc_commit, &repo)? { match check_stale_working_copy(locked_ws.locked_wc(), &desired_wc_commit, &repo)? {
WorkingCopyFreshness::Fresh | WorkingCopyFreshness::Updated(_) => { WorkingCopyFreshness::Fresh | WorkingCopyFreshness::Updated(_) => {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Nothing to do (the working copy is not stale)." "Nothing to do (the working copy is not stale)."
)?; )?;
} }
@ -424,11 +424,11 @@ fn cmd_workspace_update_stale(
) )
})?; })?;
locked_ws.finish(repo.op_id().clone())?; locked_ws.finish(repo.op_id().clone())?;
write!(ui.stderr(), "Working copy now at: ")?; write!(ui.status(), "Working copy now at: ")?;
ui.stderr_formatter().with_label("working_copy", |fmt| { ui.status().with_label("working_copy", |fmt| {
workspace_command.write_commit_summary(fmt, &desired_wc_commit) workspace_command.write_commit_summary(fmt, &desired_wc_commit)
})?; })?;
writeln!(ui.stderr())?; writeln!(ui.status())?;
print_checkout_stats(ui, stats, &desired_wc_commit)?; print_checkout_stats(ui, stats, &desired_wc_commit)?;
} }
} }

View file

@ -192,7 +192,7 @@ impl GitSidebandProgressMessageWriter {
} }
self.scratch.extend_from_slice(&progress_message[i..i + 1]); self.scratch.extend_from_slice(&progress_message[i..i + 1]);
ui.stderr().write_all(&self.scratch)?; ui.status().write_all(&self.scratch)?;
self.scratch.clear(); self.scratch.clear();
index = i + 1; index = i + 1;
@ -212,7 +212,7 @@ impl GitSidebandProgressMessageWriter {
pub fn flush(&mut self, ui: &Ui) -> std::io::Result<()> { pub fn flush(&mut self, ui: &Ui) -> std::io::Result<()> {
if !self.scratch.is_empty() { if !self.scratch.is_empty() {
self.scratch.push(b'\n'); self.scratch.push(b'\n');
ui.stderr().write_all(&self.scratch)?; ui.status().write_all(&self.scratch)?;
self.scratch.clear(); self.scratch.clear();
} }
@ -274,16 +274,16 @@ pub fn print_git_import_stats(
let max_width = refs_stats.iter().map(|x| x.ref_name.width()).max(); let max_width = refs_stats.iter().map(|x| x.ref_name.width()).max();
if let Some(max_width) = max_width { if let Some(max_width) = max_width {
let mut stderr = ui.stderr_formatter(); let mut formatter = ui.status();
for status in refs_stats { for status in refs_stats {
status.output(max_width, has_both_ref_kinds, &mut *stderr)?; status.output(max_width, has_both_ref_kinds, &mut *formatter)?;
} }
} }
} }
if !stats.abandoned_commits.is_empty() { if !stats.abandoned_commits.is_empty() {
writeln!( writeln!(
ui.stderr(), ui.status(),
"Abandoned {} commits that are no longer reachable.", "Abandoned {} commits that are no longer reachable.",
stats.abandoned_commits.len() stats.abandoned_commits.len()
)?; )?;

View file

@ -373,6 +373,12 @@ impl Ui {
}) })
} }
/// Writes a message that's a status update not part of the command's main
/// output.
pub fn status(&self) -> Box<dyn Formatter + '_> {
self.stderr_formatter()
}
/// Writer to print hint with the default "Hint: " heading. /// Writer to print hint with the default "Hint: " heading.
pub fn hint_default( pub fn hint_default(
&self, &self,
@ -382,7 +388,7 @@ impl Ui {
/// Writer to print hint without the "Hint: " heading. /// Writer to print hint without the "Hint: " heading.
pub fn hint_no_heading(&self) -> LabeledWriter<Box<dyn Formatter + '_>, &'static str> { pub fn hint_no_heading(&self) -> LabeledWriter<Box<dyn Formatter + '_>, &'static str> {
LabeledWriter::new(self.stderr_formatter(), "hint") LabeledWriter::new(self.status(), "hint")
} }
/// Writer to print hint with the given heading. /// Writer to print hint with the given heading.
@ -390,7 +396,7 @@ impl Ui {
&self, &self,
heading: H, heading: H,
) -> HeadingLabeledWriter<Box<dyn Formatter + '_>, &'static str, H> { ) -> HeadingLabeledWriter<Box<dyn Formatter + '_>, &'static str, H> {
HeadingLabeledWriter::new(self.stderr_formatter(), "hint", heading) HeadingLabeledWriter::new(self.status(), "hint", heading)
} }
/// Writer to print warning with the default "Warning: " heading. /// Writer to print warning with the default "Warning: " heading.