just playing with tangled
1// Copyright 2022 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 std::path::Path;
16
17use crate::common::TestEnvironment;
18
19#[test]
20fn test_squash() {
21 let test_env = TestEnvironment::default();
22 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
23 let repo_path = test_env.env_root().join("repo");
24
25 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
26 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
27 test_env.jj_cmd_ok(&repo_path, &["new"]);
28 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
29 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
30 test_env.jj_cmd_ok(&repo_path, &["new"]);
31 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
32 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
33 // Test the setup
34 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
35 @ 90fe0a96fc90 c
36 ◉ fa5efbdf533c b
37 ◉ 90aeefd03044 a
38 ◉ 000000000000
39 "###);
40
41 // Squashes the working copy into the parent by default
42 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash"]);
43 insta::assert_snapshot!(stdout, @"");
44 insta::assert_snapshot!(stderr, @r###"
45 Working copy now at: vruxwmqv b9280a98 (empty) (no description set)
46 Parent commit : kkmpptxz 6ca29c9d b c | (no description set)
47 "###);
48 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
49 @ b9280a9898cb
50 ◉ 6ca29c9d2e7c b c
51 ◉ 90aeefd03044 a
52 ◉ 000000000000
53 "###);
54 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
55 insta::assert_snapshot!(stdout, @r###"
56 c
57 "###);
58
59 // Can squash a given commit into its parent
60 test_env.jj_cmd_ok(&repo_path, &["undo"]);
61 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "-r", "b"]);
62 insta::assert_snapshot!(stdout, @"");
63 insta::assert_snapshot!(stderr, @r###"
64 Rebased 1 descendant commits
65 Working copy now at: mzvwutvl e87cf8eb c | (no description set)
66 Parent commit : qpvuntsm 893c93ae a b | (no description set)
67 "###);
68 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
69 @ e87cf8ebc7e1 c
70 ◉ 893c93ae2a87 a b
71 ◉ 000000000000
72 "###);
73 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "b"]);
74 insta::assert_snapshot!(stdout, @r###"
75 b
76 "###);
77 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
78 insta::assert_snapshot!(stdout, @r###"
79 c
80 "###);
81
82 // Cannot squash a merge commit (because it's unclear which parent it should go
83 // into)
84 test_env.jj_cmd_ok(&repo_path, &["undo"]);
85 test_env.jj_cmd_ok(&repo_path, &["edit", "b"]);
86 test_env.jj_cmd_ok(&repo_path, &["new"]);
87 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "d"]);
88 std::fs::write(repo_path.join("file2"), "d\n").unwrap();
89 test_env.jj_cmd_ok(&repo_path, &["new", "c", "d"]);
90 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "e"]);
91 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
92 @ c7a11b36d333 e
93 ├─╮
94 │ ◉ 5658521e0f8b d
95 ◉ │ 90fe0a96fc90 c
96 ├─╯
97 ◉ fa5efbdf533c b
98 ◉ 90aeefd03044 a
99 ◉ 000000000000
100 "###);
101 let stderr = test_env.jj_cmd_failure(&repo_path, &["squash"]);
102 insta::assert_snapshot!(stderr, @r###"
103 Error: Cannot squash merge commits
104 "###);
105
106 // Can squash into a merge commit
107 test_env.jj_cmd_ok(&repo_path, &["new", "e"]);
108 std::fs::write(repo_path.join("file1"), "e\n").unwrap();
109 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash"]);
110 insta::assert_snapshot!(stdout, @"");
111 insta::assert_snapshot!(stderr, @r###"
112 Working copy now at: xlzxqlsl 959145c1 (empty) (no description set)
113 Parent commit : nmzmmopx 80960125 e | (no description set)
114 "###);
115 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
116 @ 959145c11426
117 ◉ 80960125bb96 e
118 ├─╮
119 │ ◉ 5658521e0f8b d
120 ◉ │ 90fe0a96fc90 c
121 ├─╯
122 ◉ fa5efbdf533c b
123 ◉ 90aeefd03044 a
124 ◉ 000000000000
125 "###);
126 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "e"]);
127 insta::assert_snapshot!(stdout, @r###"
128 e
129 "###);
130}
131
132#[test]
133fn test_squash_partial() {
134 let mut test_env = TestEnvironment::default();
135 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
136 let repo_path = test_env.env_root().join("repo");
137
138 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
139 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
140 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
141 test_env.jj_cmd_ok(&repo_path, &["new"]);
142 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
143 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
144 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
145 test_env.jj_cmd_ok(&repo_path, &["new"]);
146 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
147 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
148 std::fs::write(repo_path.join("file2"), "c\n").unwrap();
149 // Test the setup
150 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
151 @ d989314f3df0 c
152 ◉ 2a2d19a3283f b
153 ◉ 47a1e795d146 a
154 ◉ 000000000000
155 "###);
156
157 // If we don't make any changes in the diff-editor, the whole change is moved
158 // into the parent
159 let edit_script = test_env.set_up_fake_diff_editor();
160 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "-r", "b", "-i"]);
161 insta::assert_snapshot!(stdout, @"");
162 insta::assert_snapshot!(stderr, @r###"
163 Rebased 1 descendant commits
164 Working copy now at: mzvwutvl f03d5ce4 c | (no description set)
165 Parent commit : qpvuntsm c9f931cd a b | (no description set)
166 "###);
167 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
168 @ f03d5ce4a973 c
169 ◉ c9f931cd78af a b
170 ◉ 000000000000
171 "###);
172 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "a"]);
173 insta::assert_snapshot!(stdout, @r###"
174 b
175 "###);
176
177 // Can squash only some changes in interactive mode
178 test_env.jj_cmd_ok(&repo_path, &["undo"]);
179 std::fs::write(&edit_script, "reset file1").unwrap();
180 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "-r", "b", "-i"]);
181 insta::assert_snapshot!(stdout, @"");
182 insta::assert_snapshot!(stderr, @r###"
183 Rebased 2 descendant commits
184 Working copy now at: mzvwutvl e7a40106 c | (no description set)
185 Parent commit : kkmpptxz 05d95164 b | (no description set)
186 "###);
187 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
188 @ e7a40106bee6 c
189 ◉ 05d951646873 b
190 ◉ 0c5ddc685260 a
191 ◉ 000000000000
192 "###);
193 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "a"]);
194 insta::assert_snapshot!(stdout, @r###"
195 a
196 "###);
197 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "a"]);
198 insta::assert_snapshot!(stdout, @r###"
199 b
200 "###);
201 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "b"]);
202 insta::assert_snapshot!(stdout, @r###"
203 b
204 "###);
205 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "b"]);
206 insta::assert_snapshot!(stdout, @r###"
207 b
208 "###);
209
210 // Can squash only some changes in non-interactive mode
211 test_env.jj_cmd_ok(&repo_path, &["undo"]);
212 // Clear the script so we know it won't be used even without -i
213 std::fs::write(&edit_script, "").unwrap();
214 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "-r", "b", "file2"]);
215 insta::assert_snapshot!(stdout, @"");
216 insta::assert_snapshot!(stderr, @r###"
217 Rebased 2 descendant commits
218 Working copy now at: mzvwutvl a911fa1d c | (no description set)
219 Parent commit : kkmpptxz fb73ad17 b | (no description set)
220 "###);
221 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
222 @ a911fa1d0627 c
223 ◉ fb73ad17899f b
224 ◉ 70621f4c7a42 a
225 ◉ 000000000000
226 "###);
227 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "a"]);
228 insta::assert_snapshot!(stdout, @r###"
229 a
230 "###);
231 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "a"]);
232 insta::assert_snapshot!(stdout, @r###"
233 b
234 "###);
235 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "b"]);
236 insta::assert_snapshot!(stdout, @r###"
237 b
238 "###);
239 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "b"]);
240 insta::assert_snapshot!(stdout, @r###"
241 b
242 "###);
243
244 // If we specify only a non-existent file, then nothing changes.
245 test_env.jj_cmd_ok(&repo_path, &["undo"]);
246 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "-r", "b", "nonexistent"]);
247 insta::assert_snapshot!(stdout, @"");
248 insta::assert_snapshot!(stderr, @r###"
249 Nothing changed.
250 "###);
251
252 // We get a warning if we pass a positional argument that looks like a revset
253 test_env.jj_cmd_ok(&repo_path, &["undo"]);
254 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "b"]);
255 insta::assert_snapshot!(stderr, @r###"
256 Warning: The argument "b" is being interpreted as a path. To specify a revset, pass -r "b" instead.
257 Nothing changed.
258 "###);
259 insta::assert_snapshot!(stdout, @"");
260}
261
262#[test]
263fn test_squash_from_to() {
264 let test_env = TestEnvironment::default();
265 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
266 let repo_path = test_env.env_root().join("repo");
267
268 // Create history like this:
269 // F
270 // |
271 // E C
272 // | |
273 // D B
274 // |/
275 // A
276 //
277 // When moving changes between e.g. C and F, we should not get unrelated changes
278 // from B and D.
279 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
280 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
281 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
282 std::fs::write(repo_path.join("file3"), "a\n").unwrap();
283 test_env.jj_cmd_ok(&repo_path, &["new"]);
284 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
285 std::fs::write(repo_path.join("file3"), "b\n").unwrap();
286 test_env.jj_cmd_ok(&repo_path, &["new"]);
287 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
288 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
289 test_env.jj_cmd_ok(&repo_path, &["edit", "a"]);
290 test_env.jj_cmd_ok(&repo_path, &["new"]);
291 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "d"]);
292 std::fs::write(repo_path.join("file3"), "d\n").unwrap();
293 test_env.jj_cmd_ok(&repo_path, &["new"]);
294 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "e"]);
295 std::fs::write(repo_path.join("file2"), "e\n").unwrap();
296 test_env.jj_cmd_ok(&repo_path, &["new"]);
297 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "f"]);
298 std::fs::write(repo_path.join("file2"), "f\n").unwrap();
299 // Test the setup
300 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
301 @ 0d7353584003 f
302 ◉ e9515f21068c e
303 ◉ bdd835cae844 d
304 │ ◉ caa4d0b23201 c
305 │ ◉ 55171e33db26 b
306 ├─╯
307 ◉ 3db0a2f5b535 a
308 ◉ 000000000000
309 "###);
310
311 // Errors out if source and destination are the same
312 let stderr = test_env.jj_cmd_failure(&repo_path, &["squash", "--into", "@"]);
313 insta::assert_snapshot!(stderr, @r###"
314 Error: Source and destination cannot be the same
315 "###);
316
317 // Can squash from sibling, which results in the source being abandoned
318 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--from", "c"]);
319 insta::assert_snapshot!(stdout, @"");
320 insta::assert_snapshot!(stderr, @r###"
321 Working copy now at: kmkuslsw 5337fca9 f | (no description set)
322 Parent commit : znkkpsqq e9515f21 e | (no description set)
323 Added 0 files, modified 1 files, removed 0 files
324 "###);
325 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
326 @ 5337fca918e8 f
327 ◉ e9515f21068c e
328 ◉ bdd835cae844 d
329 │ ◉ 55171e33db26 b c
330 ├─╯
331 ◉ 3db0a2f5b535 a
332 ◉ 000000000000
333 "###);
334 // The change from the source has been applied
335 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
336 insta::assert_snapshot!(stdout, @r###"
337 c
338 "###);
339 // File `file2`, which was not changed in source, is unchanged
340 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
341 insta::assert_snapshot!(stdout, @r###"
342 f
343 "###);
344
345 // Can squash from ancestor
346 test_env.jj_cmd_ok(&repo_path, &["undo"]);
347 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--from", "@--"]);
348 insta::assert_snapshot!(stdout, @"");
349 insta::assert_snapshot!(stderr, @r###"
350 Working copy now at: kmkuslsw 66ff309f f | (no description set)
351 Parent commit : znkkpsqq 16f4e7c4 e | (no description set)
352 "###);
353 // The change has been removed from the source (the change pointed to by 'd'
354 // became empty and was abandoned)
355 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
356 @ 66ff309f65e8 f
357 ◉ 16f4e7c4886f e
358 │ ◉ caa4d0b23201 c
359 │ ◉ 55171e33db26 b
360 ├─╯
361 ◉ 3db0a2f5b535 a d
362 ◉ 000000000000
363 "###);
364 // The change from the source has been applied (the file contents were already
365 // "f", as is typically the case when moving changes from an ancestor)
366 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
367 insta::assert_snapshot!(stdout, @r###"
368 f
369 "###);
370
371 // Can squash from descendant
372 test_env.jj_cmd_ok(&repo_path, &["undo"]);
373 let (stdout, stderr) =
374 test_env.jj_cmd_ok(&repo_path, &["squash", "--from", "e", "--into", "d"]);
375 insta::assert_snapshot!(stdout, @"");
376 insta::assert_snapshot!(stderr, @r###"
377 Rebased 1 descendant commits
378 Working copy now at: kmkuslsw b4f8051d f | (no description set)
379 Parent commit : vruxwmqv f74c102f d e | (no description set)
380 "###);
381 // The change has been removed from the source (the change pointed to by 'e'
382 // became empty and was abandoned)
383 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
384 @ b4f8051d8466 f
385 ◉ f74c102ff29a d e
386 │ ◉ caa4d0b23201 c
387 │ ◉ 55171e33db26 b
388 ├─╯
389 ◉ 3db0a2f5b535 a
390 ◉ 000000000000
391 "###);
392 // The change from the source has been applied
393 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "d"]);
394 insta::assert_snapshot!(stdout, @r###"
395 e
396 "###);
397}
398
399#[test]
400fn test_squash_from_to_partial() {
401 let mut test_env = TestEnvironment::default();
402 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
403 let repo_path = test_env.env_root().join("repo");
404
405 // Create history like this:
406 // C
407 // |
408 // D B
409 // |/
410 // A
411 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
412 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
413 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
414 std::fs::write(repo_path.join("file3"), "a\n").unwrap();
415 test_env.jj_cmd_ok(&repo_path, &["new"]);
416 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
417 std::fs::write(repo_path.join("file3"), "b\n").unwrap();
418 test_env.jj_cmd_ok(&repo_path, &["new"]);
419 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
420 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
421 std::fs::write(repo_path.join("file2"), "c\n").unwrap();
422 test_env.jj_cmd_ok(&repo_path, &["edit", "a"]);
423 test_env.jj_cmd_ok(&repo_path, &["new"]);
424 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "d"]);
425 std::fs::write(repo_path.join("file3"), "d\n").unwrap();
426 // Test the setup
427 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
428 @ bdd835cae844 d
429 │ ◉ 5028db694b6b c
430 │ ◉ 55171e33db26 b
431 ├─╯
432 ◉ 3db0a2f5b535 a
433 ◉ 000000000000
434 "###);
435
436 let edit_script = test_env.set_up_fake_diff_editor();
437
438 // If we don't make any changes in the diff-editor, the whole change is moved
439 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "-i", "--from", "c"]);
440 insta::assert_snapshot!(stdout, @"");
441 insta::assert_snapshot!(stderr, @r###"
442 Working copy now at: vruxwmqv 71b69e43 d | (no description set)
443 Parent commit : qpvuntsm 3db0a2f5 a | (no description set)
444 Added 0 files, modified 2 files, removed 0 files
445 "###);
446 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
447 @ 71b69e433fbc d
448 │ ◉ 55171e33db26 b c
449 ├─╯
450 ◉ 3db0a2f5b535 a
451 ◉ 000000000000
452 "###);
453 // The changes from the source has been applied
454 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
455 insta::assert_snapshot!(stdout, @r###"
456 c
457 "###);
458 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
459 insta::assert_snapshot!(stdout, @r###"
460 c
461 "###);
462 // File `file3`, which was not changed in source, is unchanged
463 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file3"]);
464 insta::assert_snapshot!(stdout, @r###"
465 d
466 "###);
467
468 // Can squash only part of the change in interactive mode
469 test_env.jj_cmd_ok(&repo_path, &["undo"]);
470 std::fs::write(&edit_script, "reset file2").unwrap();
471 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "-i", "--from", "c"]);
472 insta::assert_snapshot!(stdout, @"");
473 insta::assert_snapshot!(stderr, @r###"
474 Working copy now at: vruxwmqv 63f1a6e9 d | (no description set)
475 Parent commit : qpvuntsm 3db0a2f5 a | (no description set)
476 Added 0 files, modified 1 files, removed 0 files
477 "###);
478 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
479 @ 63f1a6e96edb d
480 │ ◉ d027c6e3e6bc c
481 │ ◉ 55171e33db26 b
482 ├─╯
483 ◉ 3db0a2f5b535 a
484 ◉ 000000000000
485 "###);
486 // The selected change from the source has been applied
487 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
488 insta::assert_snapshot!(stdout, @r###"
489 c
490 "###);
491 // The unselected change from the source has not been applied
492 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
493 insta::assert_snapshot!(stdout, @r###"
494 a
495 "###);
496 // File `file3`, which was changed in source's parent, is unchanged
497 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file3"]);
498 insta::assert_snapshot!(stdout, @r###"
499 d
500 "###);
501
502 // Can squash only part of the change from a sibling in non-interactive mode
503 test_env.jj_cmd_ok(&repo_path, &["undo"]);
504 // Clear the script so we know it won't be used
505 std::fs::write(&edit_script, "").unwrap();
506 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--from", "c", "file1"]);
507 insta::assert_snapshot!(stdout, @"");
508 insta::assert_snapshot!(stderr, @r###"
509 Working copy now at: vruxwmqv 17c2e663 d | (no description set)
510 Parent commit : qpvuntsm 3db0a2f5 a | (no description set)
511 Added 0 files, modified 1 files, removed 0 files
512 "###);
513 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
514 @ 17c2e6632cc5 d
515 │ ◉ 6a3ae047a03e c
516 │ ◉ 55171e33db26 b
517 ├─╯
518 ◉ 3db0a2f5b535 a
519 ◉ 000000000000
520 "###);
521 // The selected change from the source has been applied
522 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1"]);
523 insta::assert_snapshot!(stdout, @r###"
524 c
525 "###);
526 // The unselected change from the source has not been applied
527 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
528 insta::assert_snapshot!(stdout, @r###"
529 a
530 "###);
531 // File `file3`, which was changed in source's parent, is unchanged
532 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file3"]);
533 insta::assert_snapshot!(stdout, @r###"
534 d
535 "###);
536
537 // Can squash only part of the change from a descendant in non-interactive mode
538 test_env.jj_cmd_ok(&repo_path, &["undo"]);
539 // Clear the script so we know it won't be used
540 std::fs::write(&edit_script, "").unwrap();
541 let (stdout, stderr) = test_env.jj_cmd_ok(
542 &repo_path,
543 &["squash", "--from", "c", "--into", "b", "file1"],
544 );
545 insta::assert_snapshot!(stdout, @"");
546 insta::assert_snapshot!(stderr, @r###"
547 Rebased 1 descendant commits
548 "###);
549 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
550 ◉ 21253406d416 c
551 ◉ e1cf08aae711 b
552 │ @ bdd835cae844 d
553 ├─╯
554 ◉ 3db0a2f5b535 a
555 ◉ 000000000000
556 "###);
557 // The selected change from the source has been applied
558 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file1", "-r", "b"]);
559 insta::assert_snapshot!(stdout, @r###"
560 c
561 "###);
562 // The unselected change from the source has not been applied
563 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2", "-r", "b"]);
564 insta::assert_snapshot!(stdout, @r###"
565 a
566 "###);
567
568 // If we specify only a non-existent file, then nothing changes.
569 test_env.jj_cmd_ok(&repo_path, &["undo"]);
570 let (stdout, stderr) =
571 test_env.jj_cmd_ok(&repo_path, &["squash", "--from", "c", "nonexistent"]);
572 insta::assert_snapshot!(stdout, @"");
573 insta::assert_snapshot!(stderr, @r###"
574 Nothing changed.
575 "###);
576}
577
578#[test]
579fn test_squash_from_multiple() {
580 let test_env = TestEnvironment::default();
581 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
582 let repo_path = test_env.env_root().join("repo");
583
584 // Create history like this:
585 // F
586 // |
587 // E
588 // /|\
589 // B C D
590 // \|/
591 // A
592 let file = repo_path.join("file");
593 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
594 std::fs::write(&file, "a\n").unwrap();
595 test_env.jj_cmd_ok(&repo_path, &["new"]);
596 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
597 std::fs::write(&file, "b\n").unwrap();
598 test_env.jj_cmd_ok(&repo_path, &["new", "@-"]);
599 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
600 std::fs::write(&file, "c\n").unwrap();
601 test_env.jj_cmd_ok(&repo_path, &["new", "@-"]);
602 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "d"]);
603 std::fs::write(&file, "d\n").unwrap();
604 test_env.jj_cmd_ok(&repo_path, &["new", "all:visible_heads()"]);
605 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "e"]);
606 std::fs::write(&file, "e\n").unwrap();
607 test_env.jj_cmd_ok(&repo_path, &["new"]);
608 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "f"]);
609 std::fs::write(&file, "f\n").unwrap();
610 // Test the setup
611 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
612 @ 7c982f87d244 f
613 ◉ 90fb23310e1d e
614 ├─┬─╮
615 │ │ ◉ 512dff087306 b
616 │ ◉ │ 5ee503da2262 c
617 │ ├─╯
618 ◉ │ cb214cffd91a d
619 ├─╯
620 ◉ 37941ee54ace a
621 ◉ 000000000000
622 "###);
623
624 // Squash a few commits sideways
625 let (stdout, stderr) =
626 test_env.jj_cmd_ok(&repo_path, &["squash", "--from=b", "--from=c", "--into=d"]);
627 insta::assert_snapshot!(stdout, @"");
628 insta::assert_snapshot!(stderr, @r###"
629 Rebased 2 descendant commits
630 New conflicts appeared in these commits:
631 yqosqzyt 50bd7d24 d | (conflict) (no description set)
632 To resolve the conflicts, start by updating to it:
633 jj new yqosqzytrlsw
634 Then use `jj resolve`, or edit the conflict markers in the file directly.
635 Once the conflicts are resolved, you may want inspect the result with `jj diff`.
636 Then run `jj squash` to move the resolution into the conflicted commit.
637 Working copy now at: kpqxywon dd653e49 f | (no description set)
638 Parent commit : yostqsxw e40f2544 e | (no description set)
639 "###);
640 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
641 @ dd653e494199 f
642 ◉ e40f2544ad31 e
643 ├─╮
644 ◉ │ 50bd7d246d8e d
645 ├─╯
646 ◉ 37941ee54ace a b c
647 ◉ 000000000000
648 "###);
649 // The changes from the sources have been applied
650 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=d", "file"]);
651 insta::assert_snapshot!(stdout, @r###"
652 <<<<<<< Conflict 1 of 1
653 %%%%%%% Changes from base #1 to side #1
654 -a
655 +d
656 %%%%%%% Changes from base #2 to side #2
657 -a
658 +b
659 +++++++ Contents of side #3
660 c
661 >>>>>>>
662 "###);
663
664 // Squash a few commits up an down
665 test_env.jj_cmd_ok(&repo_path, &["undo"]);
666 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--from=b|c|f", "--into=e"]);
667 insta::assert_snapshot!(stdout, @"");
668 insta::assert_snapshot!(stderr, @r###"
669 Rebased 1 descendant commits
670 Working copy now at: xznxytkn 59801ce3 (empty) (no description set)
671 Parent commit : yostqsxw b7bc1dda e f | (no description set)
672 "###);
673 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
674 @ 59801ce3ff81
675 ◉ b7bc1dda247e e f
676 ├─╮
677 ◉ │ cb214cffd91a d
678 ├─╯
679 ◉ 37941ee54ace a b c
680 ◉ 000000000000
681 "###);
682 // The changes from the sources have been applied to the destination
683 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=e", "file"]);
684 insta::assert_snapshot!(stdout, @r###"
685 f
686 "###);
687
688 // Empty squash shouldn't crash
689 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--from=none()"]);
690 insta::assert_snapshot!(stdout, @"");
691 insta::assert_snapshot!(stderr, @r###"
692 Nothing changed.
693 "###);
694}
695
696#[test]
697fn test_squash_from_multiple_partial() {
698 let test_env = TestEnvironment::default();
699 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
700 let repo_path = test_env.env_root().join("repo");
701
702 // Create history like this:
703 // F
704 // |
705 // E
706 // /|\
707 // B C D
708 // \|/
709 // A
710 let file1 = repo_path.join("file1");
711 let file2 = repo_path.join("file2");
712 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "a"]);
713 std::fs::write(&file1, "a\n").unwrap();
714 std::fs::write(&file2, "a\n").unwrap();
715 test_env.jj_cmd_ok(&repo_path, &["new"]);
716 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
717 std::fs::write(&file1, "b\n").unwrap();
718 std::fs::write(&file2, "b\n").unwrap();
719 test_env.jj_cmd_ok(&repo_path, &["new", "@-"]);
720 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "c"]);
721 std::fs::write(&file1, "c\n").unwrap();
722 std::fs::write(&file2, "c\n").unwrap();
723 test_env.jj_cmd_ok(&repo_path, &["new", "@-"]);
724 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "d"]);
725 std::fs::write(&file1, "d\n").unwrap();
726 std::fs::write(&file2, "d\n").unwrap();
727 test_env.jj_cmd_ok(&repo_path, &["new", "all:visible_heads()"]);
728 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "e"]);
729 std::fs::write(&file1, "e\n").unwrap();
730 std::fs::write(&file2, "e\n").unwrap();
731 test_env.jj_cmd_ok(&repo_path, &["new"]);
732 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "f"]);
733 std::fs::write(&file1, "f\n").unwrap();
734 std::fs::write(&file2, "f\n").unwrap();
735 // Test the setup
736 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
737 @ 5adc4b1fb0f9 f
738 ◉ 8ba764396a28 e
739 ├─┬─╮
740 │ │ ◉ 2a2d19a3283f b
741 │ ◉ │ 864a16169cef c
742 │ ├─╯
743 ◉ │ 5def0e76dfaf d
744 ├─╯
745 ◉ 47a1e795d146 a
746 ◉ 000000000000
747 "###);
748
749 // Partially squash a few commits sideways
750 let (stdout, stderr) =
751 test_env.jj_cmd_ok(&repo_path, &["squash", "--from=b|c", "--into=d", "file1"]);
752 insta::assert_snapshot!(stdout, @"");
753 insta::assert_snapshot!(stderr, @r###"
754 Rebased 2 descendant commits
755 New conflicts appeared in these commits:
756 yqosqzyt 85d3ae29 d | (conflict) (no description set)
757 To resolve the conflicts, start by updating to it:
758 jj new yqosqzytrlsw
759 Then use `jj resolve`, or edit the conflict markers in the file directly.
760 Once the conflicts are resolved, you may want inspect the result with `jj diff`.
761 Then run `jj squash` to move the resolution into the conflicted commit.
762 Working copy now at: kpqxywon 97861bbf f | (no description set)
763 Parent commit : yostqsxw 2dbaf4e8 e | (no description set)
764 "###);
765 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
766 @ 97861bbf7ae5 f
767 ◉ 2dbaf4e8c7f7 e
768 ├─┬─╮
769 │ │ ◉ ba60ddff2d41 b
770 │ ◉ │ 8ef5a315bf7d c
771 │ ├─╯
772 ◉ │ 85d3ae290b9b d
773 ├─╯
774 ◉ 47a1e795d146 a
775 ◉ 000000000000
776 "###);
777 // The selected changes have been removed from the sources
778 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=b", "file1"]);
779 insta::assert_snapshot!(stdout, @r###"
780 a
781 "###);
782 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=c", "file1"]);
783 insta::assert_snapshot!(stdout, @r###"
784 a
785 "###);
786 // The selected changes from the sources have been applied
787 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=d", "file1"]);
788 insta::assert_snapshot!(stdout, @r###"
789 <<<<<<< Conflict 1 of 1
790 %%%%%%% Changes from base #1 to side #1
791 -a
792 +d
793 %%%%%%% Changes from base #2 to side #2
794 -a
795 +b
796 +++++++ Contents of side #3
797 c
798 >>>>>>>
799 "###);
800 // The unselected change from the sources have not been applied to the
801 // destination
802 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=d", "file2"]);
803 insta::assert_snapshot!(stdout, @r###"
804 d
805 "###);
806
807 // Partially squash a few commits up an down
808 test_env.jj_cmd_ok(&repo_path, &["undo"]);
809 let (stdout, stderr) =
810 test_env.jj_cmd_ok(&repo_path, &["squash", "--from=b|c|f", "--into=e", "file1"]);
811 insta::assert_snapshot!(stdout, @"");
812 insta::assert_snapshot!(stderr, @r###"
813 Rebased 1 descendant commits
814 Working copy now at: kpqxywon 610a144d f | (no description set)
815 Parent commit : yostqsxw ac27a136 e | (no description set)
816 "###);
817 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
818 @ 610a144de39b f
819 ◉ ac27a1361b09 e
820 ├─┬─╮
821 │ │ ◉ 0c8eab864a32 b
822 │ ◉ │ ad1776ad0b1b c
823 │ ├─╯
824 ◉ │ 5def0e76dfaf d
825 ├─╯
826 ◉ 47a1e795d146 a
827 ◉ 000000000000
828 "###);
829 // The selected changes have been removed from the sources
830 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=b", "file1"]);
831 insta::assert_snapshot!(stdout, @r###"
832 a
833 "###);
834 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=c", "file1"]);
835 insta::assert_snapshot!(stdout, @r###"
836 a
837 "###);
838 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=f", "file1"]);
839 insta::assert_snapshot!(stdout, @r###"
840 f
841 "###);
842 // The selected changes from the sources have been applied to the destination
843 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=e", "file1"]);
844 insta::assert_snapshot!(stdout, @r###"
845 f
846 "###);
847 // The unselected changes from the sources have not been applied
848 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "-r=d", "file2"]);
849 insta::assert_snapshot!(stdout, @r###"
850 d
851 "###);
852}
853
854#[test]
855fn test_squash_from_multiple_partial_no_op() {
856 let test_env = TestEnvironment::default();
857 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
858 let repo_path = test_env.env_root().join("repo");
859
860 // Create history like this:
861 // B C D
862 // \|/
863 // A
864 let file_a = repo_path.join("a");
865 let file_b = repo_path.join("b");
866 let file_c = repo_path.join("c");
867 let file_d = repo_path.join("d");
868 test_env.jj_cmd_ok(&repo_path, &["describe", "-m=a"]);
869 std::fs::write(file_a, "a\n").unwrap();
870 test_env.jj_cmd_ok(&repo_path, &["new", "-m=b"]);
871 std::fs::write(file_b, "b\n").unwrap();
872 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m=c"]);
873 std::fs::write(file_c, "c\n").unwrap();
874 test_env.jj_cmd_ok(&repo_path, &["new", "@-", "-m=d"]);
875 std::fs::write(file_d, "d\n").unwrap();
876 // Test the setup
877 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
878 @ 09441f0a6266 d
879 │ ◉ 5ad3ca4090a7 c
880 ├─╯
881 │ ◉ 285201979c90 b
882 ├─╯
883 ◉ 3df52ee1f8a9 a
884 ◉ 000000000000
885 "###);
886
887 // Source commits that didn't match the paths are not rewritten
888 let (stdout, stderr) = test_env.jj_cmd_ok(
889 &repo_path,
890 &["squash", "--from=@-+ ~ @", "--into=@", "-m=d", "b"],
891 );
892 insta::assert_snapshot!(stdout, @"");
893 insta::assert_snapshot!(stderr, @r###"
894 Working copy now at: mzvwutvl 9227d0d7 d
895 Parent commit : qpvuntsm 3df52ee1 a
896 Added 1 files, modified 0 files, removed 0 files
897 "###);
898 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
899 @ 9227d0d780fa d
900 │ ◉ 5ad3ca4090a7 c
901 ├─╯
902 ◉ 3df52ee1f8a9 a
903 ◉ 000000000000
904 "###);
905 let stdout = test_env.jj_cmd_success(
906 &repo_path,
907 &[
908 "obslog",
909 "-T",
910 r#"separate(" ", commit_id.short(), description)"#,
911 ],
912 );
913 insta::assert_snapshot!(stdout, @r###"
914 @ 9227d0d780fa d
915 ├─╮
916 ◉ │ 09441f0a6266 d
917 ◉ │ cba0f0aa472b d
918 ◉ 285201979c90 b
919 ◉ 81187418277d b
920 "###);
921
922 // If no source commits match the paths, then the whole operation is a no-op
923 test_env.jj_cmd_ok(&repo_path, &["undo"]);
924 let (stdout, stderr) = test_env.jj_cmd_ok(
925 &repo_path,
926 &["squash", "--from=@-+ ~ @", "--into=@", "-m=d", "a"],
927 );
928 insta::assert_snapshot!(stdout, @"");
929 insta::assert_snapshot!(stderr, @r###"
930 Nothing changed.
931 "###);
932 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
933 @ 09441f0a6266 d
934 │ ◉ 5ad3ca4090a7 c
935 ├─╯
936 │ ◉ 285201979c90 b
937 ├─╯
938 ◉ 3df52ee1f8a9 a
939 ◉ 000000000000
940 "###);
941}
942
943fn get_log_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
944 let template = r#"separate(" ", commit_id.short(), branches, description)"#;
945 test_env.jj_cmd_success(repo_path, &["log", "-T", template])
946}
947
948#[test]
949fn test_squash_description() {
950 let mut test_env = TestEnvironment::default();
951 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
952 let repo_path = test_env.env_root().join("repo");
953
954 let edit_script = test_env.set_up_fake_editor();
955 std::fs::write(&edit_script, r#"fail"#).unwrap();
956
957 // If both descriptions are empty, the resulting description is empty
958 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
959 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
960 test_env.jj_cmd_ok(&repo_path, &["new"]);
961 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
962 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
963 test_env.jj_cmd_ok(&repo_path, &["squash"]);
964 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @"");
965
966 // If the destination's description is empty and the source's description is
967 // non-empty, the resulting description is from the source
968 test_env.jj_cmd_ok(&repo_path, &["undo"]);
969 test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "source"]);
970 test_env.jj_cmd_ok(&repo_path, &["squash"]);
971 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
972 source
973 "###);
974
975 // If the destination description is non-empty and the source's description is
976 // empty, the resulting description is from the destination
977 test_env.jj_cmd_ok(&repo_path, &["op", "restore", "@--"]);
978 test_env.jj_cmd_ok(&repo_path, &["describe", "@-", "-m", "destination"]);
979 test_env.jj_cmd_ok(&repo_path, &["squash"]);
980 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
981 destination
982 "###);
983
984 // An explicit description on the command-line overrides this
985 test_env.jj_cmd_ok(&repo_path, &["undo"]);
986 test_env.jj_cmd_ok(&repo_path, &["squash", "-m", "custom"]);
987 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
988 custom
989 "###);
990
991 // If both descriptions were non-empty, we get asked for a combined description
992 test_env.jj_cmd_ok(&repo_path, &["undo"]);
993 test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "source"]);
994 std::fs::write(&edit_script, "dump editor0").unwrap();
995 test_env.jj_cmd_ok(&repo_path, &["squash"]);
996 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
997 destination
998
999 source
1000 "###);
1001 insta::assert_snapshot!(
1002 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @r###"
1003 JJ: Enter a description for the combined commit.
1004 JJ: Description from the destination commit:
1005 destination
1006
1007 JJ: Description from source commit:
1008 source
1009
1010 JJ: Lines starting with "JJ: " (like this one) will be removed.
1011 "###);
1012
1013 // An explicit description on the command-line overrides prevents launching an
1014 // editor
1015 test_env.jj_cmd_ok(&repo_path, &["undo"]);
1016 test_env.jj_cmd_ok(&repo_path, &["squash", "-m", "custom"]);
1017 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
1018 custom
1019 "###);
1020
1021 // If the source's *content* doesn't become empty, then the source remains and
1022 // both descriptions are unchanged
1023 test_env.jj_cmd_ok(&repo_path, &["undo"]);
1024 test_env.jj_cmd_ok(&repo_path, &["squash", "file1"]);
1025 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
1026 destination
1027 "###);
1028 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@"), @r###"
1029 source
1030 "###);
1031}
1032
1033#[test]
1034fn test_squash_empty() {
1035 let mut test_env = TestEnvironment::default();
1036 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
1037 let repo_path = test_env.env_root().join("repo");
1038
1039 test_env.jj_cmd_ok(&repo_path, &["commit", "-m", "parent"]);
1040
1041 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash"]);
1042 insta::assert_snapshot!(stdout, @"");
1043 insta::assert_snapshot!(stderr, @r###"
1044 Working copy now at: kkmpptxz e45abe2c (empty) (no description set)
1045 Parent commit : qpvuntsm 1265289b (empty) parent
1046 "###);
1047 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
1048 parent
1049 "###);
1050
1051 test_env.jj_cmd_ok(&repo_path, &["describe", "-m", "child"]);
1052 test_env.set_up_fake_editor();
1053 test_env.jj_cmd_ok(&repo_path, &["squash"]);
1054 insta::assert_snapshot!(get_description(&test_env, &repo_path, "@-"), @r###"
1055 parent
1056
1057 child
1058 "###);
1059}
1060
1061#[test]
1062fn test_squash_use_destination_message() {
1063 let test_env = TestEnvironment::default();
1064 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
1065 let repo_path = test_env.env_root().join("repo");
1066
1067 test_env.jj_cmd_ok(&repo_path, &["commit", "-m=a"]);
1068 test_env.jj_cmd_ok(&repo_path, &["commit", "-m=b"]);
1069 test_env.jj_cmd_ok(&repo_path, &["describe", "-m=c"]);
1070 // Test the setup
1071 insta::assert_snapshot!(get_log_output_with_description(&test_env, &repo_path), @r###"
1072 @ 71f7c810d8ed c
1073 ◉ 10dd87c3b4e2 b
1074 ◉ 4c5b3042d9e0 a
1075 ◉ 000000000000
1076 "###);
1077
1078 // Squash the current revision using the short name for the option.
1079 test_env.jj_cmd_ok(&repo_path, &["squash", "-u"]);
1080 insta::assert_snapshot!(get_log_output_with_description(&test_env, &repo_path), @r###"
1081 @ 10e30ce4a910
1082 ◉ 1c21278b775f b
1083 ◉ 4c5b3042d9e0 a
1084 ◉ 000000000000
1085 "###);
1086
1087 // Undo and squash again, but this time squash both "b" and "c" into "a".
1088 test_env.jj_cmd_ok(&repo_path, &["undo"]);
1089 test_env.jj_cmd_ok(
1090 &repo_path,
1091 &[
1092 "squash",
1093 "--use-destination-message",
1094 "--from",
1095 "description(b)::",
1096 "--into",
1097 "description(a)",
1098 ],
1099 );
1100 insta::assert_snapshot!(get_log_output_with_description(&test_env, &repo_path), @r###"
1101 @ da1507508bdf
1102 ◉ f1387f804776 a
1103 ◉ 000000000000
1104 "###);
1105}
1106
1107// The --use-destination-message and --message options are incompatible.
1108#[test]
1109fn test_squash_use_destination_message_and_message_mutual_exclusion() {
1110 let test_env = TestEnvironment::default();
1111 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
1112 let repo_path = test_env.env_root().join("repo");
1113 test_env.jj_cmd_ok(&repo_path, &["commit", "-m=a"]);
1114 test_env.jj_cmd_ok(&repo_path, &["describe", "-m=b"]);
1115 insta::assert_snapshot!(test_env.jj_cmd_cli_error(
1116 &repo_path,
1117 &[
1118 "squash",
1119 "--message=123",
1120 "--use-destination-message",
1121 ],
1122 ), @r###"
1123 error: the argument '--message <MESSAGE>' cannot be used with '--use-destination-message'
1124
1125 Usage: jj squash --message <MESSAGE> [PATHS]...
1126
1127 For more information, try '--help'.
1128 "###);
1129}
1130
1131fn get_description(test_env: &TestEnvironment, repo_path: &Path, rev: &str) -> String {
1132 test_env.jj_cmd_success(
1133 repo_path,
1134 &["log", "--no-graph", "-T", "description", "-r", rev],
1135 )
1136}
1137
1138fn get_log_output_with_description(test_env: &TestEnvironment, repo_path: &Path) -> String {
1139 let template = r#"separate(" ", commit_id.short(), description)"#;
1140 test_env.jj_cmd_success(repo_path, &["log", "-T", template])
1141}