135 lines · 4.5 KB · Rust
RawDownload
// Copyright 2020 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 itertools::Itertools;use jj_lib::repo::Repo;use jj_lib::revset::{RevsetExpression, RevsetIteratorExt}; use crate::cli_util::{short_commit_hash, CommandHelper};use crate::command_error::{user_error, CommandError};use crate::commands::next::choose_commit;use crate::ui::Ui; /// Move the working copy commit to the parent of the current revision.///////// The command moves you to the parent in a linear fashion.////// ```text/// D @  D/// |/   |/// A => A @/// |    |//// B    B/// ```////// If `--edit` is passed, it will move the working copy commit/// directly to the parent.////// ```text/// D @  D/// |/   |/// C => @/// |    |/// B    B/// |    |/// A    A////// If your working-copy commit already has visible children, then `--edit` is/// implied./// ```// TODO(#2126): Handle multiple parents, e.g merges.#[derive(clap::Args, Clone, Debug)]#[command(verbatim_doc_comment)]pub(crate) struct PrevArgs {    /// How many revisions to move backward. By default moves to the parent.    #[arg(default_value = "1")]    amount: u64,    /// Edit the parent directly, instead of moving the working-copy commit.    #[arg(long)]    edit: bool,} pub(crate) fn cmd_prev(    ui: &mut Ui,    command: &CommandHelper,    args: &PrevArgs,) -> Result<(), CommandError> {    let mut workspace_command = command.workspace_helper(ui)?;    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 edit = args.edit        || !workspace_command            .repo()            .view()            .heads()            .contains(current_wc_id);    let current_wc = workspace_command.repo().store().get_commit(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 prev` on a merge commit")),        }    };    let ancestor_expression = RevsetExpression::commit(start_id.clone()).ancestors_at(amount);    let target_revset = if edit {        ancestor_expression    } else {        // Jujutsu will always create a new commit for prev, even where Mercurial cannot        // and fails. The decision and all discussion around it are available        // here: https://github.com/martinvonz/jj/pull/1200#discussion_r1298623933        //        // If users ever request erroring out, add `.ancestors()` to the revset below.        ancestor_expression.minus(&RevsetExpression::commit(current_wc_id.clone()))    };    let targets: Vec<_> = target_revset        .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 ancestor found {amount} commit{} back",                if amount > 1 { "s" } else { "" }            )))        }        commits => choose_commit(ui, &workspace_command, "prev", commits)?,    };    // Generate a short commit hash, to make it readable in the op log.    let current_short = short_commit_hash(current_wc.id());    let target_short = short_commit_hash(target.id());    // If we're editing, just move to the revision directly.    if edit {        // The target must be rewritable if we're editing.        workspace_command.check_rewritable([target])?;        let mut tx = workspace_command.start_transaction();        tx.edit(target)?;        tx.finish(            ui,            format!("prev: {current_short} -> editing {target_short}"),        )?;        return Ok(());    }    let mut tx = workspace_command.start_transaction();    tx.check_out(target)?;    tx.finish(ui, format!("prev: {current_short} -> {target_short}"))?;    Ok(())}