From bbe71626ee036ac9e5ab14f7565d13b2ed5917b2 Mon Sep 17 00:00:00 2001 From: Matt Kulukundis Date: Tue, 18 Jun 2024 17:02:43 -0400 Subject: [PATCH] Split `jj debug` command into multiple files --- cli/src/commands/debug.rs | 442 ------------------------- cli/src/commands/debug/fileset.rs | 48 +++ cli/src/commands/debug/index.rs | 67 ++++ cli/src/commands/debug/mod.rs | 83 +++++ cli/src/commands/debug/operation.rs | 67 ++++ cli/src/commands/debug/reindex.rs | 57 ++++ cli/src/commands/debug/revset.rs | 71 ++++ cli/src/commands/debug/template.rs | 37 +++ cli/src/commands/debug/tree.rs | 71 ++++ cli/src/commands/debug/watchman.rs | 113 +++++++ cli/src/commands/debug/working_copy.rs | 49 +++ 11 files changed, 663 insertions(+), 442 deletions(-) delete mode 100644 cli/src/commands/debug.rs create mode 100644 cli/src/commands/debug/fileset.rs create mode 100644 cli/src/commands/debug/index.rs create mode 100644 cli/src/commands/debug/mod.rs create mode 100644 cli/src/commands/debug/operation.rs create mode 100644 cli/src/commands/debug/reindex.rs create mode 100644 cli/src/commands/debug/revset.rs create mode 100644 cli/src/commands/debug/template.rs create mode 100644 cli/src/commands/debug/tree.rs create mode 100644 cli/src/commands/debug/watchman.rs create mode 100644 cli/src/commands/debug/working_copy.rs diff --git a/cli/src/commands/debug.rs b/cli/src/commands/debug.rs deleted file mode 100644 index f2489820a..000000000 --- a/cli/src/commands/debug.rs +++ /dev/null @@ -1,442 +0,0 @@ -// Copyright 2023 The Jujutsu Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::any::Any; -use std::fmt::Debug; -use std::io::Write as _; - -use clap::Subcommand; -use jj_lib::backend::TreeId; -use jj_lib::default_index::{AsCompositeIndex as _, DefaultIndexStore, DefaultReadonlyIndex}; -use jj_lib::fsmonitor::{FsmonitorSettings, WatchmanConfig}; -use jj_lib::local_working_copy::LocalWorkingCopy; -use jj_lib::merged_tree::MergedTree; -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::{fileset, op_walk, revset}; - -use crate::cli_util::{CommandHelper, RevisionArg}; -use crate::command_error::{internal_error, user_error, CommandError}; -use crate::ui::Ui; -use crate::{revset_util, template_parser}; - -/// Low-level commands not intended for users -#[derive(Subcommand, Clone, Debug)] -#[command(hide = true)] -pub enum DebugCommand { - Fileset(DebugFilesetArgs), - Revset(DebugRevsetArgs), - #[command(name = "workingcopy")] - WorkingCopy(DebugWorkingCopyArgs), - Template(DebugTemplateArgs), - Index(DebugIndexArgs), - #[command(name = "reindex")] - ReIndex(DebugReIndexArgs), - #[command(visible_alias = "view")] - Operation(DebugOperationArgs), - Tree(DebugTreeArgs), - #[command(subcommand)] - Watchman(DebugWatchmanSubcommand), -} - -/// Parse fileset expression -#[derive(clap::Args, Clone, Debug)] -pub struct DebugFilesetArgs { - #[arg(value_hint = clap::ValueHint::AnyPath)] - path: String, -} - -/// Evaluate revset to full commit IDs -#[derive(clap::Args, Clone, Debug)] -pub struct DebugRevsetArgs { - revision: String, -} - -/// Show information about the working copy state -#[derive(clap::Args, Clone, Debug)] -pub struct DebugWorkingCopyArgs {} - -/// Parse a template -#[derive(clap::Args, Clone, Debug)] -pub struct DebugTemplateArgs { - template: String, -} - -/// Show commit index stats -#[derive(clap::Args, Clone, Debug)] -pub struct DebugIndexArgs {} - -/// Rebuild commit index -#[derive(clap::Args, Clone, Debug)] -pub struct DebugReIndexArgs {} - -/// Show information about an operation and its view -#[derive(clap::Args, Clone, Debug)] -pub struct DebugOperationArgs { - #[arg(default_value = "@")] - operation: String, - #[arg(long, value_enum, default_value = "all")] - display: DebugOperationDisplay, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] -pub enum DebugOperationDisplay { - /// Show only the operation details. - Operation, - /// Show the operation id only - Id, - /// Show only the view details - View, - /// Show both the view and the operation - All, -} - -/// List the recursive entries of a tree. -#[derive(clap::Args, Clone, Debug)] -pub struct DebugTreeArgs { - #[arg(long, short = 'r')] - revision: Option, - #[arg(long, conflicts_with = "revision")] - id: Option, - #[arg(long, requires = "id")] - dir: Option, - paths: Vec, - // TODO: Add an option to include trees that are ancestors of the matched paths -} - -#[derive(Subcommand, Clone, Debug)] -pub enum DebugWatchmanSubcommand { - /// Check whether `watchman` is enabled and whether it's correctly installed - Status, - QueryClock, - QueryChangedFiles, - ResetClock, -} - -pub fn cmd_debug( - ui: &mut Ui, - command: &CommandHelper, - subcommand: &DebugCommand, -) -> Result<(), CommandError> { - match subcommand { - DebugCommand::Fileset(args) => cmd_debug_fileset(ui, command, args), - DebugCommand::Revset(args) => cmd_debug_revset(ui, command, args), - DebugCommand::WorkingCopy(args) => cmd_debug_working_copy(ui, command, args), - DebugCommand::Template(args) => cmd_debug_template(ui, command, args), - DebugCommand::Index(args) => cmd_debug_index(ui, command, args), - DebugCommand::ReIndex(args) => cmd_debug_reindex(ui, command, args), - DebugCommand::Operation(args) => cmd_debug_operation(ui, command, args), - DebugCommand::Tree(args) => cmd_debug_tree(ui, command, args), - DebugCommand::Watchman(args) => cmd_debug_watchman(ui, command, args), - } -} - -fn cmd_debug_fileset( - ui: &mut Ui, - command: &CommandHelper, - args: &DebugFilesetArgs, -) -> Result<(), CommandError> { - let workspace_command = command.workspace_helper(ui)?; - let path_converter = workspace_command.path_converter(); - - let expression = fileset::parse_maybe_bare(&args.path, path_converter)?; - writeln!(ui.stdout(), "-- Parsed:")?; - writeln!(ui.stdout(), "{expression:#?}")?; - writeln!(ui.stdout())?; - - let matcher = expression.to_matcher(); - writeln!(ui.stdout(), "-- Matcher:")?; - writeln!(ui.stdout(), "{matcher:#?}")?; - Ok(()) -} - -fn cmd_debug_revset( - ui: &mut Ui, - command: &CommandHelper, - args: &DebugRevsetArgs, -) -> Result<(), CommandError> { - let workspace_command = command.workspace_helper(ui)?; - let workspace_ctx = workspace_command.revset_parse_context(); - let repo = workspace_command.repo().as_ref(); - - let expression = revset::parse(&args.revision, &workspace_ctx)?; - writeln!(ui.stdout(), "-- Parsed:")?; - writeln!(ui.stdout(), "{expression:#?}")?; - writeln!(ui.stdout())?; - - let expression = revset::optimize(expression); - writeln!(ui.stdout(), "-- Optimized:")?; - writeln!(ui.stdout(), "{expression:#?}")?; - writeln!(ui.stdout())?; - - let symbol_resolver = revset_util::default_symbol_resolver( - repo, - command.revset_extensions().symbol_resolvers(), - workspace_command.id_prefix_context()?, - ); - let expression = expression.resolve_user_expression(repo, &symbol_resolver)?; - writeln!(ui.stdout(), "-- Resolved:")?; - writeln!(ui.stdout(), "{expression:#?}")?; - writeln!(ui.stdout())?; - - let revset = expression.evaluate(repo)?; - writeln!(ui.stdout(), "-- Evaluated:")?; - writeln!(ui.stdout(), "{revset:#?}")?; - writeln!(ui.stdout())?; - - writeln!(ui.stdout(), "-- Commit IDs:")?; - for commit_id in revset.iter() { - writeln!(ui.stdout(), "{}", commit_id.hex())?; - } - Ok(()) -} - -fn cmd_debug_working_copy( - ui: &mut Ui, - command: &CommandHelper, - _args: &DebugWorkingCopyArgs, -) -> Result<(), CommandError> { - let workspace_command = command.workspace_helper(ui)?; - let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; - writeln!(ui.stdout(), "Current operation: {:?}", wc.operation_id())?; - writeln!(ui.stdout(), "Current tree: {:?}", wc.tree_id()?)?; - for (file, state) in wc.file_states()? { - writeln!( - ui.stdout(), - "{:?} {:13?} {:10?} {:?}", - state.file_type, - state.size, - state.mtime.0, - file - )?; - } - Ok(()) -} - -fn cmd_debug_template( - ui: &mut Ui, - _command: &CommandHelper, - args: &DebugTemplateArgs, -) -> Result<(), CommandError> { - let node = template_parser::parse_template(&args.template)?; - writeln!(ui.stdout(), "{node:#?}")?; - Ok(()) -} - -fn cmd_debug_index( - ui: &mut Ui, - command: &CommandHelper, - _args: &DebugIndexArgs, -) -> Result<(), CommandError> { - // Resolve the operation without loading the repo, so this command won't - // merge concurrent operations and update the index. - let workspace = command.load_workspace()?; - let repo_loader = workspace.repo_loader(); - let op = op_walk::resolve_op_for_load(repo_loader, &command.global_args().at_operation)?; - let index_store = repo_loader.index_store(); - let index = index_store - .get_index_at_op(&op, repo_loader.store()) - .map_err(internal_error)?; - if let Some(default_index) = index.as_any().downcast_ref::() { - let stats = default_index.as_composite().stats(); - writeln!(ui.stdout(), "Number of commits: {}", stats.num_commits)?; - writeln!(ui.stdout(), "Number of merges: {}", stats.num_merges)?; - writeln!( - ui.stdout(), - "Max generation number: {}", - stats.max_generation_number - )?; - writeln!(ui.stdout(), "Number of heads: {}", stats.num_heads)?; - writeln!(ui.stdout(), "Number of changes: {}", stats.num_changes)?; - writeln!(ui.stdout(), "Stats per level:")?; - for (i, level) in stats.levels.iter().enumerate() { - writeln!(ui.stdout(), " Level {i}:")?; - writeln!(ui.stdout(), " Number of commits: {}", level.num_commits)?; - writeln!(ui.stdout(), " Name: {}", level.name.as_ref().unwrap())?; - } - } else { - return Err(user_error(format!( - "Cannot get stats for indexes of type '{}'", - index_store.name() - ))); - } - Ok(()) -} - -fn cmd_debug_reindex( - ui: &mut Ui, - command: &CommandHelper, - _args: &DebugReIndexArgs, -) -> Result<(), CommandError> { - // Resolve the operation without loading the repo. The index might have to - // be rebuilt while loading the repo. - let workspace = command.load_workspace()?; - let repo_loader = workspace.repo_loader(); - let op = op_walk::resolve_op_for_load(repo_loader, &command.global_args().at_operation)?; - let index_store = repo_loader.index_store(); - if let Some(default_index_store) = index_store.as_any().downcast_ref::() { - default_index_store.reinit().map_err(internal_error)?; - let default_index = default_index_store - .build_index_at_operation(&op, repo_loader.store()) - .map_err(internal_error)?; - writeln!( - ui.status(), - "Finished indexing {:?} commits.", - default_index.as_composite().stats().num_commits - )?; - } else { - return Err(user_error(format!( - "Cannot reindex indexes of type '{}'", - index_store.name() - ))); - } - Ok(()) -} - -fn cmd_debug_operation( - ui: &mut Ui, - command: &CommandHelper, - args: &DebugOperationArgs, -) -> Result<(), CommandError> { - // Resolve the operation without loading the repo, so this command can be used - // even if e.g. the view object is broken. - let workspace = command.load_workspace()?; - let repo_loader = workspace.repo_loader(); - let op = op_walk::resolve_op_for_load(repo_loader, &args.operation)?; - if args.display == DebugOperationDisplay::Id { - writeln!(ui.stdout(), "{}", op.id().hex())?; - return Ok(()); - } - if args.display != DebugOperationDisplay::View { - writeln!(ui.stdout(), "{:#?}", op.store_operation())?; - } - if args.display != DebugOperationDisplay::Operation { - writeln!(ui.stdout(), "{:#?}", op.view()?.store_view())?; - } - Ok(()) -} - -fn cmd_debug_tree( - ui: &mut Ui, - command: &CommandHelper, - args: &DebugTreeArgs, -) -> Result<(), CommandError> { - let workspace_command = command.workspace_helper(ui)?; - let tree = if let Some(tree_id_hex) = &args.id { - 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_ref().unwrap_or(&RevisionArg::AT))?; - commit.tree()? - }; - let matcher = workspace_command - .parse_file_patterns(&args.paths)? - .to_matcher(); - 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, - command: &CommandHelper, - subcommand: &DebugWatchmanSubcommand, -) -> Result<(), CommandError> { - use jj_lib::local_working_copy::LockedLocalWorkingCopy; - - let mut workspace_command = command.workspace_helper(ui)?; - let repo = workspace_command.repo().clone(); - match subcommand { - DebugWatchmanSubcommand::Status => { - // TODO(ilyagr): It would be nice to add colors here - match command.settings().fsmonitor_settings()? { - FsmonitorSettings::Watchman { .. } => { - writeln!(ui.stdout(), "Watchman is enabled via `core.fsmonitor`.")? - } - FsmonitorSettings::None => writeln!( - ui.stdout(), - "Watchman is disabled. Set `core.fsmonitor=\"watchman\"` to \ - enable.\nAttempting to contact the `watchman` CLI regardless..." - )?, - other_fsmonitor => { - return Err(user_error(format!( - "This command does not support the currently enabled filesystem monitor: \ - {other_fsmonitor:?}." - ))) - } - }; - let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; - let _ = wc.query_watchman(&WatchmanConfig::default())?; - writeln!( - ui.stdout(), - "The watchman server seems to be installed and working correctly." - )?; - } - DebugWatchmanSubcommand::QueryClock => { - let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; - let (clock, _changed_files) = wc.query_watchman(&WatchmanConfig::default())?; - writeln!(ui.stdout(), "Clock: {clock:?}")?; - } - DebugWatchmanSubcommand::QueryChangedFiles => { - let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; - let (_clock, changed_files) = wc.query_watchman(&WatchmanConfig::default())?; - writeln!(ui.stdout(), "Changed files: {changed_files:?}")?; - } - DebugWatchmanSubcommand::ResetClock => { - let (mut locked_ws, _commit) = workspace_command.start_working_copy_mutation()?; - let Some(locked_local_wc): Option<&mut LockedLocalWorkingCopy> = - locked_ws.locked_wc().as_any_mut().downcast_mut() - else { - return Err(user_error( - "This command requires a standard local-disk working copy", - )); - }; - locked_local_wc.reset_watchman()?; - locked_ws.finish(repo.op_id().clone())?; - writeln!(ui.status(), "Reset Watchman clock")?; - } - } - Ok(()) -} - -#[cfg(not(feature = "watchman"))] -fn cmd_debug_watchman( - _ui: &mut Ui, - _command: &CommandHelper, - _subcommand: &DebugWatchmanSubcommand, -) -> Result<(), CommandError> { - Err(user_error( - "Cannot query Watchman because jj was not compiled with the `watchman` feature", - )) -} - -fn check_local_disk_wc(x: &dyn Any) -> Result<&LocalWorkingCopy, CommandError> { - x.downcast_ref() - .ok_or_else(|| user_error("This command requires a standard local-disk working copy")) -} diff --git a/cli/src/commands/debug/fileset.rs b/cli/src/commands/debug/fileset.rs new file mode 100644 index 000000000..5e2f0b568 --- /dev/null +++ b/cli/src/commands/debug/fileset.rs @@ -0,0 +1,48 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use jj_lib::fileset; + +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; +use crate::ui::Ui; + +/// Parse fileset expression +#[derive(clap::Args, Clone, Debug)] +pub struct FilesetArgs { + #[arg(value_hint = clap::ValueHint::AnyPath)] + path: String, +} + +pub fn cmd_debug_fileset( + ui: &mut Ui, + command: &CommandHelper, + args: &FilesetArgs, +) -> Result<(), CommandError> { + let workspace_command = command.workspace_helper(ui)?; + let path_converter = workspace_command.path_converter(); + + let expression = fileset::parse_maybe_bare(&args.path, path_converter)?; + writeln!(ui.stdout(), "-- Parsed:")?; + writeln!(ui.stdout(), "{expression:#?}")?; + writeln!(ui.stdout())?; + + let matcher = expression.to_matcher(); + writeln!(ui.stdout(), "-- Matcher:")?; + writeln!(ui.stdout(), "{matcher:#?}")?; + Ok(()) +} diff --git a/cli/src/commands/debug/index.rs b/cli/src/commands/debug/index.rs new file mode 100644 index 000000000..1ef968a17 --- /dev/null +++ b/cli/src/commands/debug/index.rs @@ -0,0 +1,67 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use jj_lib::default_index::{AsCompositeIndex as _, DefaultReadonlyIndex}; +use jj_lib::op_walk; + +use crate::cli_util::CommandHelper; +use crate::command_error::{internal_error, user_error, CommandError}; +use crate::ui::Ui; + +/// Show commit index stats +#[derive(clap::Args, Clone, Debug)] +pub struct IndexArgs {} + +pub fn cmd_debug_index( + ui: &mut Ui, + command: &CommandHelper, + _args: &IndexArgs, +) -> Result<(), CommandError> { + // Resolve the operation without loading the repo, so this command won't + // merge concurrent operations and update the index. + let workspace = command.load_workspace()?; + let repo_loader = workspace.repo_loader(); + let op = op_walk::resolve_op_for_load(repo_loader, &command.global_args().at_operation)?; + let index_store = repo_loader.index_store(); + let index = index_store + .get_index_at_op(&op, repo_loader.store()) + .map_err(internal_error)?; + if let Some(default_index) = index.as_any().downcast_ref::() { + let stats = default_index.as_composite().stats(); + writeln!(ui.stdout(), "Number of commits: {}", stats.num_commits)?; + writeln!(ui.stdout(), "Number of merges: {}", stats.num_merges)?; + writeln!( + ui.stdout(), + "Max generation number: {}", + stats.max_generation_number + )?; + writeln!(ui.stdout(), "Number of heads: {}", stats.num_heads)?; + writeln!(ui.stdout(), "Number of changes: {}", stats.num_changes)?; + writeln!(ui.stdout(), "Stats per level:")?; + for (i, level) in stats.levels.iter().enumerate() { + writeln!(ui.stdout(), " Level {i}:")?; + writeln!(ui.stdout(), " Number of commits: {}", level.num_commits)?; + writeln!(ui.stdout(), " Name: {}", level.name.as_ref().unwrap())?; + } + } else { + return Err(user_error(format!( + "Cannot get stats for indexes of type '{}'", + index_store.name() + ))); + } + Ok(()) +} diff --git a/cli/src/commands/debug/mod.rs b/cli/src/commands/debug/mod.rs new file mode 100644 index 000000000..5285fe221 --- /dev/null +++ b/cli/src/commands/debug/mod.rs @@ -0,0 +1,83 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod fileset; +pub mod index; +pub mod operation; +pub mod reindex; +pub mod revset; +pub mod template; +pub mod tree; +pub mod watchman; +pub mod working_copy; + +use std::any::Any; +use std::fmt::Debug; + +use clap::Subcommand; +use jj_lib::local_working_copy::LocalWorkingCopy; + +use self::fileset::{cmd_debug_fileset, FilesetArgs}; +use self::index::{cmd_debug_index, IndexArgs}; +use self::operation::{cmd_debug_operation, OperationArgs}; +use self::reindex::{cmd_debug_reindex, ReindexArgs}; +use self::revset::{cmd_debug_revset, RevsetArgs}; +use self::template::{cmd_debug_template, TemplateArgs}; +use self::tree::{cmd_debug_tree, TreeArgs}; +use self::watchman::{cmd_debug_watchman, WatchmanCommand}; +use self::working_copy::{cmd_debug_working_copy, WorkingCopyArgs}; +use crate::cli_util::CommandHelper; +use crate::command_error::{user_error, CommandError}; +use crate::ui::Ui; + +/// Low-level commands not intended for users +#[derive(Subcommand, Clone, Debug)] +#[command(hide = true)] +pub enum DebugCommand { + Fileset(FilesetArgs), + Revset(RevsetArgs), + #[command(name = "workingcopy")] + WorkingCopy(WorkingCopyArgs), + Template(TemplateArgs), + Index(IndexArgs), + Reindex(ReindexArgs), + #[command(visible_alias = "view")] + Operation(OperationArgs), + Tree(TreeArgs), + #[command(subcommand)] + Watchman(WatchmanCommand), +} + +pub fn cmd_debug( + ui: &mut Ui, + command: &CommandHelper, + subcommand: &DebugCommand, +) -> Result<(), CommandError> { + match subcommand { + DebugCommand::Fileset(args) => cmd_debug_fileset(ui, command, args), + DebugCommand::Revset(args) => cmd_debug_revset(ui, command, args), + DebugCommand::WorkingCopy(args) => cmd_debug_working_copy(ui, command, args), + DebugCommand::Template(args) => cmd_debug_template(ui, command, args), + DebugCommand::Index(args) => cmd_debug_index(ui, command, args), + DebugCommand::Reindex(args) => cmd_debug_reindex(ui, command, args), + DebugCommand::Operation(args) => cmd_debug_operation(ui, command, args), + DebugCommand::Tree(args) => cmd_debug_tree(ui, command, args), + DebugCommand::Watchman(args) => cmd_debug_watchman(ui, command, args), + } +} + +fn check_local_disk_wc(x: &dyn Any) -> Result<&LocalWorkingCopy, CommandError> { + x.downcast_ref() + .ok_or_else(|| user_error("This command requires a standard local-disk working copy")) +} diff --git a/cli/src/commands/debug/operation.rs b/cli/src/commands/debug/operation.rs new file mode 100644 index 000000000..3cd0c9929 --- /dev/null +++ b/cli/src/commands/debug/operation.rs @@ -0,0 +1,67 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use jj_lib::object_id::ObjectId; +use jj_lib::op_walk; + +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; +use crate::ui::Ui; + +/// Show information about an operation and its view +#[derive(clap::Args, Clone, Debug)] +pub struct OperationArgs { + #[arg(default_value = "@")] + operation: String, + #[arg(long, value_enum, default_value = "all")] + display: OperationDisplay, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] +pub enum OperationDisplay { + /// Show only the operation details. + Operation, + /// Show the operation id only + Id, + /// Show only the view details + View, + /// Show both the view and the operation + All, +} + +pub fn cmd_debug_operation( + ui: &mut Ui, + command: &CommandHelper, + args: &OperationArgs, +) -> Result<(), CommandError> { + // Resolve the operation without loading the repo, so this command can be used + // even if e.g. the view object is broken. + let workspace = command.load_workspace()?; + let repo_loader = workspace.repo_loader(); + let op = op_walk::resolve_op_for_load(repo_loader, &args.operation)?; + if args.display == OperationDisplay::Id { + writeln!(ui.stdout(), "{}", op.id().hex())?; + return Ok(()); + } + if args.display != OperationDisplay::View { + writeln!(ui.stdout(), "{:#?}", op.store_operation())?; + } + if args.display != OperationDisplay::Operation { + writeln!(ui.stdout(), "{:#?}", op.view()?.store_view())?; + } + Ok(()) +} diff --git a/cli/src/commands/debug/reindex.rs b/cli/src/commands/debug/reindex.rs new file mode 100644 index 000000000..6814e15ca --- /dev/null +++ b/cli/src/commands/debug/reindex.rs @@ -0,0 +1,57 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use jj_lib::default_index::{AsCompositeIndex as _, DefaultIndexStore}; +use jj_lib::op_walk; + +use crate::cli_util::CommandHelper; +use crate::command_error::{internal_error, user_error, CommandError}; +use crate::ui::Ui; + +/// Rebuild commit index +#[derive(clap::Args, Clone, Debug)] +pub struct ReindexArgs {} + +pub fn cmd_debug_reindex( + ui: &mut Ui, + command: &CommandHelper, + _args: &ReindexArgs, +) -> Result<(), CommandError> { + // Resolve the operation without loading the repo. The index might have to + // be rebuilt while loading the repo. + let workspace = command.load_workspace()?; + let repo_loader = workspace.repo_loader(); + let op = op_walk::resolve_op_for_load(repo_loader, &command.global_args().at_operation)?; + let index_store = repo_loader.index_store(); + if let Some(default_index_store) = index_store.as_any().downcast_ref::() { + default_index_store.reinit().map_err(internal_error)?; + let default_index = default_index_store + .build_index_at_operation(&op, repo_loader.store()) + .map_err(internal_error)?; + writeln!( + ui.status(), + "Finished indexing {:?} commits.", + default_index.as_composite().stats().num_commits + )?; + } else { + return Err(user_error(format!( + "Cannot reindex indexes of type '{}'", + index_store.name() + ))); + } + Ok(()) +} diff --git a/cli/src/commands/debug/revset.rs b/cli/src/commands/debug/revset.rs new file mode 100644 index 000000000..cdc6bc074 --- /dev/null +++ b/cli/src/commands/debug/revset.rs @@ -0,0 +1,71 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use jj_lib::object_id::ObjectId; +use jj_lib::revset; + +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; +use crate::revset_util; +use crate::ui::Ui; + +/// Evaluate revset to full commit IDs +#[derive(clap::Args, Clone, Debug)] +pub struct RevsetArgs { + revision: String, +} + +pub fn cmd_debug_revset( + ui: &mut Ui, + command: &CommandHelper, + args: &RevsetArgs, +) -> Result<(), CommandError> { + let workspace_command = command.workspace_helper(ui)?; + let workspace_ctx = workspace_command.revset_parse_context(); + let repo = workspace_command.repo().as_ref(); + + let expression = revset::parse(&args.revision, &workspace_ctx)?; + writeln!(ui.stdout(), "-- Parsed:")?; + writeln!(ui.stdout(), "{expression:#?}")?; + writeln!(ui.stdout())?; + + let expression = revset::optimize(expression); + writeln!(ui.stdout(), "-- Optimized:")?; + writeln!(ui.stdout(), "{expression:#?}")?; + writeln!(ui.stdout())?; + + let symbol_resolver = revset_util::default_symbol_resolver( + repo, + command.revset_extensions().symbol_resolvers(), + workspace_command.id_prefix_context()?, + ); + let expression = expression.resolve_user_expression(repo, &symbol_resolver)?; + writeln!(ui.stdout(), "-- Resolved:")?; + writeln!(ui.stdout(), "{expression:#?}")?; + writeln!(ui.stdout())?; + + let revset = expression.evaluate(repo)?; + writeln!(ui.stdout(), "-- Evaluated:")?; + writeln!(ui.stdout(), "{revset:#?}")?; + writeln!(ui.stdout())?; + + writeln!(ui.stdout(), "-- Commit IDs:")?; + for commit_id in revset.iter() { + writeln!(ui.stdout(), "{}", commit_id.hex())?; + } + Ok(()) +} diff --git a/cli/src/commands/debug/template.rs b/cli/src/commands/debug/template.rs new file mode 100644 index 000000000..858bf2c94 --- /dev/null +++ b/cli/src/commands/debug/template.rs @@ -0,0 +1,37 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; +use crate::template_parser; +use crate::ui::Ui; + +/// Parse a template +#[derive(clap::Args, Clone, Debug)] +pub struct TemplateArgs { + template: String, +} + +pub fn cmd_debug_template( + ui: &mut Ui, + _command: &CommandHelper, + args: &TemplateArgs, +) -> Result<(), CommandError> { + let node = template_parser::parse_template(&args.template)?; + writeln!(ui.stdout(), "{node:#?}")?; + Ok(()) +} diff --git a/cli/src/commands/debug/tree.rs b/cli/src/commands/debug/tree.rs new file mode 100644 index 000000000..4ab4ddb40 --- /dev/null +++ b/cli/src/commands/debug/tree.rs @@ -0,0 +1,71 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use jj_lib::backend::TreeId; +use jj_lib::merged_tree::MergedTree; +use jj_lib::repo::Repo; +use jj_lib::repo_path::RepoPathBuf; + +use crate::cli_util::{CommandHelper, RevisionArg}; +use crate::command_error::{user_error, CommandError}; +use crate::ui::Ui; + +/// List the recursive entries of a tree. +#[derive(clap::Args, Clone, Debug)] +pub struct TreeArgs { + #[arg(long, short = 'r')] + revision: Option, + #[arg(long, conflicts_with = "revision")] + id: Option, + #[arg(long, requires = "id")] + dir: Option, + paths: Vec, + // TODO: Add an option to include trees that are ancestors of the matched paths +} + +pub fn cmd_debug_tree( + ui: &mut Ui, + command: &CommandHelper, + args: &TreeArgs, +) -> Result<(), CommandError> { + let workspace_command = command.workspace_helper(ui)?; + let tree = if let Some(tree_id_hex) = &args.id { + 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_ref().unwrap_or(&RevisionArg::AT))?; + commit.tree()? + }; + let matcher = workspace_command + .parse_file_patterns(&args.paths)? + .to_matcher(); + 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(()) +} diff --git a/cli/src/commands/debug/watchman.rs b/cli/src/commands/debug/watchman.rs new file mode 100644 index 000000000..dfb1faf8f --- /dev/null +++ b/cli/src/commands/debug/watchman.rs @@ -0,0 +1,113 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::any::Any; +use std::fmt::Debug; +use std::io::Write as _; + +use clap::Subcommand; +use jj_lib::fsmonitor::{FsmonitorSettings, WatchmanConfig}; +use jj_lib::local_working_copy::LocalWorkingCopy; + +use crate::cli_util::CommandHelper; +use crate::command_error::{user_error, CommandError}; +use crate::ui::Ui; + +#[derive(Subcommand, Clone, Debug)] +pub enum WatchmanCommand { + /// Check whether `watchman` is enabled and whether it's correctly installed + Status, + QueryClock, + QueryChangedFiles, + ResetClock, +} + +#[cfg(feature = "watchman")] +pub fn cmd_debug_watchman( + ui: &mut Ui, + command: &CommandHelper, + subcommand: &WatchmanCommand, +) -> Result<(), CommandError> { + use jj_lib::local_working_copy::LockedLocalWorkingCopy; + + let mut workspace_command = command.workspace_helper(ui)?; + let repo = workspace_command.repo().clone(); + match subcommand { + WatchmanCommand::Status => { + // TODO(ilyagr): It would be nice to add colors here + match command.settings().fsmonitor_settings()? { + FsmonitorSettings::Watchman { .. } => { + writeln!(ui.stdout(), "Watchman is enabled via `core.fsmonitor`.")? + } + FsmonitorSettings::None => writeln!( + ui.stdout(), + "Watchman is disabled. Set `core.fsmonitor=\"watchman\"` to \ + enable.\nAttempting to contact the `watchman` CLI regardless..." + )?, + other_fsmonitor => { + return Err(user_error(format!( + "This command does not support the currently enabled filesystem monitor: \ + {other_fsmonitor:?}." + ))) + } + }; + let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; + let _ = wc.query_watchman(&WatchmanConfig::default())?; + writeln!( + ui.stdout(), + "The watchman server seems to be installed and working correctly." + )?; + } + WatchmanCommand::QueryClock => { + let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; + let (clock, _changed_files) = wc.query_watchman(&WatchmanConfig::default())?; + writeln!(ui.stdout(), "Clock: {clock:?}")?; + } + WatchmanCommand::QueryChangedFiles => { + let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; + let (_clock, changed_files) = wc.query_watchman(&WatchmanConfig::default())?; + writeln!(ui.stdout(), "Changed files: {changed_files:?}")?; + } + WatchmanCommand::ResetClock => { + let (mut locked_ws, _commit) = workspace_command.start_working_copy_mutation()?; + let Some(locked_local_wc): Option<&mut LockedLocalWorkingCopy> = + locked_ws.locked_wc().as_any_mut().downcast_mut() + else { + return Err(user_error( + "This command requires a standard local-disk working copy", + )); + }; + locked_local_wc.reset_watchman()?; + locked_ws.finish(repo.op_id().clone())?; + writeln!(ui.status(), "Reset Watchman clock")?; + } + } + Ok(()) +} + +#[cfg(not(feature = "watchman"))] +pub fn cmd_debug_watchman( + _ui: &mut Ui, + _command: &CommandHelper, + _subcommand: &WatchmanCommand, +) -> Result<(), CommandError> { + Err(user_error( + "Cannot query Watchman because jj was not compiled with the `watchman` feature", + )) +} + +fn check_local_disk_wc(x: &dyn Any) -> Result<&LocalWorkingCopy, CommandError> { + x.downcast_ref() + .ok_or_else(|| user_error("This command requires a standard local-disk working copy")) +} diff --git a/cli/src/commands/debug/working_copy.rs b/cli/src/commands/debug/working_copy.rs new file mode 100644 index 000000000..202fb379a --- /dev/null +++ b/cli/src/commands/debug/working_copy.rs @@ -0,0 +1,49 @@ +// Copyright 2023 The Jujutsu Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::fmt::Debug; +use std::io::Write as _; + +use jj_lib::working_copy::WorkingCopy; + +use super::check_local_disk_wc; +use crate::cli_util::CommandHelper; +use crate::command_error::CommandError; +use crate::ui::Ui; + +/// Show information about the working copy state +#[derive(clap::Args, Clone, Debug)] +pub struct WorkingCopyArgs {} + +pub fn cmd_debug_working_copy( + ui: &mut Ui, + command: &CommandHelper, + _args: &WorkingCopyArgs, +) -> Result<(), CommandError> { + let workspace_command = command.workspace_helper(ui)?; + let wc = check_local_disk_wc(workspace_command.working_copy().as_any())?; + writeln!(ui.stdout(), "Current operation: {:?}", wc.operation_id())?; + writeln!(ui.stdout(), "Current tree: {:?}", wc.tree_id()?)?; + for (file, state) in wc.file_states()? { + writeln!( + ui.stdout(), + "{:?} {:13?} {:10?} {:?}", + state.file_type, + state.size, + state.mtime.0, + file + )?; + } + Ok(()) +}