just playing with tangled
1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use jj_lib::object_id::ObjectId;
16use jj_lib::repo::Repo;
17use jj_lib::rewrite::merge_commit_trees;
18use tracing::instrument;
19
20use crate::cli_util::CommandHelper;
21use crate::command_error::{user_error, CommandError};
22use crate::description_util::{
23 description_template_for_commit, edit_description, join_message_paragraphs,
24};
25use crate::ui::Ui;
26
27/// Update the description and create a new change on top.
28#[derive(clap::Args, Clone, Debug)]
29#[command(visible_aliases=&["ci"])]
30pub(crate) struct CommitArgs {
31 /// Interactively choose which changes to include in the first commit
32 #[arg(short, long)]
33 interactive: bool,
34 /// Specify diff editor to be used (implies --interactive)
35 #[arg(long, value_name = "NAME")]
36 tool: Option<String>,
37 /// The change description to use (don't open editor)
38 #[arg(long = "message", short, value_name = "MESSAGE")]
39 message_paragraphs: Vec<String>,
40 /// Put these paths in the first commit
41 #[arg(value_hint = clap::ValueHint::AnyPath)]
42 paths: Vec<String>,
43}
44
45#[instrument(skip_all)]
46pub(crate) fn cmd_commit(
47 ui: &mut Ui,
48 command: &CommandHelper,
49 args: &CommitArgs,
50) -> Result<(), CommandError> {
51 let mut workspace_command = command.workspace_helper(ui)?;
52
53 let commit_id = workspace_command
54 .get_wc_commit_id()
55 .ok_or_else(|| user_error("This command requires a working copy"))?;
56 let commit = workspace_command.repo().store().get_commit(commit_id)?;
57 let matcher = workspace_command
58 .parse_file_patterns(&args.paths)?
59 .to_matcher();
60 let advanceable_branches = workspace_command.get_advanceable_branches(commit.parent_ids())?;
61 let diff_selector =
62 workspace_command.diff_selector(ui, args.tool.as_deref(), args.interactive)?;
63 let mut tx = workspace_command.start_transaction();
64 let base_tree = merge_commit_trees(tx.repo(), &commit.parents())?;
65 let instructions = format!(
66 "\
67You are splitting the working-copy commit: {}
68
69The diff initially shows all changes. Adjust the right side until it shows the
70contents you want for the first commit. The remainder will be included in the
71new working-copy commit.
72",
73 tx.format_commit_summary(&commit)
74 );
75 let tree_id = diff_selector.select(
76 &base_tree,
77 &commit.tree()?,
78 matcher.as_ref(),
79 Some(&instructions),
80 )?;
81 let middle_tree = tx.repo().store().get_root_tree(&tree_id)?;
82 if !args.paths.is_empty() && middle_tree.id() == base_tree.id() {
83 writeln!(
84 ui.warning_default(),
85 "The given paths do not match any file: {}",
86 args.paths.join(" ")
87 )?;
88 }
89
90 let template = description_template_for_commit(
91 ui,
92 command.settings(),
93 tx.base_workspace_helper(),
94 "",
95 commit.description(),
96 &base_tree,
97 &middle_tree,
98 )?;
99
100 let description = if !args.message_paragraphs.is_empty() {
101 join_message_paragraphs(&args.message_paragraphs)
102 } else {
103 edit_description(tx.base_repo(), &template, command.settings())?
104 };
105
106 let new_commit = tx
107 .mut_repo()
108 .rewrite_commit(command.settings(), &commit)
109 .set_tree_id(tree_id)
110 .set_description(description)
111 .write()?;
112 let workspace_ids = tx
113 .mut_repo()
114 .view()
115 .workspaces_for_wc_commit_id(commit.id());
116 if !workspace_ids.is_empty() {
117 let new_wc_commit = tx
118 .mut_repo()
119 .new_commit(
120 command.settings(),
121 vec![new_commit.id().clone()],
122 commit.tree_id().clone(),
123 )
124 .write()?;
125
126 // Does nothing if there's no branches to advance.
127 tx.advance_branches(advanceable_branches, new_commit.id());
128
129 for workspace_id in workspace_ids {
130 tx.mut_repo().edit(workspace_id, &new_wc_commit).unwrap();
131 }
132 }
133 tx.finish(ui, format!("commit {}", commit.id().hex()))?;
134 Ok(())
135}