diff --git a/cli/src/commands/diff.rs b/cli/src/commands/diff.rs index 6d32776f28..634cf2fb96 100644 --- a/cli/src/commands/diff.rs +++ b/cli/src/commands/diff.rs @@ -12,9 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use itertools::Itertools; +use jj_lib::commit::Commit; +use jj_lib::merged_tree::MergedTree; +use jj_lib::rewrite::merge_commit_trees; use tracing::instrument; -use crate::cli_util::{print_unmatched_explicit_paths, CommandHelper, RevisionArg}; +use crate::cli_util::{ + print_unmatched_explicit_paths, CommandHelper, RevisionArg, WorkspaceCommandHelper, +}; use crate::command_error::CommandError; use crate::diff_util::DiffFormatArgs; use crate::ui::Ui; @@ -52,6 +58,40 @@ pub(crate) struct DiffArgs { format: DiffFormatArgs, } +fn resolve_revision( + workspace_command: &WorkspaceCommandHelper, + r: &Option, +) -> Result { + workspace_command.resolve_single_rev(r.as_ref().unwrap_or(&RevisionArg::AT)) +} + +fn handle_merge_diff( + ui: &mut Ui, + workspace_command: &WorkspaceCommandHelper, + args: &DiffArgs, + from_tree: &MergedTree, + to_tree: &MergedTree, +) -> Result<(), CommandError> { + let fileset_expression = workspace_command.parse_file_patterns(&args.paths)?; + let matcher = fileset_expression.to_matcher(); + let diff_renderer = workspace_command.diff_renderer_for(&args.format)?; + ui.request_pager(); + diff_renderer.show_diff( + ui, + ui.stdout_formatter().as_mut(), + from_tree, + to_tree, + matcher.as_ref(), + )?; + print_unmatched_explicit_paths( + ui, + workspace_command, + &fileset_expression, + [from_tree, to_tree], + )?; + Ok(()) +} + #[instrument(skip_all)] pub(crate) fn cmd_diff( ui: &mut Ui, @@ -59,37 +99,44 @@ pub(crate) fn cmd_diff( args: &DiffArgs, ) -> Result<(), CommandError> { let workspace_command = command.workspace_helper(ui)?; - let from_tree; - let to_tree; - if args.from.is_some() || args.to.is_some() { - let from = - workspace_command.resolve_single_rev(args.from.as_ref().unwrap_or(&RevisionArg::AT))?; - from_tree = from.tree()?; - let to = - workspace_command.resolve_single_rev(args.to.as_ref().unwrap_or(&RevisionArg::AT))?; - to_tree = to.tree()?; + + let to; + let from; + + if args.to.is_some() || args.from.is_some() { + to = resolve_revision(&workspace_command, &args.to)?; + from = resolve_revision(&workspace_command, &args.from)?; } else { - let commit = workspace_command - .resolve_single_rev(args.revision.as_ref().unwrap_or(&RevisionArg::AT))?; - from_tree = commit.parent_tree(workspace_command.repo().as_ref())?; - to_tree = commit.tree()? + to = resolve_revision(&workspace_command, &args.revision)?; + let mut parents: Vec<_> = to.parents().try_collect()?; + if parents.len() != 1 { + return handle_merge_diff( + ui, + &workspace_command, + args, + &merge_commit_trees(workspace_command.repo().as_ref(), &parents)?, + &to.tree()?, + ); + } + from = parents.pop().unwrap(); } + let fileset_expression = workspace_command.parse_file_patterns(&args.paths)?; let matcher = fileset_expression.to_matcher(); let diff_renderer = workspace_command.diff_renderer_for(&args.format)?; ui.request_pager(); - diff_renderer.show_diff( + diff_renderer.show_commit_diff( ui, ui.stdout_formatter().as_mut(), - &from_tree, - &to_tree, + from.id(), + to.id(), matcher.as_ref(), )?; print_unmatched_explicit_paths( ui, &workspace_command, &fileset_expression, - [&from_tree, &to_tree], + [&from.tree()?, &to.tree()?], )?; Ok(()) } diff --git a/cli/src/diff_util.rs b/cli/src/diff_util.rs index 26a4ea6505..644f38ef1b 100644 --- a/cli/src/diff_util.rs +++ b/cli/src/diff_util.rs @@ -20,7 +20,7 @@ use std::{io, mem}; use futures::StreamExt; use itertools::Itertools; -use jj_lib::backend::{BackendError, TreeValue}; +use jj_lib::backend::{BackendError, CommitId, TreeValue}; use jj_lib::commit::Commit; use jj_lib::conflicts::{materialized_diff_stream, MaterializedTreeValue}; use jj_lib::diff::{Diff, DiffHunk}; @@ -237,6 +237,20 @@ impl<'a> DiffRenderer<'a> { } } + /// Generates diff between `from_tree` and `to_tree`. + pub fn show_commit_diff( + &self, + ui: &Ui, // TODO: remove Ui dependency if possible + formatter: &mut dyn Formatter, + from: &CommitId, + to: &CommitId, + matcher: &dyn Matcher, + ) -> Result<(), DiffRenderError> { + let from_tree = self.repo.store().get_commit(from)?.tree()?; + let to_tree = self.repo.store().get_commit(to)?.tree()?; + self.show_diff(ui, formatter, &from_tree, &to_tree, matcher) + } + /// Generates diff between `from_tree` and `to_tree`. pub fn show_diff( &self,