just playing with tangled
at gvimdiff 162 lines 5.9 kB view raw
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 clap_complete::ArgValueCompleter; 16use jj_lib::backend::Signature; 17use jj_lib::object_id::ObjectId as _; 18use jj_lib::repo::Repo as _; 19use tracing::instrument; 20 21use crate::cli_util::CommandHelper; 22use crate::command_error::user_error; 23use crate::command_error::CommandError; 24use crate::complete; 25use crate::description_util::description_template; 26use crate::description_util::edit_description; 27use crate::description_util::join_message_paragraphs; 28use crate::text_util::parse_author; 29use crate::ui::Ui; 30 31/// Update the description and create a new change on top. 32#[derive(clap::Args, Clone, Debug)] 33pub(crate) struct CommitArgs { 34 /// Interactively choose which changes to include in the first commit 35 #[arg(short, long)] 36 interactive: bool, 37 /// Specify diff editor to be used (implies --interactive) 38 #[arg(long, value_name = "NAME")] 39 tool: Option<String>, 40 /// The change description to use (don't open editor) 41 #[arg(long = "message", short, value_name = "MESSAGE")] 42 message_paragraphs: Vec<String>, 43 /// Put these paths in the first commit 44 #[arg( 45 value_name = "FILESETS", 46 value_hint = clap::ValueHint::AnyPath, 47 add = ArgValueCompleter::new(complete::modified_files), 48 )] 49 paths: Vec<String>, 50 /// Reset the author to the configured user 51 /// 52 /// This resets the author name, email, and timestamp. 53 /// 54 /// You can use it in combination with the JJ_USER and JJ_EMAIL 55 /// environment variables to set a different author: 56 /// 57 /// $ JJ_USER='Foo Bar' JJ_EMAIL=foo@bar.com jj commit --reset-author 58 #[arg(long)] 59 reset_author: bool, 60 /// Set author to the provided string 61 /// 62 /// This changes author name and email while retaining author 63 /// timestamp for non-discardable commits. 64 #[arg( 65 long, 66 conflicts_with = "reset_author", 67 value_parser = parse_author 68 )] 69 author: Option<(String, String)>, 70} 71 72#[instrument(skip_all)] 73pub(crate) fn cmd_commit( 74 ui: &mut Ui, 75 command: &CommandHelper, 76 args: &CommitArgs, 77) -> Result<(), CommandError> { 78 let mut workspace_command = command.workspace_helper(ui)?; 79 80 let commit_id = workspace_command 81 .get_wc_commit_id() 82 .ok_or_else(|| user_error("This command requires a working copy"))?; 83 let commit = workspace_command.repo().store().get_commit(commit_id)?; 84 let matcher = workspace_command 85 .parse_file_patterns(ui, &args.paths)? 86 .to_matcher(); 87 let advanceable_bookmarks = workspace_command.get_advanceable_bookmarks(commit.parent_ids())?; 88 let diff_selector = 89 workspace_command.diff_selector(ui, args.tool.as_deref(), args.interactive)?; 90 let text_editor = workspace_command.text_editor()?; 91 let mut tx = workspace_command.start_transaction(); 92 let base_tree = commit.parent_tree(tx.repo())?; 93 let format_instructions = || { 94 format!( 95 "\ 96You are splitting the working-copy commit: {} 97 98The diff initially shows all changes. Adjust the right side until it shows the 99contents you want for the first commit. The remainder will be included in the 100new working-copy commit. 101", 102 tx.format_commit_summary(&commit) 103 ) 104 }; 105 let tree_id = diff_selector.select( 106 &base_tree, 107 &commit.tree()?, 108 matcher.as_ref(), 109 format_instructions, 110 )?; 111 if !args.paths.is_empty() && tree_id == base_tree.id() { 112 writeln!( 113 ui.warning_default(), 114 "The given paths do not match any file: {}", 115 args.paths.join(" ") 116 )?; 117 } 118 119 let mut commit_builder = tx.repo_mut().rewrite_commit(&commit).detach(); 120 commit_builder.set_tree_id(tree_id); 121 if args.reset_author { 122 commit_builder.set_author(commit_builder.committer().clone()); 123 } 124 if let Some((name, email)) = args.author.clone() { 125 let new_author = Signature { 126 name, 127 email, 128 timestamp: commit_builder.author().timestamp, 129 }; 130 commit_builder.set_author(new_author); 131 } 132 133 let description = if !args.message_paragraphs.is_empty() { 134 join_message_paragraphs(&args.message_paragraphs) 135 } else { 136 if commit_builder.description().is_empty() { 137 commit_builder.set_description(tx.settings().get_string("ui.default-description")?); 138 } 139 let temp_commit = commit_builder.write_hidden()?; 140 let template = description_template(ui, &tx, "", &temp_commit)?; 141 edit_description(&text_editor, &template)? 142 }; 143 commit_builder.set_description(description); 144 let new_commit = commit_builder.write(tx.repo_mut())?; 145 146 let workspace_names = tx.repo().view().workspaces_for_wc_commit_id(commit.id()); 147 if !workspace_names.is_empty() { 148 let new_wc_commit = tx 149 .repo_mut() 150 .new_commit(vec![new_commit.id().clone()], commit.tree_id().clone()) 151 .write()?; 152 153 // Does nothing if there's no bookmarks to advance. 154 tx.advance_bookmarks(advanceable_bookmarks, new_commit.id()); 155 156 for name in workspace_names { 157 tx.repo_mut().edit(name, &new_wc_commit).unwrap(); 158 } 159 } 160 tx.finish(ui, format!("commit {}", commit.id().hex()))?; 161 Ok(()) 162}