diff --git a/cli/src/cli_util.rs b/cli/src/cli_util.rs index bdaf04e2bb..45032a7154 100644 --- a/cli/src/cli_util.rs +++ b/cli/src/cli_util.rs @@ -328,6 +328,12 @@ impl CommandHelper { .map_err(|err| map_workspace_load_error(err, self.global_args.repository.as_deref())) } + /// Returns true if the working copy to be loaded is writable, and therefore + /// should usually be snapshotted. + pub fn is_working_copy_writable(&self) -> bool { + self.is_at_head_operation() && !self.global_args.ignore_working_copy + } + /// Returns true if the current operation is considered to be the head. pub fn is_at_head_operation(&self) -> bool { // TODO: should we accept --at-op= as the head op? or should we diff --git a/cli/src/commands/operation/log.rs b/cli/src/commands/operation/log.rs index d86b98b0ba..0ccfb5e34c 100644 --- a/cli/src/commands/operation/log.rs +++ b/cli/src/commands/operation/log.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::slice; + use jj_lib::op_walk; use crate::cli_util::{format_template, CommandHelper, LogContentFormat}; @@ -21,6 +23,10 @@ use crate::operation_templater::OperationTemplateLanguage; use crate::ui::Ui; /// Show the operation log +/// +/// Like other commands, `jj op log` snapshots the current working-copy changes +/// and merges concurrent operations. Use `--at-op=@ --ignore-working-copy` to +/// inspect the current state without mutation. #[derive(clap::Args, Clone, Debug)] pub struct OperationLogArgs { /// Limit number of operations to show @@ -49,40 +55,26 @@ pub fn cmd_op_log( command: &CommandHelper, args: &OperationLogArgs, ) -> Result<(), CommandError> { - // Don't load the repo so that the operation history can be inspected even - // with a corrupted repo state. For example, you can find the first bad - // operation id to be abandoned. - let workspace = command.load_workspace()?; - let repo_loader = workspace.repo_loader(); - let head_op_str = command.global_args().at_operation.as_deref().unwrap_or("@"); - let head_ops = if head_op_str == "@" { - // If multiple head ops can't be resolved without merging, let the - // current op be empty. Beware that resolve_op_for_load() will eliminate - // redundant heads whereas get_current_head_ops() won't. - let current_op = op_walk::resolve_op_for_load(repo_loader, head_op_str).ok(); - if let Some(op) = current_op { - vec![op] - } else { - op_walk::get_current_head_ops( - repo_loader.op_store(), - repo_loader.op_heads_store().as_ref(), - )? - } + let current_op = if command.is_working_copy_writable() { + let workspace_command = command.workspace_helper(ui)?; + workspace_command.repo().operation().clone() } else { - vec![op_walk::resolve_op_for_load(repo_loader, head_op_str)?] - }; - let current_op_id = match &*head_ops { - [op] => Some(op.id()), - _ => None, + // Don't load the repo so that the operation history can be inspected + // even with a corrupted repo state. For example, you can find the first + // bad operation id to be abandoned. + let workspace = command.load_workspace()?; + command.resolve_operation(ui, workspace.repo_loader())? }; + let op_store = current_op.op_store(); + let with_content_format = LogContentFormat::new(ui, command.settings())?; let template; let op_node_template; { let language = OperationTemplateLanguage::new( - repo_loader.op_store().root_operation_id(), - current_op_id, + op_store.root_operation_id(), + Some(current_op.id()), command.operation_template_extensions(), ); let text = match &args.template { @@ -117,7 +109,7 @@ pub fn cmd_op_log( )?; } let limit = args.limit.or(args.deprecated_limit).unwrap_or(usize::MAX); - let iter = op_walk::walk_ancestors(&head_ops).take(limit); + let iter = op_walk::walk_ancestors(slice::from_ref(¤t_op)).take(limit); if !args.no_graph { let mut graph = get_graphlog(command.settings(), formatter.raw()); for op in iter { diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index 5fcc87bce9..20ceae6159 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -1335,6 +1335,8 @@ Compare changes to the repository between two operations Show the operation log +Like other commands, `jj op log` snapshots the current working-copy changes and merges concurrent operations. Use `--at-op=@ --ignore-working-copy` to inspect the current state without mutation. + **Usage:** `jj operation log [OPTIONS]` ###### **Options:** diff --git a/cli/tests/test_concurrent_operations.rs b/cli/tests/test_concurrent_operations.rs index 40fef43489..1c6e41be69 100644 --- a/cli/tests/test_concurrent_operations.rs +++ b/cli/tests/test_concurrent_operations.rs @@ -30,22 +30,19 @@ fn test_concurrent_operation_divergence() { &["describe", "-m", "message 2", "--at-op", "@-"], ); - // "--at-op=@" disables op heads merging - let stderr = test_env.jj_cmd_failure(&repo_path, &["log", "--at-op=@"]); + // "--at-op=@" disables op heads merging, and prints head operation ids. + let stderr = test_env.jj_cmd_failure(&repo_path, &["op", "log", "--at-op=@"]); insta::assert_snapshot!(stderr, @r###" Error: The "@" expression resolved to more than one operation Hint: Try specifying one of the operations by ID: e31015019d90, 48f4a48f3f70 "###); - // "op log" doesn't merge the concurrent operations - let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]); + // "op log --at-op" should work without merging the head operations + let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "--at-op=48f4a48f3f70"]); insta::assert_snapshot!(stdout, @r###" - ○ 48f4a48f3f70 test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 + @ 48f4a48f3f70 test-username@host.example.com 2001-02-03 04:05:09.000 +07:00 - 2001-02-03 04:05:09.000 +07:00 │ describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22 │ args: jj describe -m 'message 2' --at-op @- - │ ○ e31015019d90 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00 - ├─╯ describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22 - │ args: jj describe -m 'message 1' ○ b51416386f26 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 │ add workspace 'default' ○ 9a7d829846af test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00 diff --git a/cli/tests/test_operations.rs b/cli/tests/test_operations.rs index 05c81dce1e..9d2d6e732c 100644 --- a/cli/tests/test_operations.rs +++ b/cli/tests/test_operations.rs @@ -563,7 +563,7 @@ fn test_op_recover_from_bad_gc() { "###); // "op log" should still be usable. - let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "log"]); + let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["op", "log", "--ignore-working-copy"]); insta::assert_snapshot!(stdout, @r###" @ 43d51d9b0c0c test-username@host.example.com 2001-02-03 04:05:12.000 +07:00 - 2001-02-03 04:05:12.000 +07:00 │ describe commit 37bb762e5dc08073ec4323bdffc023a0f0cc901e