just playing with tangled
at diffedit3 162 lines 5.2 kB view raw
1use itertools::Itertools; 2use jj_lib::commit::Commit; 3use jj_lib::matchers::EverythingMatcher; 4use jj_lib::merged_tree::MergedTree; 5use jj_lib::repo::ReadonlyRepo; 6use jj_lib::settings::UserSettings; 7 8use crate::cli_util::{edit_temp_file, WorkspaceCommandHelper}; 9use crate::command_error::CommandError; 10use crate::diff_util::{self, DiffFormat}; 11use crate::formatter::PlainTextFormatter; 12use crate::text_util; 13use crate::ui::Ui; 14 15pub fn edit_description( 16 repo: &ReadonlyRepo, 17 description: &str, 18 settings: &UserSettings, 19) -> Result<String, CommandError> { 20 let description = format!( 21 r#"{} 22JJ: Lines starting with "JJ: " (like this one) will be removed. 23"#, 24 description 25 ); 26 27 let description = edit_temp_file( 28 "description", 29 ".jjdescription", 30 repo.repo_path(), 31 &description, 32 settings, 33 )?; 34 35 // Normalize line ending, remove leading and trailing blank lines. 36 let description = description 37 .lines() 38 .filter(|line| !line.starts_with("JJ: ")) 39 .join("\n"); 40 Ok(text_util::complete_newline(description.trim_matches('\n'))) 41} 42 43/// Combines the descriptions from the input commits. If only one is non-empty, 44/// then that one is used. Otherwise we concatenate the messages and ask the 45/// user to edit the result in their editor. 46pub fn combine_messages( 47 repo: &ReadonlyRepo, 48 sources: &[&Commit], 49 destination: &Commit, 50 settings: &UserSettings, 51) -> Result<String, CommandError> { 52 let non_empty = sources 53 .iter() 54 .chain(std::iter::once(&destination)) 55 .filter(|c| !c.description().is_empty()) 56 .take(2) 57 .collect_vec(); 58 match *non_empty.as_slice() { 59 [] => { 60 return Ok(String::new()); 61 } 62 [commit] => { 63 return Ok(commit.description().to_owned()); 64 } 65 _ => {} 66 } 67 // Produce a combined description with instructions for the user to edit. 68 // Include empty descriptins too, so the user doesn't have to wonder why they 69 // only see 2 descriptions when they combined 3 commits. 70 let mut combined = "JJ: Enter a description for the combined commit.".to_string(); 71 combined.push_str("\nJJ: Description from the destination commit:\n"); 72 combined.push_str(destination.description()); 73 for commit in sources { 74 combined.push_str("\nJJ: Description from source commit:\n"); 75 combined.push_str(commit.description()); 76 } 77 edit_description(repo, &combined, settings) 78} 79 80/// Create a description from a list of paragraphs. 81/// 82/// Based on the Git CLI behavior. See `opt_parse_m()` and `cleanup_mode` in 83/// `git/builtin/commit.c`. 84pub fn join_message_paragraphs(paragraphs: &[String]) -> String { 85 // Ensure each paragraph ends with a newline, then add another newline between 86 // paragraphs. 87 paragraphs 88 .iter() 89 .map(|p| text_util::complete_newline(p.as_str())) 90 .join("\n") 91} 92 93pub fn description_template_for_describe( 94 ui: &Ui, 95 settings: &UserSettings, 96 workspace_command: &WorkspaceCommandHelper, 97 commit: &Commit, 98) -> Result<String, CommandError> { 99 let mut diff_summary_bytes = Vec::new(); 100 diff_util::show_patch( 101 ui, 102 &mut PlainTextFormatter::new(&mut diff_summary_bytes), 103 workspace_command, 104 commit, 105 &EverythingMatcher, 106 &[DiffFormat::Summary], 107 )?; 108 let description = if commit.description().is_empty() { 109 settings.default_description() 110 } else { 111 commit.description().to_owned() 112 }; 113 if diff_summary_bytes.is_empty() { 114 Ok(description) 115 } else { 116 Ok(description + "\n" + &diff_summary_to_description(&diff_summary_bytes)) 117 } 118} 119 120pub fn description_template_for_commit( 121 ui: &Ui, 122 settings: &UserSettings, 123 workspace_command: &WorkspaceCommandHelper, 124 intro: &str, 125 overall_commit_description: &str, 126 from_tree: &MergedTree, 127 to_tree: &MergedTree, 128) -> Result<String, CommandError> { 129 let mut diff_summary_bytes = Vec::new(); 130 diff_util::show_diff( 131 ui, 132 &mut PlainTextFormatter::new(&mut diff_summary_bytes), 133 workspace_command, 134 from_tree, 135 to_tree, 136 &EverythingMatcher, 137 &[DiffFormat::Summary], 138 )?; 139 let mut template_chunks = Vec::new(); 140 if !intro.is_empty() { 141 template_chunks.push(format!("JJ: {intro}\n")); 142 } 143 template_chunks.push(if overall_commit_description.is_empty() { 144 settings.default_description() 145 } else { 146 overall_commit_description.to_owned() 147 }); 148 if !diff_summary_bytes.is_empty() { 149 template_chunks.push("\n".to_owned()); 150 template_chunks.push(diff_summary_to_description(&diff_summary_bytes)); 151 } 152 Ok(template_chunks.concat()) 153} 154 155pub fn diff_summary_to_description(bytes: &[u8]) -> String { 156 let text = std::str::from_utf8(bytes).expect( 157 "Summary diffs and repo paths must always be valid UTF8.", 158 // Double-check this assumption for diffs that include file content. 159 ); 160 "JJ: This commit contains the following changes:\n".to_owned() 161 + &textwrap::indent(text, "JJ: ") 162}