use clap_complete::ArgValueCandidates;
use clap_complete::ArgValueCompleter;
use itertools::Itertools as _;
use jj_lib::object_id::ObjectId as _;
use tracing::instrument;
use crate::cli_util::CommandHelper;
use crate::cli_util::RevisionArg;
use crate::cli_util::print_conflicted_paths;
use crate::cli_util::print_unmatched_explicit_paths;
use crate::command_error::CommandError;
use crate::command_error::cli_error;
use crate::complete;
use crate::formatter::FormatterExt as _;
use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct ResolveArgs {
#[arg(long, short, default_value = "@", value_name = "REVSET")]
#[arg(add = ArgValueCompleter::new(complete::revset_expression_mutable_conflicts))]
revision: RevisionArg,
#[arg(long, short)]
list: bool,
#[arg(long, conflicts_with = "list", value_name = "NAME")]
#[arg(add = ArgValueCandidates::new(complete::merge_editors))]
tool: Option<String>,
#[arg(value_name = "FILESETS", value_hint = clap::ValueHint::AnyPath)]
#[arg(add = ArgValueCompleter::new(complete::revision_conflicted_files))]
paths: Vec<String>,
}
#[instrument(skip_all)]
pub(crate) async fn cmd_resolve(
ui: &mut Ui,
command: &CommandHelper,
args: &ResolveArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?;
let matcher = fileset_expression.to_matcher();
let commit = workspace_command
.resolve_single_rev(ui, &args.revision)
.await?;
let tree = commit.tree();
let conflicts = tree.conflicts_matching(&matcher).collect_vec();
print_unmatched_explicit_paths(ui, &workspace_command, &fileset_expression, [&tree])?;
if conflicts.is_empty() {
return Err(cli_error(if args.paths.is_empty() {
"No conflicts found at this revision"
} else {
"No conflicts found at the given path(s)"
}));
}
if args.list {
return print_conflicted_paths(
conflicts,
ui.stdout_formatter().as_mut(),
&workspace_command,
);
}
let repo_paths = conflicts
.iter()
.map(|(path, _)| path.as_ref())
.collect_vec();
workspace_command.check_rewritable([commit.id()]).await?;
let merge_editor = workspace_command.merge_editor(ui, args.tool.as_deref())?;
let mut tx = workspace_command.start_transaction();
let (new_tree, partial_resolution_error) =
merge_editor.edit_files(ui, &tree, &repo_paths).await?;
let new_commit = tx
.repo_mut()
.rewrite_commit(&commit)
.set_tree(new_tree)
.write()
.await?;
tx.finish(
ui,
format!("Resolve conflicts in commit {}", commit.id().hex()),
)
.await?;
if workspace_command.get_wc_commit_id() != Some(new_commit.id())
&& let Some(mut formatter) = ui.status_formatter()
&& new_commit.has_conflict()
{
let new_tree = new_commit.tree();
let new_conflicts = new_tree.conflicts().collect_vec();
writeln!(
formatter.labeled("warning").with_heading("Warning: "),
"After this operation, some files at this revision still have conflicts:"
)?;
print_conflicted_paths(new_conflicts, formatter.as_mut(), &workspace_command)?;
}
if let Some(err) = partial_resolution_error {
return Err(err.into());
}
Ok(())
}