1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// 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, user_error, CommandError, CommandHelper};
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
/// ```
// 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 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 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 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(())
}