diff --git a/cli/src/commands/absorb.rs b/cli/src/commands/absorb.rs index 65c0f56c41..d86064e4ea 100644 --- a/cli/src/commands/absorb.rs +++ b/cli/src/commands/absorb.rs @@ -22,6 +22,7 @@ use tracing::instrument; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; +use crate::cli_util::print_unmatched_explicit_paths; use crate::cli_util::print_updated_commits; use crate::command_error::CommandError; use crate::complete; @@ -81,14 +82,20 @@ pub(crate) fn cmd_absorb( .parse_union_revsets(ui, &args.into)? .resolve()?; - let matcher = workspace_command - .parse_file_patterns(ui, &args.paths)? - .to_matcher(); + let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; + let matcher = fileset_expression.to_matcher(); let repo = workspace_command.repo().as_ref(); - let source = AbsorbSource::from_commit(repo, source_commit)?; + let source = AbsorbSource::from_commit(repo, source_commit.clone())?; let selected_trees = split_hunks_to_trees(repo, &source, &destinations, &matcher).block_on()?; + print_unmatched_explicit_paths( + ui, + &workspace_command, + &fileset_expression, + [&source_commit.tree().unwrap()], + )?; + let path_converter = workspace_command.path_converter(); for (path, reason) in selected_trees.skipped_paths { let ui_path = path_converter.format_file_path(&path); diff --git a/cli/src/commands/fix.rs b/cli/src/commands/fix.rs index a1c4a2ff73..d5fea0eb5b 100644 --- a/cli/src/commands/fix.rs +++ b/cli/src/commands/fix.rs @@ -19,8 +19,8 @@ use std::process::Stdio; use clap_complete::ArgValueCompleter; use itertools::Itertools as _; -use jj_lib::backend::CommitId; use jj_lib::backend::FileId; +use jj_lib::commit::Commit; use jj_lib::fileset; use jj_lib::fileset::FilesetDiagnostics; use jj_lib::fileset::FilesetExpression; @@ -38,6 +38,7 @@ use tracing::instrument; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::command_error::config_error; use crate::command_error::print_parse_diagnostics; @@ -134,18 +135,25 @@ pub(crate) fn cmd_fix( let workspace_root = workspace_command.workspace_root().to_owned(); let path_converter = workspace_command.path_converter().to_owned(); let tools_config = get_tools_config(ui, workspace_command.settings())?; - let root_commits: Vec = if args.source.is_empty() { + let root_commits: Vec = if args.source.is_empty() { let revs = workspace_command.settings().get_string("revsets.fix")?; workspace_command.parse_revset(ui, &RevisionArg::from(revs))? } else { workspace_command.parse_union_revsets(ui, &args.source)? } - .evaluate_to_commit_ids()? + .evaluate_to_commits()? .try_collect()?; - workspace_command.check_rewritable(root_commits.iter())?; - let matcher = workspace_command - .parse_file_patterns(ui, &args.paths)? - .to_matcher(); + let root_commit_ids = root_commits + .iter() + .map(|commit| commit.id().clone()) + .collect_vec(); + let root_trees = root_commits + .iter() + .map(|commit| commit.tree().unwrap()) + .collect_vec(); + workspace_command.check_rewritable(root_commit_ids.iter())?; + let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; + let matcher = fileset_expression.to_matcher(); let mut tx = workspace_command.start_transaction(); let mut parallel_fixer = ParallelFileFixer::new(|store, file_to_fix| { @@ -159,8 +167,16 @@ pub(crate) fn cmd_fix( ) .block_on() }); + + print_unmatched_explicit_paths( + ui, + tx.base_workspace_helper(), + &fileset_expression, + root_trees.iter(), + )?; + let summary = fix_files( - root_commits, + root_commit_ids, &matcher, args.include_unchanged_files, tx.repo_mut(), diff --git a/cli/src/commands/interdiff.rs b/cli/src/commands/interdiff.rs index 53e2e5b04b..efe6d5d278 100644 --- a/cli/src/commands/interdiff.rs +++ b/cli/src/commands/interdiff.rs @@ -21,6 +21,7 @@ use tracing::instrument; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::complete; use crate::diff_util::DiffFormatArgs; @@ -74,9 +75,8 @@ pub(crate) fn cmd_interdiff( workspace_command.resolve_single_rev(ui, args.from.as_ref().unwrap_or(&RevisionArg::AT))?; let to = workspace_command.resolve_single_rev(ui, args.to.as_ref().unwrap_or(&RevisionArg::AT))?; - let matcher = workspace_command - .parse_file_patterns(ui, &args.paths)? - .to_matcher(); + let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; + let matcher = fileset_expression.to_matcher(); let diff_renderer = workspace_command.diff_renderer_for(&args.format)?; ui.request_pager(); diff_renderer @@ -89,5 +89,13 @@ pub(crate) fn cmd_interdiff( ui.term_width(), ) .block_on()?; + + print_unmatched_explicit_paths( + ui, + &workspace_command, + &fileset_expression, + [&from.tree().unwrap(), &to.tree().unwrap()], + )?; + Ok(()) } diff --git a/cli/src/commands/log.rs b/cli/src/commands/log.rs index e24bf9ac0d..6975350b62 100644 --- a/cli/src/commands/log.rs +++ b/cli/src/commands/log.rs @@ -21,6 +21,7 @@ use jj_lib::graph::GraphEdge; use jj_lib::graph::GraphEdgeType; use jj_lib::graph::TopoGroupedGraphIterator; use jj_lib::graph::reverse_graph; +use jj_lib::merged_tree::MergedTree; use jj_lib::repo::Repo as _; use jj_lib::revset::RevsetEvaluationError; use jj_lib::revset::RevsetExpression; @@ -33,6 +34,7 @@ use crate::cli_util::CommandHelper; use crate::cli_util::LogContentFormat; use crate::cli_util::RevisionArg; use crate::cli_util::format_template; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::complete; use crate::diff_util::DiffFormatArgs; @@ -179,6 +181,7 @@ pub(crate) fn cmd_log( .labeled(["log", "commit", "node"]); } + let mut trees: Vec = vec![]; { ui.request_pager(); let mut formatter = ui.stdout_formatter(); @@ -250,6 +253,8 @@ pub(crate) fn cmd_log( if !buffer.ends_with(b"\n") { buffer.push(b'\n'); } + + trees.push(commit.tree().unwrap()); if let Some(renderer) = &diff_renderer { let mut formatter = ui.new_formatter(&mut buffer); renderer @@ -303,6 +308,8 @@ pub(crate) fn cmd_log( let commit = commit_or_error?; with_content_format .write(formatter, |formatter| template.format(&commit, formatter))?; + + trees.push(commit.tree().unwrap()); if let Some(renderer) = &diff_renderer { let width = ui.term_width(); renderer @@ -313,6 +320,8 @@ pub(crate) fn cmd_log( } } + print_unmatched_explicit_paths(ui, &workspace_command, &fileset_expression, trees.iter())?; + // Check to see if the user might have specified a path when they intended // to specify a revset. if let ([], [only_path]) = (args.revisions.as_slice(), args.paths.as_slice()) { diff --git a/cli/src/commands/resolve.rs b/cli/src/commands/resolve.rs index e88ecc5ed8..7c7845c9f2 100644 --- a/cli/src/commands/resolve.rs +++ b/cli/src/commands/resolve.rs @@ -21,6 +21,7 @@ use tracing::instrument; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; use crate::cli_util::print_conflicted_paths; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::command_error::cli_error; use crate::complete; @@ -87,15 +88,17 @@ pub(crate) fn cmd_resolve( args: &ResolveArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; - let matcher = workspace_command - .parse_file_patterns(ui, &args.paths)? - .to_matcher(); + let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; + let matcher = fileset_expression.to_matcher(); let commit = workspace_command.resolve_single_rev(ui, &args.revision)?; let tree = commit.tree()?; let conflicts = tree .conflicts() .filter(|path| matcher.matches(&path.0)) .collect_vec(); + + print_unmatched_explicit_paths(ui, &workspace_command, &fileset_expression, [&tree])?; + if conflicts.is_empty() { return Err(cli_error(if args.paths.is_empty() { "No conflicts found at this revision" diff --git a/cli/src/commands/restore.rs b/cli/src/commands/restore.rs index 2ad0684206..88b11efda9 100644 --- a/cli/src/commands/restore.rs +++ b/cli/src/commands/restore.rs @@ -23,6 +23,7 @@ use tracing::instrument; use crate::cli_util::CommandHelper; use crate::cli_util::RevisionArg; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::command_error::user_error; use crate::complete; @@ -140,9 +141,8 @@ pub(crate) fn cmd_restore( } workspace_command.check_rewritable([to_commit.id()])?; - let matcher = workspace_command - .parse_file_patterns(ui, &args.paths)? - .to_matcher(); + let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; + let matcher = fileset_expression.to_matcher(); let diff_selector = workspace_command.diff_selector(ui, args.tool.as_deref(), args.interactive)?; let to_tree = to_commit.tree()?; @@ -164,6 +164,14 @@ pub(crate) fn cmd_restore( }; let new_tree_id = diff_selector.select([&to_tree, &from_tree], &matcher, format_instructions)?; + + print_unmatched_explicit_paths( + ui, + &workspace_command, + &fileset_expression, + [&to_tree, &from_tree], + )?; + if &new_tree_id == to_commit.tree_id() { writeln!(ui.status(), "Nothing changed.")?; } else { diff --git a/cli/src/commands/split.rs b/cli/src/commands/split.rs index 8b1bf679a3..c8b520989c 100644 --- a/cli/src/commands/split.rs +++ b/cli/src/commands/split.rs @@ -38,6 +38,7 @@ use crate::cli_util::RevisionArg; use crate::cli_util::WorkspaceCommandHelper; use crate::cli_util::WorkspaceCommandTransaction; use crate::cli_util::compute_commit_location; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::command_error::user_error_with_hint; use crate::complete; @@ -158,9 +159,8 @@ impl SplitArgs { )); } workspace_command.check_rewritable([target_commit.id()])?; - let matcher = workspace_command - .parse_file_patterns(ui, &self.paths)? - .to_matcher(); + let fileset_expression = workspace_command.parse_file_patterns(ui, &self.paths)?; + let matcher = fileset_expression.to_matcher(); let diff_selector = workspace_command.diff_selector( ui, self.tool.as_deref(), @@ -181,6 +181,14 @@ impl SplitArgs { } else { Default::default() }; + + print_unmatched_explicit_paths( + ui, + workspace_command, + &fileset_expression, + [&target_commit.tree().unwrap()], + )?; + Ok(ResolvedSplitArgs { target_commit, matcher, diff --git a/cli/src/commands/squash.rs b/cli/src/commands/squash.rs index e6011a288b..330a26d58f 100644 --- a/cli/src/commands/squash.rs +++ b/cli/src/commands/squash.rs @@ -35,6 +35,7 @@ use crate::cli_util::DiffSelector; use crate::cli_util::RevisionArg; use crate::cli_util::WorkspaceCommandTransaction; use crate::cli_util::compute_commit_location; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::command_error::user_error; use crate::command_error::user_error_with_hint; @@ -302,10 +303,10 @@ pub(crate) fn cmd_squash( commit }; - let matcher = tx + let fileset_expression = tx .base_workspace_helper() - .parse_file_patterns(ui, &args.paths)? - .to_matcher(); + .parse_file_patterns(ui, &args.paths)?; + let matcher = fileset_expression.to_matcher(); let diff_selector = tx.base_workspace_helper() .diff_selector(ui, args.tool.as_deref(), args.interactive)?; @@ -377,6 +378,7 @@ pub(crate) fn cmd_squash( } let commit = commit_builder.write(tx.repo_mut())?; let num_rebased = tx.repo_mut().rebase_descendants()?; + if let Some(mut formatter) = ui.status_formatter() { if insert_destination_commit { write!(formatter, "Created new commit ")?; @@ -419,7 +421,16 @@ pub(crate) fn cmd_squash( } } } + + print_unmatched_explicit_paths( + ui, + tx.base_workspace_helper(), + &fileset_expression, + source_commits.iter().map(|commit| &commit.selected_tree), + )?; + tx.finish(ui, tx_description)?; + Ok(()) } diff --git a/cli/src/commands/status.rs b/cli/src/commands/status.rs index 041a2cac69..fcec263395 100644 --- a/cli/src/commands/status.rs +++ b/cli/src/commands/status.rs @@ -26,6 +26,7 @@ use tracing::instrument; use crate::cli_util::CommandHelper; use crate::cli_util::print_conflicted_paths; use crate::cli_util::print_snapshot_stats; +use crate::cli_util::print_unmatched_explicit_paths; use crate::command_error::CommandError; use crate::diff_util::DiffFormat; use crate::diff_util::get_copy_records; @@ -69,9 +70,8 @@ pub(crate) fn cmd_status( .get_wc_commit_id() .map(|id| repo.store().get_commit(id)) .transpose()?; - let matcher = workspace_command - .parse_file_patterns(ui, &args.paths)? - .to_matcher(); + let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?; + let matcher = fileset_expression.to_matcher(); ui.request_pager(); let mut formatter = ui.stdout_formatter(); let formatter = formatter.as_mut(); @@ -110,7 +110,7 @@ pub(crate) fn cmd_status( writeln!(formatter, "Untracked paths:")?; visit_collapsed_untracked_files( snapshot_stats.untracked_paths.keys(), - tree, + tree.clone(), |path, is_dir| { let ui_path = workspace_command.path_converter().format_file_path(path); writeln!( @@ -178,6 +178,13 @@ pub(crate) fn cmd_status( } } } + + print_unmatched_explicit_paths( + ui, + &workspace_command, + &fileset_expression, + [&parent_tree, &tree], + )?; } else { writeln!(formatter, "No working copy")?; } diff --git a/cli/tests/test_absorb_command.rs b/cli/tests/test_absorb_command.rs index a89f98f391..b854760b36 100644 --- a/cli/tests/test_absorb_command.rs +++ b/cli/tests/test_absorb_command.rs @@ -810,9 +810,10 @@ fn test_absorb_paths() { work_dir.write_file("file1", "1A\n"); work_dir.write_file("file2", "1A\n"); - let output = work_dir.run_jj(["absorb", "unknown"]); + let output = work_dir.run_jj(["absorb", "nonexistent"]); insta::assert_snapshot!(output, @r" ------- stderr ------- + Warning: No matching entries for paths: nonexistent Nothing changed. [EOF] "); diff --git a/cli/tests/test_fix_command.rs b/cli/tests/test_fix_command.rs index 5fcecfa0c5..671e70f749 100644 --- a/cli/tests/test_fix_command.rs +++ b/cli/tests/test_fix_command.rs @@ -510,6 +510,16 @@ fn test_relative_paths() { unfixed [EOF] "); + + // The output filtered to a non-existent file should display a warning. + let output = work_dir.run_jj(["fix", "nonexistent"]); + insta::assert_snapshot!(output, @r" + ------- stderr ------- + Warning: No matching entries for paths: nonexistent + Fixed 0 commits of 1 checked. + Nothing changed. + [EOF] + "); } #[test] diff --git a/cli/tests/test_interdiff_command.rs b/cli/tests/test_interdiff_command.rs index 65d2d4c536..be006d1a4f 100644 --- a/cli/tests/test_interdiff_command.rs +++ b/cli/tests/test_interdiff_command.rs @@ -134,6 +134,21 @@ fn test_interdiff_paths() { 1 1: barbaz [EOF] "); + + // The output filtered to a non-existent file should display a warning. + let output = work_dir.run_jj([ + "interdiff", + "--from", + "left", + "--to", + "right", + "nonexistent", + ]); + insta::assert_snapshot!(output, @r" + ------- stderr ------- + Warning: No matching entries for paths: nonexistent + [EOF] + "); } #[test] diff --git a/cli/tests/test_log_command.rs b/cli/tests/test_log_command.rs index b24aebba38..d195a853ae 100644 --- a/cli/tests/test_log_command.rs +++ b/cli/tests/test_log_command.rs @@ -856,6 +856,24 @@ fn test_log_filtered_by_path() { work_dir.write_file("file1", "foo\nbar\n"); work_dir.write_file("file2", "baz\n"); + // The output filtered to a non-existent file should display a warning. + let output = work_dir.run_jj(["log", "-r", "@-", "-T", "description", "nonexistent"]); + insta::assert_snapshot!(output, @r#" + ------- stderr ------- + Warning: No matching entries for paths: nonexistent + [EOF] + "#); + + // The output filtered to a non-existent file should display a warning. + // The warning should be displayed at the beginning of the output. + let output = work_dir.run_jj(["log", "-T", "description", "nonexistent"]); + insta::assert_snapshot!(output, @r#" + ------- stderr ------- + Warning: No matching entries for paths: nonexistent + Warning: The argument "nonexistent" is being interpreted as a fileset expression. To specify a revset, pass -r "nonexistent" instead. + [EOF] + "#); + let output = work_dir.run_jj(["log", "-T", "description", "file1"]); insta::assert_snapshot!(output, @r" @ second diff --git a/cli/tests/test_resolve_command.rs b/cli/tests/test_resolve_command.rs index adf1240407..0d4b20b971 100644 --- a/cli/tests/test_resolve_command.rs +++ b/cli/tests/test_resolve_command.rs @@ -1438,6 +1438,21 @@ fn test_pass_path_argument() { +resolution [EOF] "); + + // The output filtered to a non-existent file should display a warning. + let output = work_dir.run_jj([ + "resolve", + "nonexistent", + "file", + r#"--config=merge-tools.fake-editor.merge-args=["$output", "$path"]"#, + ]); + insta::assert_snapshot!(output, @r" + ------- stderr ------- + Warning: No matching entries for paths: nonexistent + Error: No conflicts found at the given path(s) + [EOF] + [exit status: 2] + "); } #[test] diff --git a/cli/tests/test_restore_command.rs b/cli/tests/test_restore_command.rs index a277992794..9179d51774 100644 --- a/cli/tests/test_restore_command.rs +++ b/cli/tests/test_restore_command.rs @@ -156,6 +156,15 @@ fn test_restore() { D file1 [EOF] "); + + // The output filtered to a non-existent file should display a warning. + let output = work_dir.run_jj(["restore", "nonexistent"]); + insta::assert_snapshot!(output, @r" + ------- stderr ------- + Warning: No matching entries for paths: nonexistent + Nothing changed. + [EOF] + "); } // Much of this test is copied from test_resolve_command diff --git a/cli/tests/test_squash_command.rs b/cli/tests/test_squash_command.rs index 17aaefa110..b15f55af8d 100644 --- a/cli/tests/test_squash_command.rs +++ b/cli/tests/test_squash_command.rs @@ -315,6 +315,7 @@ fn test_squash_partial() { let output = work_dir.run_jj(["squash", "-r", "b", "nonexistent"]); insta::assert_snapshot!(output, @r" ------- stderr ------- + Warning: No matching entries for paths: nonexistent Nothing changed. [EOF] "); @@ -325,6 +326,7 @@ fn test_squash_partial() { insta::assert_snapshot!(output, @r#" ------- stderr ------- Warning: The argument "b" is being interpreted as a fileset expression. To specify a revset, pass -r "b" instead. + Warning: No matching entries for paths: b Nothing changed. [EOF] "#); diff --git a/cli/tests/test_status_command.rs b/cli/tests/test_status_command.rs index 5244202737..6fd3ae2e14 100644 --- a/cli/tests/test_status_command.rs +++ b/cli/tests/test_status_command.rs @@ -108,6 +108,18 @@ fn test_status_filtered() { Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set) [EOF] "); + + // The output filtered to a non-existent file should display a warning. + let output = work_dir.run_jj(["status", "nonexistent"]); + insta::assert_snapshot!(output, @r" + Working copy changes: + Working copy (@) : qpvuntsm 2f169edb (no description set) + Parent commit (@-): zzzzzzzz 00000000 (empty) (no description set) + [EOF] + ------- stderr ------- + Warning: No matching entries for paths: nonexistent + [EOF] + "); } // See