use std::io::Write;
use itertools::Itertools;
use jj_lib::commit::Commit;
use jj_lib::repo::Repo;
use jj_lib::revset::{RevsetExpression, RevsetIteratorExt};
use crate::cli_util::{
short_commit_hash, user_error, CommandError, CommandHelper, WorkspaceCommandHelper,
};
use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)]
#[command(verbatim_doc_comment)]
pub(crate) struct NextArgs {
#[arg(default_value = "1")]
amount: u64,
#[arg(long)]
edit: bool,
}
pub fn choose_commit<'a>(
ui: &mut Ui,
workspace_command: &WorkspaceCommandHelper,
cmd: &str,
commits: &'a [Commit],
) -> Result<&'a Commit, CommandError> {
writeln!(ui.stdout(), "ambiguous {cmd} commit, choose one to target:")?;
let mut formatter = ui.stdout_formatter();
let mut choices: Vec<String> = Default::default();
for (i, commit) in commits.iter().enumerate() {
write!(formatter, "{}: ", i + 1)?;
workspace_command.write_commit_summary(formatter.as_mut(), commit)?;
writeln!(formatter)?;
choices.push(format!("{}", i + 1));
}
writeln!(formatter, "q: quit the prompt")?;
choices.push("q".to_string());
drop(formatter);
let choice = ui.prompt_choice(
"enter the index of the commit you want to target",
&choices,
None,
)?;
if choice == "q" {
return Err(user_error("ambiguous target commit"));
}
Ok(&commits[choice.parse::<usize>().unwrap() - 1])
}
pub(crate) fn cmd_next(
ui: &mut Ui,
command: &CommandHelper,
args: &NextArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let edit = args.edit;
let amount = args.amount;
let current_wc_id = workspace_command
.get_wc_commit_id()
.ok_or_else(|| user_error("This command requires a working copy"))?;
let current_wc = workspace_command.repo().store().get_commit(current_wc_id)?;
let current_short = short_commit_hash(current_wc.id());
let start_id = if edit {
current_wc_id
} else {
match current_wc.parent_ids() {
[parent_id] => parent_id,
_ => return Err(user_error("Cannot run `jj next` on a merge commit")),
}
};
let descendant_expression = RevsetExpression::commit(start_id.clone()).descendants_at(amount);
let target_expression = if edit {
descendant_expression
} else {
descendant_expression.minus(&RevsetExpression::commit(current_wc_id.clone()).descendants())
};
let targets: Vec<Commit> = target_expression
.evaluate_programmatic(workspace_command.repo().as_ref())?
.iter()
.commits(workspace_command.repo().store())
.take(2)
.try_collect()?;
let target = match targets.as_slice() {
[target] => target,
[] => {
return Err(user_error(format!(
"No descendant found {amount} commit{} forward",
if amount > 1 { "s" } else { "" }
)));
}
commits => choose_commit(ui, &workspace_command, "next", commits)?,
};
let target_short = short_commit_hash(target.id());
if edit {
workspace_command.check_rewritable([target])?;
let mut tx = workspace_command.start_transaction();
tx.edit(target)?;
tx.finish(
ui,
format!("next: {current_short} -> editing {target_short}"),
)?;
return Ok(());
}
let mut tx = workspace_command.start_transaction();
tx.check_out(target)?;
tx.finish(ui, format!("next: {current_short} -> {target_short}"))?;
Ok(())
}