debug-tree: allow looking up tree by path and id, not just revision

Sometimes only a tree has been created, so we shouldn't require a
commit for `jj debug tree`.
This commit is contained in:
Martin von Zweigbergk 2024-03-13 18:49:40 -07:00 committed by Martin von Zweigbergk
parent 3bbc3e5715
commit 3bb9fd412a
2 changed files with 107 additions and 4 deletions

View file

@ -17,9 +17,13 @@ use std::fmt::Debug;
use std::io::Write as _; use std::io::Write as _;
use clap::Subcommand; use clap::Subcommand;
use jj_lib::backend::TreeId;
use jj_lib::default_index::{AsCompositeIndex as _, DefaultIndexStore, DefaultReadonlyIndex}; use jj_lib::default_index::{AsCompositeIndex as _, DefaultIndexStore, DefaultReadonlyIndex};
use jj_lib::local_working_copy::LocalWorkingCopy; use jj_lib::local_working_copy::LocalWorkingCopy;
use jj_lib::merged_tree::MergedTree;
use jj_lib::object_id::ObjectId; use jj_lib::object_id::ObjectId;
use jj_lib::repo::Repo;
use jj_lib::repo_path::RepoPathBuf;
use jj_lib::working_copy::WorkingCopy; use jj_lib::working_copy::WorkingCopy;
use jj_lib::{op_walk, revset}; use jj_lib::{op_walk, revset};
@ -94,8 +98,12 @@ pub enum DebugOperationDisplay {
/// List the recursive entries of a tree. /// List the recursive entries of a tree.
#[derive(clap::Args, Clone, Debug)] #[derive(clap::Args, Clone, Debug)]
pub struct DebugTreeArgs { pub struct DebugTreeArgs {
#[arg(long, short = 'r', default_value = "@")] #[arg(long, short = 'r')]
revision: RevisionArg, revision: Option<RevisionArg>,
#[arg(long, conflicts_with = "revision")]
id: Option<String>,
#[arg(long, requires = "id")]
dir: Option<String>,
paths: Vec<String>, paths: Vec<String>,
// TODO: Add an option to include trees that are ancestors of the matched paths // TODO: Add an option to include trees that are ancestors of the matched paths
} }
@ -293,8 +301,22 @@ fn cmd_debug_tree(
args: &DebugTreeArgs, args: &DebugTreeArgs,
) -> Result<(), CommandError> { ) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?; let workspace_command = command.workspace_helper(ui)?;
let commit = workspace_command.resolve_single_rev(&args.revision)?; let tree = if let Some(tree_id_hex) = &args.id {
let tree = commit.tree()?; let tree_id =
TreeId::try_from_hex(tree_id_hex).map_err(|_| user_error("Invalid tree id"))?;
let dir = if let Some(dir_str) = &args.dir {
workspace_command.parse_file_path(dir_str)?
} else {
RepoPathBuf::root()
};
let store = workspace_command.repo().store();
let tree = store.get_tree(&dir, &tree_id)?;
MergedTree::resolved(tree)
} else {
let commit =
workspace_command.resolve_single_rev(args.revision.as_deref().unwrap_or("@"))?;
commit.tree()?
};
let matcher = workspace_command.matcher_from_values(&args.paths)?; let matcher = workspace_command.matcher_from_values(&args.paths)?;
for (path, value) in tree.entries_matching(matcher.as_ref()) { for (path, value) in tree.entries_matching(matcher.as_ref()) {
let ui_path = workspace_command.format_file_path(&path); let ui_path = workspace_command.format_file_path(&path);

View file

@ -117,6 +117,87 @@ fn test_debug_reindex() {
); );
} }
#[test]
fn test_debug_tree() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
let workspace_path = test_env.env_root().join("repo");
let subdir = workspace_path.join("dir").join("subdir");
std::fs::create_dir_all(&subdir).unwrap();
std::fs::write(subdir.join("file1"), "contents 1").unwrap();
test_env.jj_cmd_ok(&workspace_path, &["new"]);
std::fs::write(subdir.join("file2"), "contents 2").unwrap();
// Defaults to showing the tree at the current commit
let stdout = test_env.jj_cmd_success(&workspace_path, &["debug", "tree"]);
assert_snapshot!(stdout.replace('\\',"/"), @r###"
dir/subdir/file1: Resolved(Some(File { id: FileId("498e9b01d79cb8d31cdf0df1a663cc1fcefd9de3"), executable: false }))
dir/subdir/file2: Resolved(Some(File { id: FileId("b2496eaffe394cd50a9db4de5787f45f09fd9722"), executable: false }))
"###
);
// Can show the tree at another commit
let stdout = test_env.jj_cmd_success(&workspace_path, &["debug", "tree", "-r@-"]);
assert_snapshot!(stdout.replace('\\',"/"), @r###"
dir/subdir/file1: Resolved(Some(File { id: FileId("498e9b01d79cb8d31cdf0df1a663cc1fcefd9de3"), executable: false }))
"###
);
// Can filter by paths
let stdout = test_env.jj_cmd_success(&workspace_path, &["debug", "tree", "dir/subdir/file2"]);
assert_snapshot!(stdout.replace('\\',"/"), @r###"
dir/subdir/file2: Resolved(Some(File { id: FileId("b2496eaffe394cd50a9db4de5787f45f09fd9722"), executable: false }))
"###
);
// Can a show the root tree by id
let stdout = test_env.jj_cmd_success(
&workspace_path,
&[
"debug",
"tree",
"--id=0958358e3f80e794f032b25ed2be96cf5825da6c",
],
);
assert_snapshot!(stdout.replace('\\',"/"), @r###"
dir/subdir/file1: Resolved(Some(File { id: FileId("498e9b01d79cb8d31cdf0df1a663cc1fcefd9de3"), executable: false }))
dir/subdir/file2: Resolved(Some(File { id: FileId("b2496eaffe394cd50a9db4de5787f45f09fd9722"), executable: false }))
"###
);
// Can a show non-root tree by id
let stdout = test_env.jj_cmd_success(
&workspace_path,
&[
"debug",
"tree",
"--dir=dir",
"--id=6ac232efa713535ae518a1a898b77e76c0478184",
],
);
assert_snapshot!(stdout.replace('\\',"/"), @r###"
dir/subdir/file1: Resolved(Some(File { id: FileId("498e9b01d79cb8d31cdf0df1a663cc1fcefd9de3"), executable: false }))
dir/subdir/file2: Resolved(Some(File { id: FileId("b2496eaffe394cd50a9db4de5787f45f09fd9722"), executable: false }))
"###
);
// Can filter by paths when showing non-root tree (matcher applies from root)
let stdout = test_env.jj_cmd_success(
&workspace_path,
&[
"debug",
"tree",
"--dir=dir",
"--id=6ac232efa713535ae518a1a898b77e76c0478184",
"dir/subdir/file2",
],
);
assert_snapshot!(stdout.replace('\\',"/"), @r###"
dir/subdir/file2: Resolved(Some(File { id: FileId("b2496eaffe394cd50a9db4de5787f45f09fd9722"), executable: false }))
"###
);
}
#[test] #[test]
fn test_debug_operation_id() { fn test_debug_operation_id() {
let test_env = TestEnvironment::default(); let test_env = TestEnvironment::default();