@@ -19,12 +19,14 @@ use jj_lib::commit::Commit;
1919use jj_lib:: matchers:: Matcher ;
2020use jj_lib:: object_id:: ObjectId ;
2121use jj_lib:: repo:: Repo ;
22+ use jj_lib:: rewrite:: CommitWithSelection ;
2223use tracing:: instrument;
2324
2425use crate :: cli_util:: CommandHelper ;
2526use crate :: cli_util:: DiffSelector ;
2627use crate :: cli_util:: RevisionArg ;
2728use crate :: cli_util:: WorkspaceCommandHelper ;
29+ use crate :: cli_util:: WorkspaceCommandTransaction ;
2830use crate :: command_error:: user_error_with_hint;
2931use crate :: command_error:: CommandError ;
3032use crate :: complete;
@@ -138,44 +140,14 @@ pub(crate) fn cmd_split(
138140 } = args. resolve ( ui, & workspace_command) ?;
139141 let text_editor = workspace_command. text_editor ( ) ?;
140142 let mut tx = workspace_command. start_transaction ( ) ;
141- let end_tree = target_commit. tree ( ) ?;
142- let base_tree = target_commit. parent_tree ( tx. repo ( ) ) ?;
143- let format_instructions = || {
144- format ! (
145- "\
146- You are splitting a commit into two: {}
147-
148- The diff initially shows the changes in the commit you're splitting.
149-
150- Adjust the right side until it shows the contents you want for the first commit.
151- The remainder will be in the second commit.
152- " ,
153- tx. format_commit_summary( & target_commit)
154- )
155- } ;
156143
157144 // Prompt the user to select the changes they want for the first commit.
158- let selected_tree_id =
159- diff_selector. select ( & base_tree, & end_tree, matcher. as_ref ( ) , format_instructions) ?;
160- if & selected_tree_id == target_commit. tree_id ( ) {
161- // The user selected everything from the original commit.
162- writeln ! (
163- ui. warning_default( ) ,
164- "All changes have been selected, so the second commit will be empty"
165- ) ?;
166- } else if selected_tree_id == base_tree. id ( ) {
167- // The user selected nothing, so the first commit will be empty.
168- writeln ! (
169- ui. warning_default( ) ,
170- "No changes have been selected, so the first commit will be empty"
171- ) ?;
172- }
145+ let target = select_diff ( ui, & tx, & target_commit, & matcher, & diff_selector) ?;
173146
174147 // Create the first commit, which includes the changes selected by the user.
175- let selected_tree = tx. repo ( ) . store ( ) . get_root_tree ( & selected_tree_id) ?;
176148 let first_commit = {
177- let mut commit_builder = tx. repo_mut ( ) . rewrite_commit ( & target_commit ) . detach ( ) ;
178- commit_builder. set_tree_id ( selected_tree_id ) ;
149+ let mut commit_builder = tx. repo_mut ( ) . rewrite_commit ( & target . commit ) . detach ( ) ;
150+ commit_builder. set_tree_id ( target . selected_tree . id ( ) ) ;
179151 if commit_builder. description ( ) . is_empty ( ) {
180152 commit_builder. set_description ( tx. settings ( ) . get_string ( "ui.default-description" ) ?) ;
181153 }
@@ -194,27 +166,28 @@ The remainder will be in the second commit.
194166 // Create the second commit, which includes everything the user didn't
195167 // select.
196168 let second_commit = {
197- let new_tree = if parallel {
169+ let target_tree = target. commit . tree ( ) ?;
170+ let new_tree = if args. parallel {
198171 // Merge the original commit tree with its parent using the tree
199172 // containing the user selected changes as the base for the merge.
200173 // This results in a tree with the changes the user didn't select.
201- end_tree . merge ( & selected_tree, & base_tree ) ?
174+ target_tree . merge ( & target . selected_tree , & target . parent_tree ) ?
202175 } else {
203- end_tree
176+ target_tree
204177 } ;
205178 let parents = if parallel {
206- target_commit . parent_ids ( ) . to_vec ( )
179+ target . commit . parent_ids ( ) . to_vec ( )
207180 } else {
208181 vec ! [ first_commit. id( ) . clone( ) ]
209182 } ;
210- let mut commit_builder = tx. repo_mut ( ) . rewrite_commit ( & target_commit ) . detach ( ) ;
183+ let mut commit_builder = tx. repo_mut ( ) . rewrite_commit ( & target . commit ) . detach ( ) ;
211184 commit_builder
212185 . set_parents ( parents)
213186 . set_tree_id ( new_tree. id ( ) )
214187 // Generate a new change id so that the commit being split doesn't
215188 // become divergent.
216189 . generate_new_change_id ( ) ;
217- let description = if target_commit . description ( ) . is_empty ( ) {
190+ let description = if target . commit . description ( ) . is_empty ( ) {
218191 // If there was no description before, don't ask for one for the
219192 // second commit.
220193 "" . to_string ( )
@@ -238,11 +211,11 @@ The remainder will be in the second commit.
238211 // moves any bookmarks pointing to the target commit to the second
239212 // commit.
240213 tx. repo_mut ( )
241- . set_rewritten_commit ( target_commit . id ( ) . clone ( ) , second_commit. id ( ) . clone ( ) ) ;
214+ . set_rewritten_commit ( target . commit . id ( ) . clone ( ) , second_commit. id ( ) . clone ( ) ) ;
242215 }
243216 let mut num_rebased = 0 ;
244217 tx. repo_mut ( )
245- . transform_descendants ( vec ! [ target_commit . id( ) . clone( ) ] , |mut rewriter| {
218+ . transform_descendants ( vec ! [ target . commit . id( ) . clone( ) ] , |mut rewriter| {
246219 num_rebased += 1 ;
247220 if parallel && legacy_bookmark_behavior {
248221 // The old_parent is the second commit due to the rewrite above.
@@ -259,7 +232,7 @@ The remainder will be in the second commit.
259232 // Move the working copy commit (@) to the second commit for any workspaces
260233 // where the target commit is the working copy commit.
261234 for ( workspace_id, working_copy_commit) in tx. base_repo ( ) . clone ( ) . view ( ) . wc_commit_ids ( ) {
262- if working_copy_commit == target_commit . id ( ) {
235+ if working_copy_commit == target . commit . id ( ) {
263236 tx. repo_mut ( ) . edit ( workspace_id. clone ( ) , & second_commit) ?;
264237 }
265238 }
@@ -274,7 +247,55 @@ The remainder will be in the second commit.
274247 tx. write_commit_summary ( formatter. as_mut ( ) , & second_commit) ?;
275248 writeln ! ( formatter) ?;
276249 }
277- tx. finish ( ui, format ! ( "split commit {}" , target_commit . id( ) . hex( ) ) ) ?;
250+ tx. finish ( ui, format ! ( "split commit {}" , target . commit . id( ) . hex( ) ) ) ?;
278251 Ok ( ( ) )
279252}
280253
254+ /// Prompts the user to select the content they want in the first commit and
255+ /// returns the target commit and the tree corresponding to the selection.
256+ fn select_diff (
257+ ui : & Ui ,
258+ tx : & WorkspaceCommandTransaction ,
259+ target_commit : & Commit ,
260+ matcher : & dyn Matcher ,
261+ diff_selector : & DiffSelector ,
262+ ) -> Result < CommitWithSelection , CommandError > {
263+ let format_instructions = || {
264+ format ! (
265+ "\
266+ You are splitting a commit into two: {}
267+
268+ The diff initially shows the changes in the commit you're splitting.
269+
270+ Adjust the right side until it shows the contents you want for the first commit.
271+ The remainder will be in the second commit.
272+ " ,
273+ tx. format_commit_summary( target_commit)
274+ )
275+ } ;
276+ let parent_tree = target_commit. parent_tree ( tx. repo ( ) ) ?;
277+ let selected_tree_id = diff_selector. select (
278+ & parent_tree,
279+ & target_commit. tree ( ) ?,
280+ matcher,
281+ format_instructions,
282+ ) ?;
283+ let selection = CommitWithSelection {
284+ commit : target_commit. clone ( ) ,
285+ selected_tree : tx. repo ( ) . store ( ) . get_root_tree ( & selected_tree_id) ?,
286+ parent_tree,
287+ } ;
288+ if selection. is_full_selection ( ) {
289+ writeln ! (
290+ ui. warning_default( ) ,
291+ "All changes have been selected, so the second commit will be empty"
292+ ) ?;
293+ } else if selection. is_empty_selection ( ) {
294+ writeln ! (
295+ ui. warning_default( ) ,
296+ "No changes have been selected, so the first commit will be empty"
297+ ) ?;
298+ }
299+
300+ Ok ( selection)
301+ }
0 commit comments