@@ -19,12 +19,14 @@ use jj_lib::commit::Commit;
19
19
use jj_lib:: matchers:: Matcher ;
20
20
use jj_lib:: object_id:: ObjectId ;
21
21
use jj_lib:: repo:: Repo ;
22
+ use jj_lib:: rewrite:: CommitWithSelection ;
22
23
use tracing:: instrument;
23
24
24
25
use crate :: cli_util:: CommandHelper ;
25
26
use crate :: cli_util:: DiffSelector ;
26
27
use crate :: cli_util:: RevisionArg ;
27
28
use crate :: cli_util:: WorkspaceCommandHelper ;
29
+ use crate :: cli_util:: WorkspaceCommandTransaction ;
28
30
use crate :: command_error:: user_error_with_hint;
29
31
use crate :: command_error:: CommandError ;
30
32
use crate :: complete;
@@ -138,44 +140,14 @@ pub(crate) fn cmd_split(
138
140
} = args. resolve ( ui, & workspace_command) ?;
139
141
let text_editor = workspace_command. text_editor ( ) ?;
140
142
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
- } ;
156
143
157
144
// 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) ?;
173
146
174
147
// 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) ?;
176
148
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 ( ) ) ;
179
151
if commit_builder. description ( ) . is_empty ( ) {
180
152
commit_builder. set_description ( tx. settings ( ) . get_string ( "ui.default-description" ) ?) ;
181
153
}
@@ -194,27 +166,28 @@ The remainder will be in the second commit.
194
166
// Create the second commit, which includes everything the user didn't
195
167
// select.
196
168
let second_commit = {
197
- let new_tree = if parallel {
169
+ let target_tree = target. commit . tree ( ) ?;
170
+ let new_tree = if args. parallel {
198
171
// Merge the original commit tree with its parent using the tree
199
172
// containing the user selected changes as the base for the merge.
200
173
// 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 ) ?
202
175
} else {
203
- end_tree
176
+ target_tree
204
177
} ;
205
178
let parents = if parallel {
206
- target_commit . parent_ids ( ) . to_vec ( )
179
+ target . commit . parent_ids ( ) . to_vec ( )
207
180
} else {
208
181
vec ! [ first_commit. id( ) . clone( ) ]
209
182
} ;
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 ( ) ;
211
184
commit_builder
212
185
. set_parents ( parents)
213
186
. set_tree_id ( new_tree. id ( ) )
214
187
// Generate a new change id so that the commit being split doesn't
215
188
// become divergent.
216
189
. generate_new_change_id ( ) ;
217
- let description = if target_commit . description ( ) . is_empty ( ) {
190
+ let description = if target . commit . description ( ) . is_empty ( ) {
218
191
// If there was no description before, don't ask for one for the
219
192
// second commit.
220
193
"" . to_string ( )
@@ -238,11 +211,11 @@ The remainder will be in the second commit.
238
211
// moves any bookmarks pointing to the target commit to the second
239
212
// commit.
240
213
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 ( ) ) ;
242
215
}
243
216
let mut num_rebased = 0 ;
244
217
tx. repo_mut ( )
245
- . transform_descendants ( vec ! [ target_commit . id( ) . clone( ) ] , |mut rewriter| {
218
+ . transform_descendants ( vec ! [ target . commit . id( ) . clone( ) ] , |mut rewriter| {
246
219
num_rebased += 1 ;
247
220
if parallel && legacy_bookmark_behavior {
248
221
// The old_parent is the second commit due to the rewrite above.
@@ -259,7 +232,7 @@ The remainder will be in the second commit.
259
232
// Move the working copy commit (@) to the second commit for any workspaces
260
233
// where the target commit is the working copy commit.
261
234
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 ( ) {
263
236
tx. repo_mut ( ) . edit ( workspace_id. clone ( ) , & second_commit) ?;
264
237
}
265
238
}
@@ -274,7 +247,55 @@ The remainder will be in the second commit.
274
247
tx. write_commit_summary ( formatter. as_mut ( ) , & second_commit) ?;
275
248
writeln ! ( formatter) ?;
276
249
}
277
- tx. finish ( ui, format ! ( "split commit {}" , target_commit . id( ) . hex( ) ) ) ?;
250
+ tx. finish ( ui, format ! ( "split commit {}" , target . commit . id( ) . hex( ) ) ) ?;
278
251
Ok ( ( ) )
279
252
}
280
253
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