diff --git a/cli/src/commands/debug.rs b/cli/src/commands/debug.rs index bd3305e92f..14d2908e2b 100644 --- a/cli/src/commands/debug.rs +++ b/cli/src/commands/debug.rs @@ -23,7 +23,7 @@ use jj_lib::local_working_copy::{LocalWorkingCopy, LockedLocalWorkingCopy}; use jj_lib::revset; use jj_lib::working_copy::WorkingCopy; -use crate::cli_util::{resolve_op_for_load, user_error, CommandError, CommandHelper}; +use crate::cli_util::{resolve_op_for_load, user_error, CommandError, CommandHelper, RevisionArg}; use crate::template_parser; use crate::ui::Ui; @@ -40,6 +40,7 @@ pub enum DebugCommands { ReIndex(DebugReIndexArgs), #[command(visible_alias = "view")] Operation(DebugOperationArgs), + Tree(DebugTreeArgs), #[command(subcommand)] Watchman(DebugWatchmanSubcommand), } @@ -89,6 +90,15 @@ pub enum DebugOperationDisplay { All, } +/// List the recursive entries of a tree. +#[derive(clap::Args, Clone, Debug)] +pub struct DebugTreeArgs { + #[arg(long, short = 'r', default_value = "@")] + revision: RevisionArg, + paths: Vec, + // TODO: Add an option to include trees that are ancestors of the matched paths +} + #[derive(Subcommand, Clone, Debug)] pub enum DebugWatchmanSubcommand { QueryClock, @@ -198,6 +208,7 @@ pub fn cmd_debug( writeln!(ui.stdout(), "{:#?}", op.view()?.store_view())?; } } + DebugCommands::Tree(sub_args) => cmd_debug_tree(ui, command, sub_args)?, DebugCommands::Watchman(watchman_subcommand) => { cmd_debug_watchman(ui, command, watchman_subcommand)?; } @@ -242,6 +253,23 @@ fn cmd_debug_revset( Ok(()) } +fn cmd_debug_tree( + ui: &mut Ui, + command: &CommandHelper, + args: &DebugTreeArgs, +) -> Result<(), CommandError> { + let workspace_command = command.workspace_helper(ui)?; + let commit = workspace_command.resolve_single_rev(&args.revision, ui)?; + let tree = commit.tree()?; + let matcher = workspace_command.matcher_from_values(&args.paths)?; + for (path, value) in tree.entries_matching(matcher.as_ref()) { + let ui_path = workspace_command.format_file_path(&path); + writeln!(ui.stdout(), "{ui_path}: {value:?}")?; + } + + Ok(()) +} + #[cfg(feature = "watchman")] fn cmd_debug_watchman( ui: &mut Ui, diff --git a/cli/tests/test_chmod_command.rs b/cli/tests/test_chmod_command.rs index bb046c6569..8ba47e5a84 100644 --- a/cli/tests/test_chmod_command.rs +++ b/cli/tests/test_chmod_command.rs @@ -65,6 +65,11 @@ fn test_chmod_regular_conflict() { ◉ base ◉ "###); + let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "tree"]); + insta::assert_snapshot!(stdout, + @r###" + file: Conflicted { removes: [Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })], adds: [Some(File { id: FileId("587be6b4c3f93f93c489c0111bba5596147a26cb"), executable: true }), Some(File { id: FileId("8ba3a16384aacc37d01564b28401755ce8053f51"), executable: false })] } + "###); let stdout = test_env.jj_cmd_success(&repo_path, &["cat", "file"]); insta::assert_snapshot!(stdout, @r###" @@ -76,6 +81,11 @@ fn test_chmod_regular_conflict() { // Test chmodding a conflict test_env.jj_cmd_ok(&repo_path, &["chmod", "x", "file"]); + let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "tree"]); + insta::assert_snapshot!(stdout, + @r###" + file: Conflicted { removes: [Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: true })], adds: [Some(File { id: FileId("587be6b4c3f93f93c489c0111bba5596147a26cb"), executable: true }), Some(File { id: FileId("8ba3a16384aacc37d01564b28401755ce8053f51"), executable: true })] } + "###); let stdout = test_env.jj_cmd_success(&repo_path, &["cat", "file"]); insta::assert_snapshot!(stdout, @r###" @@ -85,6 +95,11 @@ fn test_chmod_regular_conflict() { Adding executable file with id 8ba3a16384aacc37d01564b28401755ce8053f51 "###); test_env.jj_cmd_ok(&repo_path, &["chmod", "n", "file"]); + let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "tree"]); + insta::assert_snapshot!(stdout, + @r###" + file: Conflicted { removes: [Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })], adds: [Some(File { id: FileId("587be6b4c3f93f93c489c0111bba5596147a26cb"), executable: false }), Some(File { id: FileId("8ba3a16384aacc37d01564b28401755ce8053f51"), executable: false })] } + "###); let stdout = test_env.jj_cmd_success(&repo_path, &["cat", "file"]); insta::assert_snapshot!(stdout, @r###" @@ -103,6 +118,11 @@ fn test_chmod_regular_conflict() { insta::assert_snapshot!(stderr, @r###" Error: No such path at 'nonexistent'. "###); + let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "tree"]); + insta::assert_snapshot!(stdout, + @r###" + file: Conflicted { removes: [Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })], adds: [Some(File { id: FileId("587be6b4c3f93f93c489c0111bba5596147a26cb"), executable: false }), Some(File { id: FileId("8ba3a16384aacc37d01564b28401755ce8053f51"), executable: false })] } + "###); let stdout = test_env.jj_cmd_success(&repo_path, &["cat", "file"]); insta::assert_snapshot!(stdout, @r###" @@ -161,6 +181,11 @@ fn test_chmod_file_dir_deletion_conflicts() { "###); // The file-dir conflict cannot be chmod-ed + let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "tree", "-r=file_dir"]); + insta::assert_snapshot!(stdout, + @r###" + file: Conflicted { removes: [Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })], adds: [Some(File { id: FileId("78981922613b2afb6025042ff6bd878ac1994e85"), executable: false }), Some(Tree(TreeId("133bb38fc4e4bf6b551f1f04db7e48f04cac2877")))] } + "###); let stdout = test_env.jj_cmd_success(&repo_path, &["cat", "-r=file_dir", "file"]); insta::assert_snapshot!(stdout, @r###" @@ -175,6 +200,11 @@ fn test_chmod_file_dir_deletion_conflicts() { "###); // The file_deletion conflict can be chmod-ed + let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "tree", "-r=file_deletion"]); + insta::assert_snapshot!(stdout, + @r###" + file: Conflicted { removes: [Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })], adds: [Some(File { id: FileId("78981922613b2afb6025042ff6bd878ac1994e85"), executable: false }), None] } + "###); let stdout = test_env.jj_cmd_success(&repo_path, &["cat", "-r=file_deletion", "file"]); insta::assert_snapshot!(stdout, @r###" @@ -189,11 +219,16 @@ fn test_chmod_file_dir_deletion_conflicts() { test_env.jj_cmd_ok(&repo_path, &["chmod", "x", "file", "-r=file_deletion"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" - Working copy now at: kmkuslsw 8b70a1d2 file_deletion | (conflict) file_deletion + Working copy now at: kmkuslsw 4cc432b5 file_deletion | (conflict) file_deletion Parent commit : zsuskuln c51c9c55 file | file Parent commit : royxmykx 6b18b3c1 deletion | deletion Added 0 files, modified 1 files, removed 0 files "###); + let stdout = test_env.jj_cmd_success(&repo_path, &["debug", "tree", "-r=file_deletion"]); + insta::assert_snapshot!(stdout, + @r###" + file: Conflicted { removes: [Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: true })], adds: [Some(File { id: FileId("78981922613b2afb6025042ff6bd878ac1994e85"), executable: true }), None] } + "###); let stdout = test_env.jj_cmd_success(&repo_path, &["cat", "-r=file_deletion", "file"]); insta::assert_snapshot!(stdout, @r###"