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 crate::common::create_commit;
16use crate::common::CommandOutput;
17use crate::common::TestEnvironment;
18use crate::common::TestWorkDir;
19
20#[test]
21fn test_rebase_invalid() {
22 let test_env = TestEnvironment::default();
23 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
24 let work_dir = test_env.work_dir("repo");
25
26 create_commit(&work_dir, "a", &[]);
27 create_commit(&work_dir, "b", &["a"]);
28
29 // Missing destination
30 let output = work_dir.run_jj(["rebase"]);
31 insta::assert_snapshot!(output, @r"
32 ------- stderr -------
33 error: the following required arguments were not provided:
34 <--destination <REVSETS>|--insert-after <REVSETS>|--insert-before <REVSETS>>
35
36 Usage: jj rebase <--destination <REVSETS>|--insert-after <REVSETS>|--insert-before <REVSETS>>
37
38 For more information, try '--help'.
39 [EOF]
40 [exit status: 2]
41 ");
42
43 // Both -r and -s
44 let output = work_dir.run_jj(["rebase", "-r", "a", "-s", "a", "-d", "b"]);
45 insta::assert_snapshot!(output, @r"
46 ------- stderr -------
47 error: the argument '--revisions <REVSETS>' cannot be used with '--source <REVSETS>'
48
49 Usage: jj rebase --revisions <REVSETS> <--destination <REVSETS>|--insert-after <REVSETS>|--insert-before <REVSETS>>
50
51 For more information, try '--help'.
52 [EOF]
53 [exit status: 2]
54 ");
55
56 // Both -b and -s
57 let output = work_dir.run_jj(["rebase", "-b", "a", "-s", "a", "-d", "b"]);
58 insta::assert_snapshot!(output, @r"
59 ------- stderr -------
60 error: the argument '--branch <REVSETS>' cannot be used with '--source <REVSETS>'
61
62 Usage: jj rebase --branch <REVSETS> <--destination <REVSETS>|--insert-after <REVSETS>|--insert-before <REVSETS>>
63
64 For more information, try '--help'.
65 [EOF]
66 [exit status: 2]
67 ");
68
69 // Both -d and --after
70 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "b", "--after", "b"]);
71 insta::assert_snapshot!(output, @r"
72 ------- stderr -------
73 error: the argument '--destination <REVSETS>' cannot be used with '--insert-after <REVSETS>'
74
75 Usage: jj rebase --revisions <REVSETS> <--destination <REVSETS>|--insert-after <REVSETS>|--insert-before <REVSETS>>
76
77 For more information, try '--help'.
78 [EOF]
79 [exit status: 2]
80 ");
81
82 // Both -d and --before
83 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "b", "--before", "b"]);
84 insta::assert_snapshot!(output, @r"
85 ------- stderr -------
86 error: the argument '--destination <REVSETS>' cannot be used with '--insert-before <REVSETS>'
87
88 Usage: jj rebase --revisions <REVSETS> <--destination <REVSETS>|--insert-after <REVSETS>|--insert-before <REVSETS>>
89
90 For more information, try '--help'.
91 [EOF]
92 [exit status: 2]
93 ");
94
95 // Rebase onto self with -r
96 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "a"]);
97 insta::assert_snapshot!(output, @r"
98 ------- stderr -------
99 Error: Cannot rebase 2443ea76b0b1 onto itself
100 [EOF]
101 [exit status: 1]
102 ");
103
104 // Rebase root with -r
105 let output = work_dir.run_jj(["rebase", "-r", "root()", "-d", "a"]);
106 insta::assert_snapshot!(output, @r"
107 ------- stderr -------
108 Error: The root commit 000000000000 is immutable
109 [EOF]
110 [exit status: 1]
111 ");
112
113 // Rebase onto descendant with -s
114 let output = work_dir.run_jj(["rebase", "-s", "a", "-d", "b"]);
115 insta::assert_snapshot!(output, @r"
116 ------- stderr -------
117 Error: Cannot rebase 2443ea76b0b1 onto descendant 1394f625cbbd
118 [EOF]
119 [exit status: 1]
120 ");
121}
122
123#[test]
124fn test_rebase_empty_sets() {
125 let test_env = TestEnvironment::default();
126 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
127 let work_dir = test_env.work_dir("repo");
128
129 create_commit(&work_dir, "a", &[]);
130 create_commit(&work_dir, "b", &["a"]);
131
132 // TODO: Make all of these say "Nothing changed"?
133 let output = work_dir.run_jj(["rebase", "-r=none()", "-d=b"]);
134 insta::assert_snapshot!(output, @r"
135 ------- stderr -------
136 Nothing changed.
137 [EOF]
138 ");
139 let output = work_dir.run_jj(["rebase", "-s=none()", "-d=b"]);
140 insta::assert_snapshot!(output, @r"
141 ------- stderr -------
142 Error: Revset `none()` didn't resolve to any revisions
143 [EOF]
144 [exit status: 1]
145 ");
146 let output = work_dir.run_jj(["rebase", "-b=none()", "-d=b"]);
147 insta::assert_snapshot!(output, @r"
148 ------- stderr -------
149 Error: Revset `none()` didn't resolve to any revisions
150 [EOF]
151 [exit status: 1]
152 ");
153 // Empty because "b..a" is empty
154 let output = work_dir.run_jj(["rebase", "-b=a", "-d=b"]);
155 insta::assert_snapshot!(output, @r"
156 ------- stderr -------
157 Nothing changed.
158 [EOF]
159 ");
160}
161
162#[test]
163fn test_rebase_bookmark() {
164 let test_env = TestEnvironment::default();
165 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
166 let work_dir = test_env.work_dir("repo");
167
168 create_commit(&work_dir, "a", &[]);
169 create_commit(&work_dir, "b", &["a"]);
170 create_commit(&work_dir, "c", &["b"]);
171 create_commit(&work_dir, "d", &["b"]);
172 create_commit(&work_dir, "e", &["a"]);
173 // Test the setup
174 insta::assert_snapshot!(get_log_output(&work_dir), @r"
175 @ e: a
176 │ ○ d: b
177 │ │ ○ c: b
178 │ ├─╯
179 │ ○ b: a
180 ├─╯
181 ○ a
182 ◆
183 [EOF]
184 ");
185
186 let output = work_dir.run_jj(["rebase", "-b", "c", "-d", "e"]);
187 insta::assert_snapshot!(output, @r"
188 ------- stderr -------
189 Rebased 3 commits onto destination
190 [EOF]
191 ");
192 insta::assert_snapshot!(get_log_output(&work_dir), @r"
193 ○ d: b
194 │ ○ c: b
195 ├─╯
196 ○ b: e
197 @ e: a
198 ○ a
199 ◆
200 [EOF]
201 ");
202
203 // Test rebasing multiple bookmarks at once
204 work_dir.run_jj(["undo"]).success();
205 let output = work_dir.run_jj(["rebase", "-b=e", "-b=d", "-d=b"]);
206 insta::assert_snapshot!(output, @r"
207 ------- stderr -------
208 Skipped rebase of 1 commits that were already in place
209 Rebased 1 commits onto destination
210 Working copy (@) now at: znkkpsqq 9ca2a154 e | e
211 Parent commit (@-) : zsuskuln 1394f625 b | b
212 Added 1 files, modified 0 files, removed 0 files
213 [EOF]
214 ");
215 insta::assert_snapshot!(get_log_output(&work_dir), @r"
216 @ e: b
217 │ ○ d: b
218 ├─╯
219 │ ○ c: b
220 ├─╯
221 ○ b: a
222 ○ a
223 ◆
224 [EOF]
225 ");
226
227 // Same test but with more than one revision per argument
228 work_dir.run_jj(["undo"]).success();
229 let output = work_dir.run_jj(["rebase", "-b=e|d", "-d=b"]);
230 insta::assert_snapshot!(output, @r"
231 ------- stderr -------
232 Error: Revset `e|d` resolved to more than one revision
233 Hint: The revset `e|d` resolved to these revisions:
234 znkkpsqq e52756c8 e | e
235 vruxwmqv 514fa6b2 d | d
236 Hint: Prefix the expression with `all:` to allow any number of revisions (i.e. `all:e|d`).
237 [EOF]
238 [exit status: 1]
239 ");
240 let output = work_dir.run_jj(["rebase", "-b=all:e|d", "-d=b"]);
241 insta::assert_snapshot!(output, @r"
242 ------- stderr -------
243 Skipped rebase of 1 commits that were already in place
244 Rebased 1 commits onto destination
245 Working copy (@) now at: znkkpsqq 817e3fb0 e | e
246 Parent commit (@-) : zsuskuln 1394f625 b | b
247 Added 1 files, modified 0 files, removed 0 files
248 [EOF]
249 ");
250 insta::assert_snapshot!(get_log_output(&work_dir), @r"
251 @ e: b
252 │ ○ d: b
253 ├─╯
254 │ ○ c: b
255 ├─╯
256 ○ b: a
257 ○ a
258 ◆
259 [EOF]
260 ");
261}
262
263#[test]
264fn test_rebase_bookmark_with_merge() {
265 let test_env = TestEnvironment::default();
266 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
267 let work_dir = test_env.work_dir("repo");
268
269 create_commit(&work_dir, "a", &[]);
270 create_commit(&work_dir, "b", &["a"]);
271 create_commit(&work_dir, "c", &[]);
272 create_commit(&work_dir, "d", &["c"]);
273 create_commit(&work_dir, "e", &["a", "d"]);
274 // Test the setup
275 insta::assert_snapshot!(get_log_output(&work_dir), @r"
276 @ e: a d
277 ├─╮
278 │ ○ d: c
279 │ ○ c
280 │ │ ○ b: a
281 ├───╯
282 ○ │ a
283 ├─╯
284 ◆
285 [EOF]
286 ");
287
288 let output = work_dir.run_jj(["rebase", "-b", "d", "-d", "b"]);
289 insta::assert_snapshot!(output, @r"
290 ------- stderr -------
291 Rebased 3 commits onto destination
292 Working copy (@) now at: znkkpsqq 5f8a3db2 e | e
293 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
294 Parent commit (@-) : vruxwmqv 1677f795 d | d
295 Added 1 files, modified 0 files, removed 0 files
296 [EOF]
297 ");
298 insta::assert_snapshot!(get_log_output(&work_dir), @r"
299 @ e: a d
300 ├─╮
301 │ ○ d: c
302 │ ○ c: b
303 │ ○ b: a
304 ├─╯
305 ○ a
306 ◆
307 [EOF]
308 ");
309
310 work_dir.run_jj(["undo"]).success();
311 let output = work_dir.run_jj(["rebase", "-d", "b"]);
312 insta::assert_snapshot!(output, @r"
313 ------- stderr -------
314 Rebased 3 commits onto destination
315 Working copy (@) now at: znkkpsqq a331ac11 e | e
316 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
317 Parent commit (@-) : vruxwmqv 3d0f3644 d | d
318 Added 1 files, modified 0 files, removed 0 files
319 [EOF]
320 ");
321 insta::assert_snapshot!(get_log_output(&work_dir), @r"
322 @ e: a d
323 ├─╮
324 │ ○ d: c
325 │ ○ c: b
326 │ ○ b: a
327 ├─╯
328 ○ a
329 ◆
330 [EOF]
331 ");
332}
333
334#[test]
335fn test_rebase_single_revision() {
336 let test_env = TestEnvironment::default();
337 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
338 let work_dir = test_env.work_dir("repo");
339
340 create_commit(&work_dir, "a", &[]);
341 create_commit(&work_dir, "b", &["a"]);
342 create_commit(&work_dir, "c", &["a"]);
343 create_commit(&work_dir, "d", &["b", "c"]);
344 create_commit(&work_dir, "e", &["d"]);
345 // Test the setup
346 insta::assert_snapshot!(get_log_output(&work_dir), @r"
347 @ e: d
348 ○ d: b c
349 ├─╮
350 │ ○ c: a
351 ○ │ b: a
352 ├─╯
353 ○ a
354 ◆
355 [EOF]
356 ");
357
358 // Descendants of the rebased commit "c" should be rebased onto parents. First
359 // we test with a non-merge commit.
360 let output = work_dir.run_jj(["rebase", "-r", "c", "-d", "b"]);
361 insta::assert_snapshot!(output, @r"
362 ------- stderr -------
363 Rebased 1 commits onto destination
364 Rebased 2 descendant commits
365 Working copy (@) now at: znkkpsqq 2668ffbe e | e
366 Parent commit (@-) : vruxwmqv 7b370c85 d | d
367 Added 0 files, modified 0 files, removed 1 files
368 [EOF]
369 ");
370 insta::assert_snapshot!(get_log_output(&work_dir), @r"
371 @ e: d
372 ○ d: b a
373 ├─╮
374 │ │ ○ c: b
375 ├───╯
376 ○ │ b: a
377 ├─╯
378 ○ a
379 ◆
380 [EOF]
381 ");
382 work_dir.run_jj(["undo"]).success();
383
384 // Now, let's try moving the merge commit. After, both parents of "d" ("b" and
385 // "c") should become parents of "e".
386 let output = work_dir.run_jj(["rebase", "-r", "d", "-d", "a"]);
387 insta::assert_snapshot!(output, @r"
388 ------- stderr -------
389 Rebased 1 commits onto destination
390 Rebased 1 descendant commits
391 Working copy (@) now at: znkkpsqq ed210c15 e | e
392 Parent commit (@-) : zsuskuln 1394f625 b | b
393 Parent commit (@-) : royxmykx c0cb3a0b c | c
394 Added 0 files, modified 0 files, removed 1 files
395 [EOF]
396 ");
397 insta::assert_snapshot!(get_log_output(&work_dir), @r"
398 @ e: b c
399 ├─╮
400 │ ○ c: a
401 ○ │ b: a
402 ├─╯
403 │ ○ d: a
404 ├─╯
405 ○ a
406 ◆
407 [EOF]
408 ");
409}
410
411#[test]
412fn test_rebase_single_revision_merge_parent() {
413 let test_env = TestEnvironment::default();
414 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
415 let work_dir = test_env.work_dir("repo");
416
417 create_commit(&work_dir, "a", &[]);
418 create_commit(&work_dir, "b", &[]);
419 create_commit(&work_dir, "c", &["b"]);
420 create_commit(&work_dir, "d", &["a", "c"]);
421 // Test the setup
422 insta::assert_snapshot!(get_log_output(&work_dir), @r"
423 @ d: a c
424 ├─╮
425 │ ○ c: b
426 │ ○ b
427 ○ │ a
428 ├─╯
429 ◆
430 [EOF]
431 ");
432
433 // Descendants of the rebased commit should be rebased onto parents, and if
434 // the descendant is a merge commit, it shouldn't forget its other parents.
435 let output = work_dir.run_jj(["rebase", "-r", "c", "-d", "a"]);
436 insta::assert_snapshot!(output, @r"
437 ------- stderr -------
438 Rebased 1 commits onto destination
439 Rebased 1 descendant commits
440 Working copy (@) now at: vruxwmqv a37531e8 d | d
441 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
442 Parent commit (@-) : zsuskuln d370aee1 b | b
443 Added 0 files, modified 0 files, removed 1 files
444 [EOF]
445 ");
446 insta::assert_snapshot!(get_log_output(&work_dir), @r"
447 @ d: a b
448 ├─╮
449 │ ○ b
450 │ │ ○ c: a
451 ├───╯
452 ○ │ a
453 ├─╯
454 ◆
455 [EOF]
456 ");
457}
458
459#[test]
460fn test_rebase_multiple_revisions() {
461 let test_env = TestEnvironment::default();
462 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
463 let work_dir = test_env.work_dir("repo");
464
465 create_commit(&work_dir, "a", &[]);
466 create_commit(&work_dir, "b", &["a"]);
467 create_commit(&work_dir, "c", &["b"]);
468 create_commit(&work_dir, "d", &["a"]);
469 create_commit(&work_dir, "e", &["d"]);
470 create_commit(&work_dir, "f", &["c", "e"]);
471 create_commit(&work_dir, "g", &["f"]);
472 create_commit(&work_dir, "h", &["g"]);
473 create_commit(&work_dir, "i", &["f"]);
474 // Test the setup
475 insta::assert_snapshot!(get_log_output(&work_dir), @r"
476 @ i: f
477 │ ○ h: g
478 │ ○ g: f
479 ├─╯
480 ○ f: c e
481 ├─╮
482 │ ○ e: d
483 │ ○ d: a
484 ○ │ c: b
485 ○ │ b: a
486 ├─╯
487 ○ a
488 ◆
489 [EOF]
490 ");
491
492 // Test with two non-related non-merge commits.
493 let output = work_dir.run_jj(["rebase", "-r", "c", "-r", "e", "-d", "a"]);
494 insta::assert_snapshot!(output, @r"
495 ------- stderr -------
496 Rebased 2 commits onto destination
497 Rebased 4 descendant commits
498 Working copy (@) now at: xznxytkn 016685dc i | i
499 Parent commit (@-) : kmkuslsw e04d3932 f | f
500 Added 0 files, modified 0 files, removed 2 files
501 [EOF]
502 ");
503 insta::assert_snapshot!(get_log_output(&work_dir), @r"
504 @ i: f
505 │ ○ h: g
506 │ ○ g: f
507 ├─╯
508 ○ f: b d
509 ├─╮
510 │ ○ d: a
511 ○ │ b: a
512 ├─╯
513 │ ○ e: a
514 ├─╯
515 │ ○ c: a
516 ├─╯
517 ○ a
518 ◆
519 [EOF]
520 ");
521 work_dir.run_jj(["undo"]).success();
522
523 // Test with two related non-merge commits. Since "b" is a parent of "c", when
524 // rebasing commits "b" and "c", their ancestry relationship should be
525 // preserved.
526 let output = work_dir.run_jj(["rebase", "-r", "b", "-r", "c", "-d", "e"]);
527 insta::assert_snapshot!(output, @r"
528 ------- stderr -------
529 Rebased 2 commits onto destination
530 Rebased 4 descendant commits
531 Working copy (@) now at: xznxytkn 94538385 i | i
532 Parent commit (@-) : kmkuslsw dae8d293 f | f
533 Added 0 files, modified 0 files, removed 2 files
534 [EOF]
535 ");
536 insta::assert_snapshot!(get_log_output(&work_dir), @r"
537 @ i: f
538 │ ○ h: g
539 │ ○ g: f
540 ├─╯
541 ○ f: a e
542 ├─╮
543 │ │ ○ c: b
544 │ │ ○ b: e
545 │ ├─╯
546 │ ○ e: d
547 │ ○ d: a
548 ├─╯
549 ○ a
550 ◆
551 [EOF]
552 ");
553 work_dir.run_jj(["undo"]).success();
554
555 // Test with a subgraph containing a merge commit. Since the merge commit "f"
556 // was extracted, its descendants which are not part of the subgraph will
557 // inherit its descendants which are not in the subtree ("c" and "d").
558 // "f" will retain its parent "c" since "c" is outside the target set, and not
559 // a descendant of any new children.
560 let output = work_dir.run_jj(["rebase", "-r", "e::g", "-d", "a"]);
561 insta::assert_snapshot!(output, @r"
562 ------- stderr -------
563 Rebased 3 commits onto destination
564 Rebased 2 descendant commits
565 Working copy (@) now at: xznxytkn 1868ded4 i | i
566 Parent commit (@-) : royxmykx 7e4fbf4f c | c
567 Parent commit (@-) : vruxwmqv 4cc44fbf d | d
568 Added 0 files, modified 0 files, removed 2 files
569 [EOF]
570 ");
571 insta::assert_snapshot!(get_log_output(&work_dir), @r"
572 @ i: c d
573 ├─╮
574 │ │ ○ h: c d
575 ╭─┬─╯
576 │ ○ d: a
577 │ │ ○ g: f
578 │ │ ○ f: c e
579 ╭───┤
580 │ │ ○ e: a
581 │ ├─╯
582 ○ │ c: b
583 ○ │ b: a
584 ├─╯
585 ○ a
586 ◆
587 [EOF]
588 ");
589 work_dir.run_jj(["undo"]).success();
590
591 // Test with commits in a disconnected subgraph. The subgraph has the
592 // relationship d->e->f->g->h, but only "d", "f" and "h" are in the set of
593 // rebased commits. "d" should be a new parent of "f", and "f" should be a
594 // new parent of "h". "f" will retain its parent "c" since "c" is outside the
595 // target set, and not a descendant of any new children.
596 let output = work_dir.run_jj(["rebase", "-r", "d", "-r", "f", "-r", "h", "-d", "b"]);
597 insta::assert_snapshot!(output, @r"
598 ------- stderr -------
599 Rebased 3 commits onto destination
600 Rebased 3 descendant commits
601 Working copy (@) now at: xznxytkn 9cfd1635 i | i
602 Parent commit (@-) : royxmykx 7e4fbf4f c | c
603 Parent commit (@-) : znkkpsqq ecf9a1d5 e | e
604 Added 0 files, modified 0 files, removed 2 files
605 [EOF]
606 ");
607 insta::assert_snapshot!(get_log_output(&work_dir), @r"
608 @ i: c e
609 ├─╮
610 │ │ ○ g: c e
611 ╭─┬─╯
612 │ ○ e: a
613 │ │ ○ h: f
614 │ │ ○ f: c d
615 ╭───┤
616 │ │ ○ d: b
617 ○ │ │ c: b
618 ├───╯
619 ○ │ b: a
620 ├─╯
621 ○ a
622 ◆
623 [EOF]
624 ");
625 work_dir.run_jj(["undo"]).success();
626
627 // Test rebasing a subgraph onto its descendants.
628 let output = work_dir.run_jj(["rebase", "-r", "d::e", "-d", "i"]);
629 insta::assert_snapshot!(output, @r"
630 ------- stderr -------
631 Rebased 2 commits onto destination
632 Rebased 4 descendant commits
633 Working copy (@) now at: xznxytkn 5d911e5c i | i
634 Parent commit (@-) : kmkuslsw d1bfda8c f | f
635 Added 0 files, modified 0 files, removed 2 files
636 [EOF]
637 ");
638 insta::assert_snapshot!(get_log_output(&work_dir), @r"
639 ○ e: d
640 ○ d: i
641 @ i: f
642 │ ○ h: g
643 │ ○ g: f
644 ├─╯
645 ○ f: c a
646 ├─╮
647 ○ │ c: b
648 ○ │ b: a
649 ├─╯
650 ○ a
651 ◆
652 [EOF]
653 ");
654}
655
656#[test]
657fn test_rebase_revision_onto_descendant() {
658 let test_env = TestEnvironment::default();
659 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
660 let work_dir = test_env.work_dir("repo");
661
662 create_commit(&work_dir, "base", &[]);
663 create_commit(&work_dir, "a", &["base"]);
664 create_commit(&work_dir, "b", &["base"]);
665 create_commit(&work_dir, "merge", &["b", "a"]);
666 // Test the setup
667 insta::assert_snapshot!(get_log_output(&work_dir), @r"
668 @ merge: b a
669 ├─╮
670 │ ○ a: base
671 ○ │ b: base
672 ├─╯
673 ○ base
674 ◆
675 [EOF]
676 ");
677 let setup_opid = work_dir.current_operation_id();
678
679 // Simpler example
680 let output = work_dir.run_jj(["rebase", "-r", "base", "-d", "a"]);
681 insta::assert_snapshot!(output, @r"
682 ------- stderr -------
683 Rebased 1 commits onto destination
684 Rebased 3 descendant commits
685 Working copy (@) now at: vruxwmqv bff4a4eb merge | merge
686 Parent commit (@-) : royxmykx c84e900d b | b
687 Parent commit (@-) : zsuskuln d57db87b a | a
688 Added 0 files, modified 0 files, removed 1 files
689 [EOF]
690 ");
691 insta::assert_snapshot!(get_log_output(&work_dir), @r"
692 @ merge: b a
693 ├─╮
694 ○ │ b
695 │ │ ○ base: a
696 │ ├─╯
697 │ ○ a
698 ├─╯
699 ◆
700 [EOF]
701 ");
702
703 // Now, let's rebase onto the descendant merge
704 let output = work_dir.run_jj(["op", "restore", &setup_opid]);
705 insta::assert_snapshot!(output, @r"
706 ------- stderr -------
707 Restored to operation: 60e340690be6 (2001-02-03 08:05:15) create bookmark merge pointing to commit b05964d109522cd06e48f1a2661e1a0f58be0984
708 Working copy (@) now at: vruxwmqv b05964d1 merge | merge
709 Parent commit (@-) : royxmykx cea87a87 b | b
710 Parent commit (@-) : zsuskuln 2c5b7858 a | a
711 Added 1 files, modified 0 files, removed 0 files
712 [EOF]
713 ");
714 let output = work_dir.run_jj(["rebase", "-r", "base", "-d", "merge"]);
715 insta::assert_snapshot!(output, @r"
716 ------- stderr -------
717 Rebased 1 commits onto destination
718 Rebased 3 descendant commits
719 Working copy (@) now at: vruxwmqv 986b7a49 merge | merge
720 Parent commit (@-) : royxmykx c07c677c b | b
721 Parent commit (@-) : zsuskuln abc90087 a | a
722 Added 0 files, modified 0 files, removed 1 files
723 [EOF]
724 ");
725 insta::assert_snapshot!(get_log_output(&work_dir), @r"
726 ○ base: merge
727 @ merge: b a
728 ├─╮
729 │ ○ a
730 ○ │ b
731 ├─╯
732 ◆
733 [EOF]
734 ");
735
736 // TODO(ilyagr): These will be good tests for `jj rebase --insert-after` and
737 // `--insert-before`, once those are implemented.
738}
739
740#[test]
741fn test_rebase_multiple_destinations() {
742 let test_env = TestEnvironment::default();
743 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
744 let work_dir = test_env.work_dir("repo");
745
746 create_commit(&work_dir, "a", &[]);
747 create_commit(&work_dir, "b", &[]);
748 create_commit(&work_dir, "c", &[]);
749 // Test the setup
750 insta::assert_snapshot!(get_log_output(&work_dir), @r"
751 @ c
752 │ ○ b
753 ├─╯
754 │ ○ a
755 ├─╯
756 ◆
757 [EOF]
758 ");
759
760 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "b", "-d", "c"]);
761 insta::assert_snapshot!(output, @r"
762 ------- stderr -------
763 Rebased 1 commits onto destination
764 [EOF]
765 ");
766 insta::assert_snapshot!(get_log_output(&work_dir), @r"
767 ○ a: b c
768 ├─╮
769 │ @ c
770 ○ │ b
771 ├─╯
772 ◆
773 [EOF]
774 ");
775
776 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "b|c"]);
777 insta::assert_snapshot!(output, @r"
778 ------- stderr -------
779 Error: Revset `b|c` resolved to more than one revision
780 Hint: The revset `b|c` resolved to these revisions:
781 royxmykx fe2e8e8b c | c
782 zsuskuln d370aee1 b | b
783 Hint: Prefix the expression with `all:` to allow any number of revisions (i.e. `all:b|c`).
784 [EOF]
785 [exit status: 1]
786 ");
787
788 // try with 'all:' and succeed
789 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "all:b|c"]);
790 insta::assert_snapshot!(output, @r"
791 ------- stderr -------
792 Rebased 1 commits onto destination
793 [EOF]
794 ");
795 insta::assert_snapshot!(get_log_output(&work_dir), @r"
796 ○ a: c b
797 ├─╮
798 │ ○ b
799 @ │ c
800 ├─╯
801 ◆
802 [EOF]
803 ");
804
805 // undo and do it again, but with 'ui.always-allow-large-revsets'
806 work_dir.run_jj(["undo"]).success();
807 work_dir
808 .run_jj([
809 "rebase",
810 "--config=ui.always-allow-large-revsets=true",
811 "-r=a",
812 "-d=b|c",
813 ])
814 .success();
815 insta::assert_snapshot!(get_log_output(&work_dir), @r"
816 ○ a: c b
817 ├─╮
818 │ ○ b
819 @ │ c
820 ├─╯
821 ◆
822 [EOF]
823 ");
824
825 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "b", "-d", "b"]);
826 insta::assert_snapshot!(output, @r"
827 ------- stderr -------
828 Error: More than one revset resolved to revision d370aee184ba
829 [EOF]
830 [exit status: 1]
831 ");
832
833 // Same error with 'all:' if there is overlap.
834 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "all:b|c", "-d", "b"]);
835 insta::assert_snapshot!(output, @r"
836 ------- stderr -------
837 Error: More than one revset resolved to revision d370aee184ba
838 [EOF]
839 [exit status: 1]
840 ");
841
842 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "b", "-d", "root()"]);
843 insta::assert_snapshot!(output, @r"
844 ------- stderr -------
845 Error: The Git backend does not support creating merge commits with the root commit as one of the parents.
846 [EOF]
847 [exit status: 1]
848 ");
849}
850
851#[test]
852fn test_rebase_with_descendants() {
853 let test_env = TestEnvironment::default();
854 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
855 let work_dir = test_env.work_dir("repo");
856
857 create_commit(&work_dir, "a", &[]);
858 create_commit(&work_dir, "b", &[]);
859 create_commit(&work_dir, "c", &["a", "b"]);
860 create_commit(&work_dir, "d", &["c"]);
861 // Test the setup
862 insta::assert_snapshot!(get_log_output(&work_dir), @r"
863 @ d: c
864 ○ c: a b
865 ├─╮
866 │ ○ b
867 ○ │ a
868 ├─╯
869 ◆
870 [EOF]
871 ");
872
873 let output = work_dir.run_jj(["rebase", "-s", "b", "-d", "a"]);
874 insta::assert_snapshot!(output, @r"
875 ------- stderr -------
876 Rebased 3 commits onto destination
877 Working copy (@) now at: vruxwmqv 705832bd d | d
878 Parent commit (@-) : royxmykx 57c7246a c | c
879 [EOF]
880 ");
881 insta::assert_snapshot!(get_log_output(&work_dir), @r"
882 @ d: c
883 ○ c: a b
884 ├─╮
885 │ ○ b: a
886 ├─╯
887 ○ a
888 ◆
889 [EOF]
890 ");
891
892 // Rebase several subtrees at once.
893 work_dir.run_jj(["undo"]).success();
894 let output = work_dir.run_jj(["rebase", "-s=c", "-s=d", "-d=a"]);
895 insta::assert_snapshot!(output, @r"
896 ------- stderr -------
897 Rebased 2 commits onto destination
898 Working copy (@) now at: vruxwmqv 92c2bc9a d | d
899 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
900 Added 0 files, modified 0 files, removed 2 files
901 [EOF]
902 ");
903 insta::assert_snapshot!(get_log_output(&work_dir), @r"
904 @ d: a
905 │ ○ c: a
906 ├─╯
907 ○ a
908 │ ○ b
909 ├─╯
910 ◆
911 [EOF]
912 ");
913
914 work_dir.run_jj(["undo"]).success();
915 // Reminder of the setup
916 insta::assert_snapshot!(get_log_output(&work_dir), @r"
917 @ d: c
918 ○ c: a b
919 ├─╮
920 │ ○ b
921 ○ │ a
922 ├─╯
923 ◆
924 [EOF]
925 ");
926
927 // `d` was a descendant of `b`, and both are moved to be direct descendants of
928 // `a`. `c` remains a descendant of `b`.
929 let output = work_dir.run_jj(["rebase", "-s=b", "-s=d", "-d=a"]);
930 insta::assert_snapshot!(output, @r"
931 ------- stderr -------
932 Rebased 3 commits onto destination
933 Working copy (@) now at: vruxwmqv f1e71cb7 d | d
934 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
935 Added 0 files, modified 0 files, removed 2 files
936 [EOF]
937 ");
938 insta::assert_snapshot!(get_log_output(&work_dir), @r"
939 @ d: a
940 │ ○ c: a b
941 ╭─┤
942 │ ○ b: a
943 ├─╯
944 ○ a
945 ◆
946 [EOF]
947 ");
948
949 // Same test as above, but with multiple commits per argument
950 work_dir.run_jj(["undo"]).success();
951 let output = work_dir.run_jj(["rebase", "-s=b|d", "-d=a"]);
952 insta::assert_snapshot!(output, @r"
953 ------- stderr -------
954 Error: Revset `b|d` resolved to more than one revision
955 Hint: The revset `b|d` resolved to these revisions:
956 vruxwmqv df54a9fd d | d
957 zsuskuln d370aee1 b | b
958 Hint: Prefix the expression with `all:` to allow any number of revisions (i.e. `all:b|d`).
959 [EOF]
960 [exit status: 1]
961 ");
962 let output = work_dir.run_jj(["rebase", "-s=all:b|d", "-d=a"]);
963 insta::assert_snapshot!(output, @r"
964 ------- stderr -------
965 Rebased 3 commits onto destination
966 Working copy (@) now at: vruxwmqv d17539f7 d | d
967 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
968 Added 0 files, modified 0 files, removed 2 files
969 [EOF]
970 ");
971 insta::assert_snapshot!(get_log_output(&work_dir), @r"
972 @ d: a
973 │ ○ c: a b
974 ╭─┤
975 │ ○ b: a
976 ├─╯
977 ○ a
978 ◆
979 [EOF]
980 ");
981}
982
983#[test]
984fn test_rebase_error_revision_does_not_exist() {
985 let test_env = TestEnvironment::default();
986 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
987 let work_dir = test_env.work_dir("repo");
988
989 work_dir.run_jj(["describe", "-m", "one"]).success();
990 work_dir
991 .run_jj(["bookmark", "create", "-r@", "b-one"])
992 .success();
993 work_dir.run_jj(["new", "-r", "@-", "-m", "two"]).success();
994
995 let output = work_dir.run_jj(["rebase", "-b", "b-one", "-d", "this"]);
996 insta::assert_snapshot!(output, @r"
997 ------- stderr -------
998 Error: Revision `this` doesn't exist
999 [EOF]
1000 [exit status: 1]
1001 ");
1002
1003 let output = work_dir.run_jj(["rebase", "-b", "this", "-d", "b-one"]);
1004 insta::assert_snapshot!(output, @r"
1005 ------- stderr -------
1006 Error: Revision `this` doesn't exist
1007 [EOF]
1008 [exit status: 1]
1009 ");
1010}
1011
1012// This behavior illustrates https://github.com/jj-vcs/jj/issues/2600
1013#[test]
1014fn test_rebase_with_child_and_descendant_bug_2600() {
1015 let test_env = TestEnvironment::default();
1016 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1017 let work_dir = test_env.work_dir("repo");
1018
1019 create_commit(&work_dir, "notroot", &[]);
1020 create_commit(&work_dir, "base", &["notroot"]);
1021 create_commit(&work_dir, "a", &["base"]);
1022 create_commit(&work_dir, "b", &["base", "a"]);
1023 create_commit(&work_dir, "c", &["b"]);
1024 let setup_opid = work_dir.current_operation_id();
1025
1026 // Test the setup
1027 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1028 @ c: b
1029 ○ b: base a
1030 ├─╮
1031 │ ○ a: base
1032 ├─╯
1033 ○ base: notroot
1034 ○ notroot
1035 ◆
1036 [EOF]
1037 ");
1038
1039 // ===================== rebase -s tests =================
1040 // This should be a no-op
1041 let output = work_dir.run_jj(["rebase", "-s", "base", "-d", "notroot"]);
1042 insta::assert_snapshot!(output, @r"
1043 ------- stderr -------
1044 Skipped rebase of 4 commits that were already in place
1045 Nothing changed.
1046 [EOF]
1047 ");
1048 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1049 @ c: b
1050 ○ b: base a
1051 ├─╮
1052 │ ○ a: base
1053 ├─╯
1054 ○ base: notroot
1055 ○ notroot
1056 ◆
1057 [EOF]
1058 ");
1059
1060 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1061 // This should be a no-op
1062 let output = work_dir.run_jj(["rebase", "-s", "a", "-d", "base"]);
1063 insta::assert_snapshot!(output, @r"
1064 ------- stderr -------
1065 Skipped rebase of 3 commits that were already in place
1066 Nothing changed.
1067 [EOF]
1068 ");
1069 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1070 @ c: b
1071 ○ b: base a
1072 ├─╮
1073 │ ○ a: base
1074 ├─╯
1075 ○ base: notroot
1076 ○ notroot
1077 ◆
1078 [EOF]
1079 ");
1080
1081 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1082 let output = work_dir.run_jj(["rebase", "-s", "a", "-d", "root()"]);
1083 insta::assert_snapshot!(output, @r"
1084 ------- stderr -------
1085 Rebased 3 commits onto destination
1086 Working copy (@) now at: znkkpsqq cf8ecff5 c | c
1087 Parent commit (@-) : vruxwmqv 24e1a270 b | b
1088 [EOF]
1089 ");
1090 // Commit "a" should be rebased onto the root commit. Commit "b" should have
1091 // "base" and "a" as parents as before.
1092 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1093 @ c: b
1094 ○ b: base a
1095 ├─╮
1096 │ ○ a
1097 ○ │ base: notroot
1098 ○ │ notroot
1099 ├─╯
1100 ◆
1101 [EOF]
1102 ");
1103
1104 // ===================== rebase -b tests =================
1105 // ====== Reminder of the setup =========
1106 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1107 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1108 @ c: b
1109 ○ b: base a
1110 ├─╮
1111 │ ○ a: base
1112 ├─╯
1113 ○ base: notroot
1114 ○ notroot
1115 ◆
1116 [EOF]
1117 ");
1118
1119 // The commits in roots(base..c), i.e. commit "a" should be rebased onto "base",
1120 // which is a no-op
1121 let output = work_dir.run_jj(["rebase", "-b", "c", "-d", "base"]);
1122 insta::assert_snapshot!(output, @r"
1123 ------- stderr -------
1124 Skipped rebase of 3 commits that were already in place
1125 Nothing changed.
1126 [EOF]
1127 ");
1128 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1129 @ c: b
1130 ○ b: base a
1131 ├─╮
1132 │ ○ a: base
1133 ├─╯
1134 ○ base: notroot
1135 ○ notroot
1136 ◆
1137 [EOF]
1138 ");
1139
1140 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1141 let output = work_dir.run_jj(["rebase", "-b", "c", "-d", "a"]);
1142 insta::assert_snapshot!(output, @r"
1143 ------- stderr -------
1144 Rebased 2 commits onto destination
1145 Working copy (@) now at: znkkpsqq 76914dcc c | c
1146 Parent commit (@-) : vruxwmqv f73f03c7 b | b
1147 [EOF]
1148 ");
1149 // The commits in roots(a..c), i.e. commit "b" should be rebased onto "a",
1150 // which means "b" loses its "base" parent
1151 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1152 @ c: b
1153 ○ b: a
1154 ○ a: base
1155 ○ base: notroot
1156 ○ notroot
1157 ◆
1158 [EOF]
1159 ");
1160
1161 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1162 // This should be a no-op
1163 let output = work_dir.run_jj(["rebase", "-b", "a", "-d", "root()"]);
1164 insta::assert_snapshot!(output, @r"
1165 ------- stderr -------
1166 Skipped rebase of 5 commits that were already in place
1167 Nothing changed.
1168 [EOF]
1169 ");
1170 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1171 @ c: b
1172 ○ b: base a
1173 ├─╮
1174 │ ○ a: base
1175 ├─╯
1176 ○ base: notroot
1177 ○ notroot
1178 ◆
1179 [EOF]
1180 ");
1181
1182 // ===================== rebase -r tests =================
1183 // ====== Reminder of the setup =========
1184 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1185 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1186 @ c: b
1187 ○ b: base a
1188 ├─╮
1189 │ ○ a: base
1190 ├─╯
1191 ○ base: notroot
1192 ○ notroot
1193 ◆
1194 [EOF]
1195 ");
1196
1197 let output = work_dir.run_jj(["rebase", "-r", "base", "-d", "root()"]);
1198 insta::assert_snapshot!(output, @r"
1199 ------- stderr -------
1200 Rebased 1 commits onto destination
1201 Rebased 3 descendant commits
1202 Working copy (@) now at: znkkpsqq 45371aaf c | c
1203 Parent commit (@-) : vruxwmqv c0a76bf4 b | b
1204 Added 0 files, modified 0 files, removed 1 files
1205 [EOF]
1206 ");
1207 // The user would expect unsimplified ancestry here.
1208 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1209 @ c: b
1210 ○ b: notroot a
1211 ├─╮
1212 │ ○ a: notroot
1213 ├─╯
1214 ○ notroot
1215 │ ○ base
1216 ├─╯
1217 ◆
1218 [EOF]
1219 ");
1220
1221 // This tests the algorithm for rebasing onto descendants. The result should
1222 // have unsimplified ancestry.
1223 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1224 let output = work_dir.run_jj(["rebase", "-r", "base", "-d", "b"]);
1225 insta::assert_snapshot!(output, @r"
1226 ------- stderr -------
1227 Rebased 1 commits onto destination
1228 Rebased 3 descendant commits
1229 Working copy (@) now at: znkkpsqq e28fa972 c | c
1230 Parent commit (@-) : vruxwmqv 8d0eeb6a b | b
1231 Added 0 files, modified 0 files, removed 1 files
1232 [EOF]
1233 ");
1234 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1235 @ c: b
1236 │ ○ base: b
1237 ├─╯
1238 ○ b: notroot a
1239 ├─╮
1240 │ ○ a: notroot
1241 ├─╯
1242 ○ notroot
1243 ◆
1244 [EOF]
1245 ");
1246
1247 // This tests the algorithm for rebasing onto descendants. The result should
1248 // have unsimplified ancestry.
1249 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1250 let output = work_dir.run_jj(["rebase", "-r", "base", "-d", "a"]);
1251 insta::assert_snapshot!(output, @r"
1252 ------- stderr -------
1253 Rebased 1 commits onto destination
1254 Rebased 3 descendant commits
1255 Working copy (@) now at: znkkpsqq a9da974c c | c
1256 Parent commit (@-) : vruxwmqv 0072139c b | b
1257 Added 0 files, modified 0 files, removed 1 files
1258 [EOF]
1259 ");
1260 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1261 @ c: b
1262 ○ b: notroot a
1263 ├─╮
1264 │ │ ○ base: a
1265 │ ├─╯
1266 │ ○ a: notroot
1267 ├─╯
1268 ○ notroot
1269 ◆
1270 [EOF]
1271 ");
1272
1273 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1274 // ====== Reminder of the setup =========
1275 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1276 @ c: b
1277 ○ b: base a
1278 ├─╮
1279 │ ○ a: base
1280 ├─╯
1281 ○ base: notroot
1282 ○ notroot
1283 ◆
1284 [EOF]
1285 ");
1286
1287 let output = work_dir.run_jj(["rebase", "-r", "a", "-d", "root()"]);
1288 insta::assert_snapshot!(output, @r"
1289 ------- stderr -------
1290 Rebased 1 commits onto destination
1291 Rebased 2 descendant commits
1292 Working copy (@) now at: znkkpsqq 7210b05e c | c
1293 Parent commit (@-) : vruxwmqv da3f7511 b | b
1294 Added 0 files, modified 0 files, removed 1 files
1295 [EOF]
1296 ");
1297 // In this case, it is unclear whether the user would always prefer unsimplified
1298 // ancestry (whether `b` should also be a direct child of the root commit).
1299 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1300 @ c: b
1301 ○ b: base
1302 ○ base: notroot
1303 ○ notroot
1304 │ ○ a
1305 ├─╯
1306 ◆
1307 [EOF]
1308 ");
1309
1310 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1311 let output = work_dir.run_jj(["rebase", "-r", "b", "-d", "root()"]);
1312 insta::assert_snapshot!(output, @r"
1313 ------- stderr -------
1314 Rebased 1 commits onto destination
1315 Rebased 1 descendant commits
1316 Working copy (@) now at: znkkpsqq f280545e c | c
1317 Parent commit (@-) : zsuskuln 0a7fb8f6 base | base
1318 Parent commit (@-) : royxmykx 86a06598 a | a
1319 Added 0 files, modified 0 files, removed 1 files
1320 [EOF]
1321 ");
1322 // The user would expect unsimplified ancestry here.
1323 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1324 @ c: base a
1325 ├─╮
1326 │ ○ a: base
1327 ├─╯
1328 ○ base: notroot
1329 ○ notroot
1330 │ ○ b
1331 ├─╯
1332 ◆
1333 [EOF]
1334 ");
1335
1336 // This tests the algorithm for rebasing onto descendants. The result should
1337 // have unsimplified ancestry.
1338 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1339 let output = work_dir.run_jj(["rebase", "-r", "b", "-d", "c"]);
1340 insta::assert_snapshot!(output, @r"
1341 ------- stderr -------
1342 Rebased 1 commits onto destination
1343 Rebased 1 descendant commits
1344 Working copy (@) now at: znkkpsqq c0a7cd80 c | c
1345 Parent commit (@-) : zsuskuln 0a7fb8f6 base | base
1346 Parent commit (@-) : royxmykx 86a06598 a | a
1347 Added 0 files, modified 0 files, removed 1 files
1348 [EOF]
1349 ");
1350 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1351 ○ b: c
1352 @ c: base a
1353 ├─╮
1354 │ ○ a: base
1355 ├─╯
1356 ○ base: notroot
1357 ○ notroot
1358 ◆
1359 [EOF]
1360 ");
1361
1362 // In this test, the commit with weird ancestry is not rebased (neither directly
1363 // nor indirectly).
1364 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1365 let output = work_dir.run_jj(["rebase", "-r", "c", "-d", "a"]);
1366 insta::assert_snapshot!(output, @r"
1367 ------- stderr -------
1368 Rebased 1 commits onto destination
1369 Working copy (@) now at: znkkpsqq 7a3bc050 c | c
1370 Parent commit (@-) : royxmykx 86a06598 a | a
1371 Added 0 files, modified 0 files, removed 1 files
1372 [EOF]
1373 ");
1374 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1375 @ c: a
1376 │ ○ b: base a
1377 ╭─┤
1378 ○ │ a: base
1379 ├─╯
1380 ○ base: notroot
1381 ○ notroot
1382 ◆
1383 [EOF]
1384 ");
1385}
1386
1387#[test]
1388fn test_rebase_after() {
1389 let test_env = TestEnvironment::default();
1390 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1391 let work_dir = test_env.work_dir("repo");
1392
1393 create_commit(&work_dir, "a", &[]);
1394 create_commit(&work_dir, "b1", &["a"]);
1395 create_commit(&work_dir, "b2", &["b1"]);
1396 create_commit(&work_dir, "b3", &["a"]);
1397 create_commit(&work_dir, "b4", &["b3"]);
1398 create_commit(&work_dir, "c", &["b2", "b4"]);
1399 create_commit(&work_dir, "d", &["c"]);
1400 create_commit(&work_dir, "e", &["c"]);
1401 create_commit(&work_dir, "f", &["e"]);
1402 // Test the setup
1403 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1404 @ f: e
1405 ○ e: c
1406 │ ○ d: c
1407 ├─╯
1408 ○ c: b2 b4
1409 ├─╮
1410 │ ○ b4: b3
1411 │ ○ b3: a
1412 ○ │ b2: b1
1413 ○ │ b1: a
1414 ├─╯
1415 ○ a
1416 ◆
1417 [EOF]
1418 ");
1419 let setup_opid = work_dir.current_operation_id();
1420
1421 // Rebasing a commit after its parents should be a no-op.
1422 let output = work_dir.run_jj(["rebase", "-r", "c", "--after", "b2", "--after", "b4"]);
1423 insta::assert_snapshot!(output, @r"
1424 ------- stderr -------
1425 Skipped rebase of 4 commits that were already in place
1426 Nothing changed.
1427 [EOF]
1428 ");
1429 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1430 @ f: e
1431 ○ e: c
1432 │ ○ d: c
1433 ├─╯
1434 ○ c: b2 b4
1435 ├─╮
1436 │ ○ b4: b3
1437 │ ○ b3: a
1438 ○ │ b2: b1
1439 ○ │ b1: a
1440 ├─╯
1441 ○ a
1442 ◆
1443 [EOF]
1444 ");
1445
1446 // Rebasing a commit after itself should be a no-op.
1447 let output = work_dir.run_jj(["rebase", "-r", "c", "--after", "c"]);
1448 insta::assert_snapshot!(output, @r"
1449 ------- stderr -------
1450 Skipped rebase of 4 commits that were already in place
1451 Nothing changed.
1452 [EOF]
1453 ");
1454 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1455 @ f: e
1456 ○ e: c
1457 │ ○ d: c
1458 ├─╯
1459 ○ c: b2 b4
1460 ├─╮
1461 │ ○ b4: b3
1462 │ ○ b3: a
1463 ○ │ b2: b1
1464 ○ │ b1: a
1465 ├─╯
1466 ○ a
1467 ◆
1468 [EOF]
1469 ");
1470
1471 // Rebase a commit after another commit. "c" has parents "b2" and "b4", so its
1472 // children "d" and "e" should be rebased onto "b2" and "b4" respectively.
1473 let output = work_dir.run_jj(["rebase", "-r", "c", "--after", "e"]);
1474 insta::assert_snapshot!(output, @r"
1475 ------- stderr -------
1476 Rebased 1 commits onto destination
1477 Rebased 3 descendant commits
1478 Working copy (@) now at: xznxytkn e0e873c8 f | f
1479 Parent commit (@-) : kmkuslsw 754793f3 c | c
1480 [EOF]
1481 ");
1482 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1483 @ f: c
1484 ○ c: e
1485 ○ e: b2 b4
1486 ├─╮
1487 │ │ ○ d: b2 b4
1488 ╭─┬─╯
1489 │ ○ b4: b3
1490 │ ○ b3: a
1491 ○ │ b2: b1
1492 ○ │ b1: a
1493 ├─╯
1494 ○ a
1495 ◆
1496 [EOF]
1497 ");
1498 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1499
1500 // Rebase a commit after a leaf commit.
1501 let output = work_dir.run_jj(["rebase", "-r", "e", "--after", "f"]);
1502 insta::assert_snapshot!(output, @r"
1503 ------- stderr -------
1504 Rebased 1 commits onto destination
1505 Rebased 1 descendant commits
1506 Working copy (@) now at: xznxytkn 9804b742 f | f
1507 Parent commit (@-) : kmkuslsw cd86b3e4 c | c
1508 Added 0 files, modified 0 files, removed 1 files
1509 [EOF]
1510 ");
1511 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1512 ○ e: f
1513 @ f: c
1514 │ ○ d: c
1515 ├─╯
1516 ○ c: b2 b4
1517 ├─╮
1518 │ ○ b4: b3
1519 │ ○ b3: a
1520 ○ │ b2: b1
1521 ○ │ b1: a
1522 ├─╯
1523 ○ a
1524 ◆
1525 [EOF]
1526 ");
1527 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1528
1529 // Rebase a commit after a commit in a bookmark of a merge commit.
1530 let output = work_dir.run_jj(["rebase", "-r", "f", "--after", "b1"]);
1531 insta::assert_snapshot!(output, @r"
1532 ------- stderr -------
1533 Rebased 1 commits onto destination
1534 Rebased 4 descendant commits
1535 Working copy (@) now at: xznxytkn 80c27408 f | f
1536 Parent commit (@-) : zsuskuln 072d5ae1 b1 | b1
1537 Added 0 files, modified 0 files, removed 5 files
1538 [EOF]
1539 ");
1540 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1541 ○ e: c
1542 │ ○ d: c
1543 ├─╯
1544 ○ c: b2 b4
1545 ├─╮
1546 │ ○ b4: b3
1547 │ ○ b3: a
1548 ○ │ b2: f
1549 @ │ f: b1
1550 ○ │ b1: a
1551 ├─╯
1552 ○ a
1553 ◆
1554 [EOF]
1555 ");
1556 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1557
1558 // Rebase a commit after the last commit in a bookmark of a merge commit.
1559 let output = work_dir.run_jj(["rebase", "-r", "f", "--after", "b2"]);
1560 insta::assert_snapshot!(output, @r"
1561 ------- stderr -------
1562 Rebased 1 commits onto destination
1563 Rebased 3 descendant commits
1564 Working copy (@) now at: xznxytkn ebbc24b1 f | f
1565 Parent commit (@-) : royxmykx 2b8e1148 b2 | b2
1566 Added 0 files, modified 0 files, removed 4 files
1567 [EOF]
1568 ");
1569 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1570 ○ e: c
1571 │ ○ d: c
1572 ├─╯
1573 ○ c: f b4
1574 ├─╮
1575 │ ○ b4: b3
1576 │ ○ b3: a
1577 @ │ f: b2
1578 ○ │ b2: b1
1579 ○ │ b1: a
1580 ├─╯
1581 ○ a
1582 ◆
1583 [EOF]
1584 ");
1585 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1586
1587 // Rebase a commit after a commit with multiple children.
1588 // "c" has two children "d" and "e", so the rebased commit "f" will inherit the
1589 // two children.
1590 let output = work_dir.run_jj(["rebase", "-r", "f", "--after", "c"]);
1591 insta::assert_snapshot!(output, @r"
1592 ------- stderr -------
1593 Rebased 1 commits onto destination
1594 Rebased 2 descendant commits
1595 Working copy (@) now at: xznxytkn 8f8c91d3 f | f
1596 Parent commit (@-) : kmkuslsw cd86b3e4 c | c
1597 Added 0 files, modified 0 files, removed 1 files
1598 [EOF]
1599 ");
1600 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1601 ○ e: f
1602 │ ○ d: f
1603 ├─╯
1604 @ f: c
1605 ○ c: b2 b4
1606 ├─╮
1607 │ ○ b4: b3
1608 │ ○ b3: a
1609 ○ │ b2: b1
1610 ○ │ b1: a
1611 ├─╯
1612 ○ a
1613 ◆
1614 [EOF]
1615 ");
1616 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1617
1618 // Rebase a commit after multiple commits.
1619 let output = work_dir.run_jj(["rebase", "-r", "f", "--after", "e", "--after", "d"]);
1620 insta::assert_snapshot!(output, @r"
1621 ------- stderr -------
1622 Rebased 1 commits onto destination
1623 Working copy (@) now at: xznxytkn 7784e5a0 f | f
1624 Parent commit (@-) : nkmrtpmo 858693f7 e | e
1625 Parent commit (@-) : lylxulpl 7d0512e5 d | d
1626 Added 1 files, modified 0 files, removed 0 files
1627 [EOF]
1628 ");
1629 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1630 @ f: e d
1631 ├─╮
1632 │ ○ d: c
1633 ○ │ e: c
1634 ├─╯
1635 ○ c: b2 b4
1636 ├─╮
1637 │ ○ b4: b3
1638 │ ○ b3: a
1639 ○ │ b2: b1
1640 ○ │ b1: a
1641 ├─╯
1642 ○ a
1643 ◆
1644 [EOF]
1645 ");
1646 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1647
1648 // Rebase two unrelated commits.
1649 let output = work_dir.run_jj(["rebase", "-r", "d", "-r", "e", "--after", "a"]);
1650 insta::assert_snapshot!(output, @r"
1651 ------- stderr -------
1652 Rebased 2 commits onto destination
1653 Rebased 6 descendant commits
1654 Working copy (@) now at: xznxytkn 0b53613e f | f
1655 Parent commit (@-) : kmkuslsw 193687bb c | c
1656 Added 1 files, modified 0 files, removed 0 files
1657 [EOF]
1658 ");
1659 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1660 @ f: c
1661 ○ c: b2 b4
1662 ├─╮
1663 │ ○ b4: b3
1664 │ ○ b3: d e
1665 │ ├─╮
1666 ○ │ │ b2: b1
1667 ○ │ │ b1: d e
1668 ╰─┬─╮
1669 │ ○ e: a
1670 ○ │ d: a
1671 ├─╯
1672 ○ a
1673 ◆
1674 [EOF]
1675 ");
1676 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1677
1678 // Rebase a subgraph with merge commit and two parents, which should preserve
1679 // the merge.
1680 let output = work_dir.run_jj(["rebase", "-r", "b2", "-r", "b4", "-r", "c", "--after", "f"]);
1681 insta::assert_snapshot!(output, @r"
1682 ------- stderr -------
1683 Rebased 3 commits onto destination
1684 Rebased 3 descendant commits
1685 Working copy (@) now at: xznxytkn eaf1d6b8 f | f
1686 Parent commit (@-) : nkmrtpmo 0d7e4ce9 e | e
1687 Added 0 files, modified 0 files, removed 3 files
1688 [EOF]
1689 ");
1690 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1691 ○ c: b2 b4
1692 ├─╮
1693 │ ○ b4: f
1694 ○ │ b2: f
1695 ├─╯
1696 @ f: e
1697 ○ e: b1 b3
1698 ├─╮
1699 │ │ ○ d: b1 b3
1700 ╭─┬─╯
1701 │ ○ b3: a
1702 ○ │ b1: a
1703 ├─╯
1704 ○ a
1705 ◆
1706 [EOF]
1707 ");
1708 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1709
1710 // Rebase a subgraph with four commits after one of the commits itself.
1711 let output = work_dir.run_jj(["rebase", "-r", "b1::d", "--after", "c"]);
1712 insta::assert_snapshot!(output, @r"
1713 ------- stderr -------
1714 Rebased 4 commits onto destination
1715 Rebased 2 descendant commits
1716 Working copy (@) now at: xznxytkn 9bc7e54c f | f
1717 Parent commit (@-) : nkmrtpmo 0f80251b e | e
1718 Added 1 files, modified 0 files, removed 0 files
1719 [EOF]
1720 ");
1721 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1722 @ f: e
1723 ○ e: d
1724 ○ d: c
1725 ○ c: b2 b4
1726 ├─╮
1727 ○ │ b2: b1
1728 ○ │ b1: a b4
1729 ├─╮
1730 │ ○ b4: b3
1731 │ ○ b3: a
1732 ├─╯
1733 ○ a
1734 ◆
1735 [EOF]
1736 ");
1737 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1738
1739 // Rebase a subgraph before the parents of one of the commits in the subgraph.
1740 // "c" had parents "b2" and "b4", but no longer has "b4" as a parent since
1741 // "b4" would be a descendant of "c" after the rebase.
1742 let output = work_dir.run_jj(["rebase", "-r", "b2::d", "--after", "root()"]);
1743 insta::assert_snapshot!(output, @r"
1744 ------- stderr -------
1745 Rebased 3 commits onto destination
1746 Rebased 6 descendant commits
1747 Working copy (@) now at: xznxytkn 0875aabc f | f
1748 Parent commit (@-) : nkmrtpmo d429661b e | e
1749 Added 1 files, modified 0 files, removed 0 files
1750 [EOF]
1751 ");
1752 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1753 @ f: e
1754 ○ e: b1 b4
1755 ├─╮
1756 │ ○ b4: b3
1757 │ ○ b3: a
1758 ○ │ b1: a
1759 ├─╯
1760 ○ a: d
1761 ○ d: c
1762 ○ c: b2
1763 ○ b2
1764 ◆
1765 [EOF]
1766 ");
1767 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1768
1769 // Rebase a subgraph with disconnected commits. Since "b2" is an ancestor of
1770 // "e", "b2" should be a parent of "e" after the rebase.
1771 let output = work_dir.run_jj(["rebase", "-r", "e", "-r", "b2", "--after", "d"]);
1772 insta::assert_snapshot!(output, @r"
1773 ------- stderr -------
1774 Rebased 2 commits onto destination
1775 Rebased 3 descendant commits
1776 Working copy (@) now at: xznxytkn 3238a418 f | f
1777 Parent commit (@-) : kmkuslsw 6a51bd41 c | c
1778 Added 0 files, modified 0 files, removed 2 files
1779 [EOF]
1780 ");
1781 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1782 @ f: c
1783 │ ○ e: b2
1784 │ ○ b2: d
1785 │ ○ d: c
1786 ├─╯
1787 ○ c: b1 b4
1788 ├─╮
1789 │ ○ b4: b3
1790 │ ○ b3: a
1791 ○ │ b1: a
1792 ├─╯
1793 ○ a
1794 ◆
1795 [EOF]
1796 ");
1797 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1798
1799 // `rebase -s` of commit "c" and its descendants after itself should be a no-op.
1800 let output = work_dir.run_jj(["rebase", "-s", "c", "--after", "c"]);
1801 insta::assert_snapshot!(output, @r"
1802 ------- stderr -------
1803 Skipped rebase of 4 commits that were already in place
1804 Nothing changed.
1805 [EOF]
1806 ");
1807 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1808 @ f: e
1809 ○ e: c
1810 │ ○ d: c
1811 ├─╯
1812 ○ c: b2 b4
1813 ├─╮
1814 │ ○ b4: b3
1815 │ ○ b3: a
1816 ○ │ b2: b1
1817 ○ │ b1: a
1818 ├─╯
1819 ○ a
1820 ◆
1821 [EOF]
1822 ");
1823 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1824
1825 // `rebase -s` of a commit and its descendants after multiple commits.
1826 let output = work_dir.run_jj(["rebase", "-s", "c", "--after", "b1", "--after", "b3"]);
1827 insta::assert_snapshot!(output, @r"
1828 ------- stderr -------
1829 Rebased 4 commits onto destination
1830 Rebased 2 descendant commits
1831 Working copy (@) now at: xznxytkn a4ace41c f | f
1832 Parent commit (@-) : nkmrtpmo c7744d08 e | e
1833 Added 0 files, modified 0 files, removed 2 files
1834 [EOF]
1835 ");
1836 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1837 ○ b4: d f
1838 ├─╮
1839 │ │ ○ b2: d f
1840 ╭─┬─╯
1841 │ @ f: e
1842 │ ○ e: c
1843 ○ │ d: c
1844 ├─╯
1845 ○ c: b1 b3
1846 ├─╮
1847 │ ○ b3: a
1848 ○ │ b1: a
1849 ├─╯
1850 ○ a
1851 ◆
1852 [EOF]
1853 ");
1854 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1855
1856 // `rebase -b` of commit "b3" after "b1" moves its descendants which are not
1857 // already descendants of "b1" (just "b3" and "b4") in between "b1" and its
1858 // child "b2".
1859 let output = work_dir.run_jj(["rebase", "-b", "b3", "--after", "b1"]);
1860 insta::assert_snapshot!(output, @r"
1861 ------- stderr -------
1862 Rebased 6 commits onto destination
1863 Rebased 1 descendant commits
1864 Working copy (@) now at: xznxytkn b4078b57 f | f
1865 Parent commit (@-) : nkmrtpmo 1b95558f e | e
1866 Added 0 files, modified 0 files, removed 1 files
1867 [EOF]
1868 ");
1869 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1870 ○ b2: d f
1871 ├─╮
1872 │ @ f: e
1873 │ ○ e: c
1874 ○ │ d: c
1875 ├─╯
1876 ○ c: b4
1877 ○ b4: b3
1878 ○ b3: b1
1879 ○ b1: a
1880 ○ a
1881 ◆
1882 [EOF]
1883 ");
1884 work_dir.run_jj(["op", "restore", &setup_opid]).success();
1885
1886 // Should error if a loop will be created.
1887 let output = work_dir.run_jj(["rebase", "-r", "e", "--after", "a", "--after", "b2"]);
1888 insta::assert_snapshot!(output, @r"
1889 ------- stderr -------
1890 Error: Refusing to create a loop: commit 2b8e1148290f would be both an ancestor and a descendant of the rebased commits
1891 [EOF]
1892 [exit status: 1]
1893 ");
1894}
1895
1896#[test]
1897fn test_rebase_before() {
1898 let test_env = TestEnvironment::default();
1899 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
1900 let work_dir = test_env.work_dir("repo");
1901
1902 create_commit(&work_dir, "a", &[]);
1903 create_commit(&work_dir, "b1", &["a"]);
1904 create_commit(&work_dir, "b2", &["b1"]);
1905 create_commit(&work_dir, "b3", &["a"]);
1906 create_commit(&work_dir, "b4", &["b3"]);
1907 create_commit(&work_dir, "c", &["b2", "b4"]);
1908 create_commit(&work_dir, "d", &["c"]);
1909 create_commit(&work_dir, "e", &["c"]);
1910 create_commit(&work_dir, "f", &["e"]);
1911 // Test the setup
1912 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1913 @ f: e
1914 ○ e: c
1915 │ ○ d: c
1916 ├─╯
1917 ○ c: b2 b4
1918 ├─╮
1919 │ ○ b4: b3
1920 │ ○ b3: a
1921 ○ │ b2: b1
1922 ○ │ b1: a
1923 ├─╯
1924 ○ a
1925 ◆
1926 [EOF]
1927 ");
1928 let setup_opid = work_dir.current_operation_id();
1929
1930 // Rebasing a commit before its children should be a no-op.
1931 let output = work_dir.run_jj(["rebase", "-r", "c", "--before", "d", "--before", "e"]);
1932 insta::assert_snapshot!(output, @r"
1933 ------- stderr -------
1934 Skipped rebase of 4 commits that were already in place
1935 Nothing changed.
1936 [EOF]
1937 ");
1938 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1939 @ f: e
1940 ○ e: c
1941 │ ○ d: c
1942 ├─╯
1943 ○ c: b2 b4
1944 ├─╮
1945 │ ○ b4: b3
1946 │ ○ b3: a
1947 ○ │ b2: b1
1948 ○ │ b1: a
1949 ├─╯
1950 ○ a
1951 ◆
1952 [EOF]
1953 ");
1954
1955 // Rebasing a commit before itself should be a no-op.
1956 let output = work_dir.run_jj(["rebase", "-r", "c", "--before", "c"]);
1957 insta::assert_snapshot!(output, @r"
1958 ------- stderr -------
1959 Skipped rebase of 4 commits that were already in place
1960 Nothing changed.
1961 [EOF]
1962 ");
1963 insta::assert_snapshot!(get_log_output(&work_dir), @r"
1964 @ f: e
1965 ○ e: c
1966 │ ○ d: c
1967 ├─╯
1968 ○ c: b2 b4
1969 ├─╮
1970 │ ○ b4: b3
1971 │ ○ b3: a
1972 ○ │ b2: b1
1973 ○ │ b1: a
1974 ├─╯
1975 ○ a
1976 ◆
1977 [EOF]
1978 ");
1979
1980 // Rebasing a commit before the root commit should error.
1981 let output = work_dir.run_jj(["rebase", "-r", "c", "--before", "root()"]);
1982 insta::assert_snapshot!(output, @r"
1983 ------- stderr -------
1984 Error: The root commit 000000000000 is immutable
1985 [EOF]
1986 [exit status: 1]
1987 ");
1988
1989 // Rebase a commit before another commit. "c" has parents "b2" and "b4", so its
1990 // children "d" and "e" should be rebased onto "b2" and "b4" respectively.
1991 let output = work_dir.run_jj(["rebase", "-r", "c", "--before", "a"]);
1992 insta::assert_snapshot!(output, @r"
1993 ------- stderr -------
1994 Rebased 1 commits onto destination
1995 Rebased 8 descendant commits
1996 Working copy (@) now at: xznxytkn 24335685 f | f
1997 Parent commit (@-) : nkmrtpmo e9a28d4b e | e
1998 [EOF]
1999 ");
2000 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2001 @ f: e
2002 ○ e: b2 b4
2003 ├─╮
2004 │ │ ○ d: b2 b4
2005 ╭─┬─╯
2006 │ ○ b4: b3
2007 │ ○ b3: a
2008 ○ │ b2: b1
2009 ○ │ b1: a
2010 ├─╯
2011 ○ a: c
2012 ○ c
2013 ◆
2014 [EOF]
2015 ");
2016 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2017
2018 // Rebase a commit before its parent.
2019 let output = work_dir.run_jj(["rebase", "-r", "f", "--before", "e"]);
2020 insta::assert_snapshot!(output, @r"
2021 ------- stderr -------
2022 Rebased 1 commits onto destination
2023 Rebased 1 descendant commits
2024 Working copy (@) now at: xznxytkn 8e3b728a f | f
2025 Parent commit (@-) : kmkuslsw cd86b3e4 c | c
2026 Added 0 files, modified 0 files, removed 1 files
2027 [EOF]
2028 ");
2029 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2030 ○ e: f
2031 @ f: c
2032 │ ○ d: c
2033 ├─╯
2034 ○ c: b2 b4
2035 ├─╮
2036 │ ○ b4: b3
2037 │ ○ b3: a
2038 ○ │ b2: b1
2039 ○ │ b1: a
2040 ├─╯
2041 ○ a
2042 ◆
2043 [EOF]
2044 ");
2045 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2046
2047 // Rebase a commit before a commit in a bookmark of a merge commit.
2048 let output = work_dir.run_jj(["rebase", "-r", "f", "--before", "b2"]);
2049 insta::assert_snapshot!(output, @r"
2050 ------- stderr -------
2051 Rebased 1 commits onto destination
2052 Rebased 4 descendant commits
2053 Working copy (@) now at: xznxytkn 2b4f48f8 f | f
2054 Parent commit (@-) : zsuskuln 072d5ae1 b1 | b1
2055 Added 0 files, modified 0 files, removed 5 files
2056 [EOF]
2057 ");
2058 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2059 ○ e: c
2060 │ ○ d: c
2061 ├─╯
2062 ○ c: b2 b4
2063 ├─╮
2064 │ ○ b4: b3
2065 │ ○ b3: a
2066 ○ │ b2: f
2067 @ │ f: b1
2068 ○ │ b1: a
2069 ├─╯
2070 ○ a
2071 ◆
2072 [EOF]
2073 ");
2074 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2075
2076 // Rebase a commit before the first commit in a bookmark of a merge commit.
2077 let output = work_dir.run_jj(["rebase", "-r", "f", "--before", "b1"]);
2078 insta::assert_snapshot!(output, @r"
2079 ------- stderr -------
2080 Rebased 1 commits onto destination
2081 Rebased 5 descendant commits
2082 Working copy (@) now at: xznxytkn 488ebb95 f | f
2083 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
2084 Added 0 files, modified 0 files, removed 6 files
2085 [EOF]
2086 ");
2087 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2088 ○ e: c
2089 │ ○ d: c
2090 ├─╯
2091 ○ c: b2 b4
2092 ├─╮
2093 │ ○ b4: b3
2094 │ ○ b3: a
2095 ○ │ b2: b1
2096 ○ │ b1: f
2097 @ │ f: a
2098 ├─╯
2099 ○ a
2100 ◆
2101 [EOF]
2102 ");
2103 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2104
2105 // Rebase a commit before a merge commit. "c" has two parents "b2" and "b4", so
2106 // the rebased commit "f" will have the two commits "b2" and "b4" as its
2107 // parents.
2108 let output = work_dir.run_jj(["rebase", "-r", "f", "--before", "c"]);
2109 insta::assert_snapshot!(output, @r"
2110 ------- stderr -------
2111 Rebased 1 commits onto destination
2112 Rebased 3 descendant commits
2113 Working copy (@) now at: xznxytkn aae1bc10 f | f
2114 Parent commit (@-) : royxmykx 2b8e1148 b2 | b2
2115 Parent commit (@-) : znkkpsqq a52a83a4 b4 | b4
2116 Added 0 files, modified 0 files, removed 2 files
2117 [EOF]
2118 ");
2119 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2120 ○ e: c
2121 │ ○ d: c
2122 ├─╯
2123 ○ c: f
2124 @ f: b2 b4
2125 ├─╮
2126 │ ○ b4: b3
2127 │ ○ b3: a
2128 ○ │ b2: b1
2129 ○ │ b1: a
2130 ├─╯
2131 ○ a
2132 ◆
2133 [EOF]
2134 ");
2135 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2136
2137 // Rebase a commit before multiple commits.
2138 let output = work_dir.run_jj(["rebase", "-r", "b1", "--before", "d", "--before", "e"]);
2139 insta::assert_snapshot!(output, @r"
2140 ------- stderr -------
2141 Rebased 1 commits onto destination
2142 Rebased 5 descendant commits
2143 Working copy (@) now at: xznxytkn 8268ec4d f | f
2144 Parent commit (@-) : nkmrtpmo fd26fbd4 e | e
2145 [EOF]
2146 ");
2147 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2148 @ f: e
2149 ○ e: b1
2150 │ ○ d: b1
2151 ├─╯
2152 ○ b1: c
2153 ○ c: b2 b4
2154 ├─╮
2155 │ ○ b4: b3
2156 │ ○ b3: a
2157 ○ │ b2: a
2158 ├─╯
2159 ○ a
2160 ◆
2161 [EOF]
2162 ");
2163 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2164
2165 // Rebase a commit before two commits in separate bookmarks to create a merge
2166 // commit.
2167 let output = work_dir.run_jj(["rebase", "-r", "f", "--before", "b2", "--before", "b4"]);
2168 insta::assert_snapshot!(output, @r"
2169 ------- stderr -------
2170 Rebased 1 commits onto destination
2171 Rebased 5 descendant commits
2172 Working copy (@) now at: xznxytkn 7ba8014f f | f
2173 Parent commit (@-) : zsuskuln 072d5ae1 b1 | b1
2174 Parent commit (@-) : vruxwmqv 523e6a8b b3 | b3
2175 Added 0 files, modified 0 files, removed 4 files
2176 [EOF]
2177 ");
2178 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2179 ○ e: c
2180 │ ○ d: c
2181 ├─╯
2182 ○ c: b2 b4
2183 ├─╮
2184 │ ○ b4: f
2185 ○ │ b2: f
2186 ├─╯
2187 @ f: b1 b3
2188 ├─╮
2189 │ ○ b3: a
2190 ○ │ b1: a
2191 ├─╯
2192 ○ a
2193 ◆
2194 [EOF]
2195 ");
2196 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2197
2198 // Rebase two unrelated commits "b2" and "b4" before a single commit "a". This
2199 // creates a merge commit "a" with the two parents "b2" and "b4".
2200 let output = work_dir.run_jj(["rebase", "-r", "b2", "-r", "b4", "--before", "a"]);
2201 insta::assert_snapshot!(output, @r"
2202 ------- stderr -------
2203 Rebased 2 commits onto destination
2204 Rebased 7 descendant commits
2205 Working copy (@) now at: xznxytkn fabd8dd7 f | f
2206 Parent commit (@-) : nkmrtpmo b5933877 e | e
2207 [EOF]
2208 ");
2209 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2210 @ f: e
2211 ○ e: c
2212 │ ○ d: c
2213 ├─╯
2214 ○ c: b1 b3
2215 ├─╮
2216 │ ○ b3: a
2217 ○ │ b1: a
2218 ├─╯
2219 ○ a: b2 b4
2220 ├─╮
2221 │ ○ b4
2222 ○ │ b2
2223 ├─╯
2224 ◆
2225 [EOF]
2226 ");
2227 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2228
2229 // Rebase a subgraph with a merge commit and two parents.
2230 let output = work_dir.run_jj(["rebase", "-r", "b2", "-r", "b4", "-r", "c", "--before", "e"]);
2231 insta::assert_snapshot!(output, @r"
2232 ------- stderr -------
2233 Rebased 3 commits onto destination
2234 Rebased 3 descendant commits
2235 Working copy (@) now at: xznxytkn cbe2be58 f | f
2236 Parent commit (@-) : nkmrtpmo e31053d1 e | e
2237 [EOF]
2238 ");
2239 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2240 @ f: e
2241 ○ e: c
2242 ○ c: b2 b4
2243 ├─╮
2244 │ ○ b4: b1 b3
2245 │ ├─╮
2246 ○ │ │ b2: b1 b3
2247 ╰─┬─╮
2248 ○ │ │ d: b1 b3
2249 ╰─┬─╮
2250 │ ○ b3: a
2251 ○ │ b1: a
2252 ├─╯
2253 ○ a
2254 ◆
2255 [EOF]
2256 ");
2257 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2258
2259 // Rebase a subgraph with disconnected commits. Since "b1" is an ancestor of
2260 // "e", "b1" should be a parent of "e" after the rebase.
2261 let output = work_dir.run_jj(["rebase", "-r", "b1", "-r", "e", "--before", "a"]);
2262 insta::assert_snapshot!(output, @r"
2263 ------- stderr -------
2264 Rebased 2 commits onto destination
2265 Rebased 7 descendant commits
2266 Working copy (@) now at: xznxytkn 1c48b514 f | f
2267 Parent commit (@-) : kmkuslsw c0fd979a c | c
2268 [EOF]
2269 ");
2270 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2271 @ f: c
2272 │ ○ d: c
2273 ├─╯
2274 ○ c: b2 b4
2275 ├─╮
2276 │ ○ b4: b3
2277 │ ○ b3: a
2278 ○ │ b2: a
2279 ├─╯
2280 ○ a: e
2281 ○ e: b1
2282 ○ b1
2283 ◆
2284 [EOF]
2285 ");
2286 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2287
2288 // Rebase a subgraph before the parents of one of the commits in the subgraph.
2289 // "c" had parents "b2" and "b4", but no longer has "b4" as a parent since
2290 // "b4" would be a descendant of "c" after the rebase.
2291 let output = work_dir.run_jj(["rebase", "-r", "b2::d", "--before", "a"]);
2292 insta::assert_snapshot!(output, @r"
2293 ------- stderr -------
2294 Rebased 3 commits onto destination
2295 Rebased 6 descendant commits
2296 Working copy (@) now at: xznxytkn f5991dc7 f | f
2297 Parent commit (@-) : nkmrtpmo 37894e3c e | e
2298 Added 1 files, modified 0 files, removed 0 files
2299 [EOF]
2300 ");
2301 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2302 @ f: e
2303 ○ e: b1 b4
2304 ├─╮
2305 │ ○ b4: b3
2306 │ ○ b3: a
2307 ○ │ b1: a
2308 ├─╯
2309 ○ a: d
2310 ○ d: c
2311 ○ c: b2
2312 ○ b2
2313 ◆
2314 [EOF]
2315 ");
2316 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2317
2318 // Rebase a subgraph before the parents of one of the commits in the subgraph.
2319 // "c" had parents "b2" and "b4", but no longer has "b4" as a parent since
2320 // "b4" would be a descendant of "c" after the rebase.
2321 let output = work_dir.run_jj(["rebase", "-r", "b2::d", "--before", "a"]);
2322 insta::assert_snapshot!(output, @r"
2323 ------- stderr -------
2324 Rebased 3 commits onto destination
2325 Rebased 6 descendant commits
2326 Working copy (@) now at: xznxytkn 308a31e9 f | f
2327 Parent commit (@-) : nkmrtpmo 538444a5 e | e
2328 Added 1 files, modified 0 files, removed 0 files
2329 [EOF]
2330 ");
2331 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2332 @ f: e
2333 ○ e: b1 b4
2334 ├─╮
2335 │ ○ b4: b3
2336 │ ○ b3: a
2337 ○ │ b1: a
2338 ├─╯
2339 ○ a: d
2340 ○ d: c
2341 ○ c: b2
2342 ○ b2
2343 ◆
2344 [EOF]
2345 ");
2346 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2347
2348 // `rebase -s` of commit "c" and its descendants before itself should be a
2349 // no-op.
2350 let output = work_dir.run_jj(["rebase", "-s", "c", "--before", "c"]);
2351 insta::assert_snapshot!(output, @r"
2352 ------- stderr -------
2353 Skipped rebase of 4 commits that were already in place
2354 Nothing changed.
2355 [EOF]
2356 ");
2357 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2358 @ f: e
2359 ○ e: c
2360 │ ○ d: c
2361 ├─╯
2362 ○ c: b2 b4
2363 ├─╮
2364 │ ○ b4: b3
2365 │ ○ b3: a
2366 ○ │ b2: b1
2367 ○ │ b1: a
2368 ├─╯
2369 ○ a
2370 ◆
2371 [EOF]
2372 ");
2373 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2374
2375 // `rebase -s` of a commit and its descendants before multiple commits.
2376 let output = work_dir.run_jj(["rebase", "-s", "c", "--before", "b2", "--before", "b4"]);
2377 insta::assert_snapshot!(output, @r"
2378 ------- stderr -------
2379 Rebased 4 commits onto destination
2380 Rebased 2 descendant commits
2381 Working copy (@) now at: xznxytkn 84704387 f | f
2382 Parent commit (@-) : nkmrtpmo cff61821 e | e
2383 Added 0 files, modified 0 files, removed 2 files
2384 [EOF]
2385 ");
2386 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2387 ○ b4: d f
2388 ├─╮
2389 │ │ ○ b2: d f
2390 ╭─┬─╯
2391 │ @ f: e
2392 │ ○ e: c
2393 ○ │ d: c
2394 ├─╯
2395 ○ c: b1 b3
2396 ├─╮
2397 │ ○ b3: a
2398 ○ │ b1: a
2399 ├─╯
2400 ○ a
2401 ◆
2402 [EOF]
2403 ");
2404 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2405
2406 // `rebase -b` of commit "b3" before "b2" moves its descendants which are not
2407 // already descendants of its parent "b1" (just "b3" and "b4") in between "b1"
2408 // and its child "b2".
2409 let output = work_dir.run_jj(["rebase", "-b", "b3", "--before", "b1"]);
2410 insta::assert_snapshot!(output, @r"
2411 ------- stderr -------
2412 Skipped rebase of 2 commits that were already in place
2413 Rebased 4 commits onto destination
2414 Rebased 2 descendant commits
2415 Working copy (@) now at: xznxytkn 16422f85 f | f
2416 Parent commit (@-) : nkmrtpmo ef9dea83 e | e
2417 Added 0 files, modified 0 files, removed 2 files
2418 [EOF]
2419 ");
2420 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2421 ○ b2: b1
2422 ○ b1: d f
2423 ├─╮
2424 │ @ f: e
2425 │ ○ e: c
2426 ○ │ d: c
2427 ├─╯
2428 ○ c: b4
2429 ○ b4: b3
2430 ○ b3: a
2431 ○ a
2432 ◆
2433 [EOF]
2434 ");
2435 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2436
2437 // Should error if a loop will be created.
2438 let output = work_dir.run_jj(["rebase", "-r", "e", "--before", "b2", "--before", "c"]);
2439 insta::assert_snapshot!(output, @r"
2440 ------- stderr -------
2441 Error: Refusing to create a loop: commit 2b8e1148290f would be both an ancestor and a descendant of the rebased commits
2442 [EOF]
2443 [exit status: 1]
2444 ");
2445}
2446
2447#[test]
2448fn test_rebase_after_before() {
2449 let test_env = TestEnvironment::default();
2450 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2451 let work_dir = test_env.work_dir("repo");
2452
2453 create_commit(&work_dir, "x", &[]);
2454 create_commit(&work_dir, "y", &["x"]);
2455 create_commit(&work_dir, "z", &["y"]);
2456 create_commit(&work_dir, "a", &[]);
2457 create_commit(&work_dir, "b1", &["a"]);
2458 create_commit(&work_dir, "b2", &["a"]);
2459 create_commit(&work_dir, "c", &["b1", "b2"]);
2460 create_commit(&work_dir, "d", &["c"]);
2461 create_commit(&work_dir, "e", &["c"]);
2462 create_commit(&work_dir, "f", &["e"]);
2463 // Test the setup
2464 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2465 @ f: e
2466 ○ e: c
2467 │ ○ d: c
2468 ├─╯
2469 ○ c: b1 b2
2470 ├─╮
2471 │ ○ b2: a
2472 ○ │ b1: a
2473 ├─╯
2474 ○ a
2475 │ ○ z: y
2476 │ ○ y: x
2477 │ ○ x
2478 ├─╯
2479 ◆
2480 [EOF]
2481 ");
2482 let setup_opid = work_dir.current_operation_id();
2483
2484 // Rebase a commit after another commit and before that commit's child to
2485 // insert directly between the two commits.
2486 let output = work_dir.run_jj(["rebase", "-r", "d", "--after", "e", "--before", "f"]);
2487 insta::assert_snapshot!(output, @r"
2488 ------- stderr -------
2489 Rebased 1 commits onto destination
2490 Rebased 1 descendant commits
2491 Working copy (@) now at: nmzmmopx 56c81c6d f | f
2492 Parent commit (@-) : nkmrtpmo ff196f69 d | d
2493 Added 1 files, modified 0 files, removed 0 files
2494 [EOF]
2495 ");
2496 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2497 @ f: d
2498 ○ d: e
2499 ○ e: c
2500 ○ c: b1 b2
2501 ├─╮
2502 │ ○ b2: a
2503 ○ │ b1: a
2504 ├─╯
2505 ○ a
2506 │ ○ z: y
2507 │ ○ y: x
2508 │ ○ x
2509 ├─╯
2510 ◆
2511 [EOF]
2512 ");
2513 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2514
2515 // Rebase a commit after another commit and before that commit's descendant to
2516 // create a new merge commit.
2517 let output = work_dir.run_jj(["rebase", "-r", "d", "--after", "a", "--before", "f"]);
2518 insta::assert_snapshot!(output, @r"
2519 ------- stderr -------
2520 Rebased 1 commits onto destination
2521 Rebased 1 descendant commits
2522 Working copy (@) now at: nmzmmopx 398173ed f | f
2523 Parent commit (@-) : xznxytkn b3e6aadf e | e
2524 Parent commit (@-) : nkmrtpmo db529447 d | d
2525 Added 1 files, modified 0 files, removed 0 files
2526 [EOF]
2527 ");
2528 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2529 @ f: e d
2530 ├─╮
2531 │ ○ d: a
2532 ○ │ e: c
2533 ○ │ c: b1 b2
2534 ├───╮
2535 │ │ ○ b2: a
2536 │ ├─╯
2537 ○ │ b1: a
2538 ├─╯
2539 ○ a
2540 │ ○ z: y
2541 │ ○ y: x
2542 │ ○ x
2543 ├─╯
2544 ◆
2545 [EOF]
2546 ");
2547 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2548
2549 // "c" has parents "b1" and "b2", so when it is rebased, its children "d" and
2550 // "e" should have "b1" and "b2" as parents as well. "c" is then inserted in
2551 // between "d" and "e", making "e" a merge commit with 3 parents "b1", "b2",
2552 // and "c".
2553 let output = work_dir.run_jj(["rebase", "-r", "c", "--after", "d", "--before", "e"]);
2554 insta::assert_snapshot!(output, @r"
2555 ------- stderr -------
2556 Rebased 1 commits onto destination
2557 Rebased 3 descendant commits
2558 Working copy (@) now at: nmzmmopx 2be98daf f | f
2559 Parent commit (@-) : xznxytkn 911fc846 e | e
2560 Added 1 files, modified 0 files, removed 0 files
2561 [EOF]
2562 ");
2563 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2564 @ f: e
2565 ○ e: b1 b2 c
2566 ├─┬─╮
2567 │ │ ○ c: d
2568 │ │ ○ d: b1 b2
2569 ╭─┬─╯
2570 │ ○ b2: a
2571 ○ │ b1: a
2572 ├─╯
2573 ○ a
2574 │ ○ z: y
2575 │ ○ y: x
2576 │ ○ x
2577 ├─╯
2578 ◆
2579 [EOF]
2580 ");
2581 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2582
2583 // Rebase multiple commits and preserve their ancestry. Apart from the heads of
2584 // the target commits ("d" and "e"), "f" also has commits "b1" and "b2" as
2585 // parents since its parents "d" and "e" were in the target set and were
2586 // replaced by their closest ancestors outside the target set.
2587 let output = work_dir.run_jj([
2588 "rebase", "-r", "c", "-r", "d", "-r", "e", "--after", "a", "--before", "f",
2589 ]);
2590 insta::assert_snapshot!(output, @r"
2591 ------- stderr -------
2592 Rebased 3 commits onto destination
2593 Rebased 1 descendant commits
2594 Working copy (@) now at: nmzmmopx bee09b10 f | f
2595 Parent commit (@-) : znkkpsqq 9167144b b1 | b1
2596 Parent commit (@-) : kmkuslsw 87fed139 b2 | b2
2597 Parent commit (@-) : nkmrtpmo 4a8ca156 d | d
2598 Parent commit (@-) : xznxytkn 0cc1825e e | e
2599 Added 1 files, modified 0 files, removed 0 files
2600 [EOF]
2601 ");
2602 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2603 @ f: b1 b2 d e
2604 ├─┬─┬─╮
2605 │ │ │ ○ e: c
2606 │ │ ○ │ d: c
2607 │ │ ├─╯
2608 │ │ ○ c: a
2609 │ ○ │ b2: a
2610 │ ├─╯
2611 ○ │ b1: a
2612 ├─╯
2613 ○ a
2614 │ ○ z: y
2615 │ ○ y: x
2616 │ ○ x
2617 ├─╯
2618 ◆
2619 [EOF]
2620 ");
2621 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2622
2623 // `rebase -s` of a commit and its descendants.
2624 let output = work_dir.run_jj(["rebase", "-s", "c", "--before", "b1", "--after", "b2"]);
2625 insta::assert_snapshot!(output, @r"
2626 ------- stderr -------
2627 Rebased 4 commits onto destination
2628 Rebased 1 descendant commits
2629 Working copy (@) now at: nmzmmopx 951204cf f | f
2630 Parent commit (@-) : xznxytkn fe8ec4e2 e | e
2631 Added 0 files, modified 0 files, removed 1 files
2632 [EOF]
2633 ");
2634 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2635 ○ b1: a d f
2636 ├─┬─╮
2637 │ │ @ f: e
2638 │ │ ○ e: c
2639 │ ○ │ d: c
2640 │ ├─╯
2641 │ ○ c: b2
2642 │ ○ b2: a
2643 ├─╯
2644 ○ a
2645 │ ○ z: y
2646 │ ○ y: x
2647 │ ○ x
2648 ├─╯
2649 ◆
2650 [EOF]
2651 ");
2652 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2653
2654 // `rebase -b` of a commit "y" to a destination after "a" will rebase all
2655 // commits in "roots(a..y)" and their descendants, corresponding to "x", "y"
2656 // and "z". They will be inserted in a new branch after "a" and before "c".
2657 let output = work_dir.run_jj(["rebase", "-b", "y", "--after", "a", "--before", "c"]);
2658 insta::assert_snapshot!(output, @r"
2659 ------- stderr -------
2660 Rebased 3 commits onto destination
2661 Rebased 4 descendant commits
2662 Working copy (@) now at: nmzmmopx 4496f88e f | f
2663 Parent commit (@-) : xznxytkn a85404a6 e | e
2664 Added 3 files, modified 0 files, removed 0 files
2665 [EOF]
2666 ");
2667 insta::assert_snapshot!(get_log_output(&work_dir), @r"
2668 @ f: e
2669 ○ e: c
2670 │ ○ d: c
2671 ├─╯
2672 ○ c: b1 b2 z
2673 ├─┬─╮
2674 │ │ ○ z: y
2675 │ │ ○ y: x
2676 │ │ ○ x: a
2677 │ ○ │ b2: a
2678 │ ├─╯
2679 ○ │ b1: a
2680 ├─╯
2681 ○ a
2682 ◆
2683 [EOF]
2684 ");
2685 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2686
2687 // Should error if a loop will be created.
2688 let output = work_dir.run_jj(["rebase", "-r", "e", "--after", "c", "--before", "a"]);
2689 insta::assert_snapshot!(output, @r"
2690 ------- stderr -------
2691 Error: Refusing to create a loop: commit 31b84afe1c8f would be both an ancestor and a descendant of the rebased commits
2692 [EOF]
2693 [exit status: 1]
2694 ");
2695}
2696
2697#[test]
2698fn test_rebase_skip_emptied() {
2699 let test_env = TestEnvironment::default();
2700 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2701 let work_dir = test_env.work_dir("repo");
2702
2703 create_commit(&work_dir, "a", &[]);
2704 create_commit(&work_dir, "b", &["a"]);
2705 work_dir
2706 .run_jj(["new", "a", "-m", "will become empty"])
2707 .success();
2708 work_dir.run_jj(["restore", "--from=b"]).success();
2709 work_dir.run_jj(["new", "-m", "already empty"]).success();
2710 work_dir
2711 .run_jj(["new", "-m", "also already empty"])
2712 .success();
2713 let setup_opid = work_dir.current_operation_id();
2714
2715 // Test the setup
2716 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "description"]), @r"
2717 @ also already empty
2718 ○ already empty
2719 ○ will become empty
2720 │ ○ b
2721 ├─╯
2722 ○ a
2723 ◆
2724 [EOF]
2725 ");
2726
2727 let output = work_dir.run_jj(["rebase", "-d=b", "--skip-emptied"]);
2728 insta::assert_snapshot!(output, @r"
2729 ------- stderr -------
2730 Rebased 2 commits onto destination
2731 Abandoned 1 newly emptied commits
2732 Working copy (@) now at: yostqsxw bc4222f2 (empty) also already empty
2733 Parent commit (@-) : vruxwmqv 6b41ecb2 (empty) already empty
2734 [EOF]
2735 ");
2736
2737 // The parent commit became empty and was dropped, but the already empty commits
2738 // were kept
2739 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "description"]), @r"
2740 @ also already empty
2741 ○ already empty
2742 ○ b
2743 ○ a
2744 ◆
2745 [EOF]
2746 ");
2747
2748 work_dir.run_jj(["op", "restore", &setup_opid]).success();
2749 // Test the setup
2750 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "description"]), @r"
2751 @ also already empty
2752 ○ already empty
2753 ○ will become empty
2754 │ ○ b
2755 ├─╯
2756 ○ a
2757 ◆
2758 [EOF]
2759 ");
2760
2761 let output = work_dir.run_jj([
2762 "rebase",
2763 "-r=description('will become empty')",
2764 "-d=b",
2765 "--skip-emptied",
2766 ]);
2767 insta::assert_snapshot!(output, @r"
2768 ------- stderr -------
2769 Rebased 2 descendant commits
2770 Abandoned 1 newly emptied commits
2771 Working copy (@) now at: yostqsxw 74149b9b (empty) also already empty
2772 Parent commit (@-) : vruxwmqv 3bdb2801 (empty) already empty
2773 Added 0 files, modified 0 files, removed 1 files
2774 [EOF]
2775 ");
2776
2777 // Rebasing a single commit which becomes empty abandons that commit, whilst its
2778 // already empty descendants were kept
2779 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "description"]), @r"
2780 @ also already empty
2781 ○ already empty
2782 │ ○ b
2783 ├─╯
2784 ○ a
2785 ◆
2786 [EOF]
2787 ");
2788}
2789
2790#[test]
2791fn test_rebase_skip_emptied_descendants() {
2792 let test_env = TestEnvironment::default();
2793 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2794 let work_dir = test_env.work_dir("repo");
2795
2796 create_commit(&work_dir, "a", &[]);
2797 create_commit(&work_dir, "b", &["a"]);
2798 work_dir
2799 .run_jj(["new", "a", "-m", "c (will become empty)"])
2800 .success();
2801 work_dir.run_jj(["restore", "--from=b"]).success();
2802 work_dir
2803 .run_jj(["bookmark", "create", "-r@", "c"])
2804 .success();
2805 work_dir.run_jj(["new", "-m", "already empty"]).success();
2806 work_dir
2807 .run_jj(["new", "-m", "also already empty"])
2808 .success();
2809
2810 // Test the setup
2811 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "description"]), @r"
2812 @ also already empty
2813 ○ already empty
2814 ○ c (will become empty)
2815 │ ○ b
2816 ├─╯
2817 ○ a
2818 ◆
2819 [EOF]
2820 ");
2821
2822 let output = work_dir.run_jj(["rebase", "-r", "b", "--before", "c", "--skip-emptied"]);
2823 insta::assert_snapshot!(output, @r"
2824 ------- stderr -------
2825 Skipped rebase of 1 commits that were already in place
2826 Rebased 3 descendant commits
2827 Working copy (@) now at: znkkpsqq 353bac5c (empty) also already empty
2828 Parent commit (@-) : yostqsxw 0a3f76fd (empty) already empty
2829 [EOF]
2830 ");
2831
2832 // Commits not in the rebase target set should not be abandoned even if they
2833 // were emptied.
2834 insta::assert_snapshot!(work_dir.run_jj(["log", "-T", "description"]), @r"
2835 @ also already empty
2836 ○ already empty
2837 ○ c (will become empty)
2838 ○ b
2839 ○ a
2840 ◆
2841 [EOF]
2842 ");
2843}
2844
2845#[test]
2846fn test_rebase_skip_if_on_destination() {
2847 let test_env = TestEnvironment::default();
2848 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2849 let work_dir = test_env.work_dir("repo");
2850
2851 create_commit(&work_dir, "a", &[]);
2852 create_commit(&work_dir, "b1", &["a"]);
2853 create_commit(&work_dir, "b2", &["a"]);
2854 create_commit(&work_dir, "c", &["b1", "b2"]);
2855 create_commit(&work_dir, "d", &["c"]);
2856 create_commit(&work_dir, "e", &["c"]);
2857 create_commit(&work_dir, "f", &["e"]);
2858 // Test the setup
2859 insta::assert_snapshot!(get_long_log_output(&work_dir), @r"
2860 @ f lylxulpl 88f778c5: e
2861 ○ e kmkuslsw 48dd9e3f: c
2862 │ ○ d znkkpsqq 92438fc9: c
2863 ├─╯
2864 ○ c vruxwmqv c41e416e: b1 b2
2865 ├─╮
2866 │ ○ b2 royxmykx 903ab0d6: a
2867 ○ │ b1 zsuskuln 072d5ae1: a
2868 ├─╯
2869 ○ a rlvkpnrz 2443ea76
2870 ◆ zzzzzzzz 00000000
2871 [EOF]
2872 ");
2873
2874 // Skip rebase with -b
2875 let output = work_dir.run_jj(["rebase", "-b", "d", "-d", "a"]);
2876 insta::assert_snapshot!(output, @r"
2877 ------- stderr -------
2878 Skipped rebase of 6 commits that were already in place
2879 Nothing changed.
2880 [EOF]
2881 ");
2882 insta::assert_snapshot!(get_long_log_output(&work_dir), @r"
2883 @ f lylxulpl 88f778c5: e
2884 ○ e kmkuslsw 48dd9e3f: c
2885 │ ○ d znkkpsqq 92438fc9: c
2886 ├─╯
2887 ○ c vruxwmqv c41e416e: b1 b2
2888 ├─╮
2889 │ ○ b2 royxmykx 903ab0d6: a
2890 ○ │ b1 zsuskuln 072d5ae1: a
2891 ├─╯
2892 ○ a rlvkpnrz 2443ea76
2893 ◆ zzzzzzzz 00000000
2894 [EOF]
2895 ");
2896
2897 // Skip rebase with -s
2898 let output = work_dir.run_jj(["rebase", "-s", "c", "-d", "b1", "-d", "b2"]);
2899 insta::assert_snapshot!(output, @r"
2900 ------- stderr -------
2901 Skipped rebase of 4 commits that were already in place
2902 Nothing changed.
2903 [EOF]
2904 ");
2905 insta::assert_snapshot!(get_long_log_output(&work_dir), @r"
2906 @ f lylxulpl 88f778c5: e
2907 ○ e kmkuslsw 48dd9e3f: c
2908 │ ○ d znkkpsqq 92438fc9: c
2909 ├─╯
2910 ○ c vruxwmqv c41e416e: b1 b2
2911 ├─╮
2912 │ ○ b2 royxmykx 903ab0d6: a
2913 ○ │ b1 zsuskuln 072d5ae1: a
2914 ├─╯
2915 ○ a rlvkpnrz 2443ea76
2916 ◆ zzzzzzzz 00000000
2917 [EOF]
2918 ");
2919
2920 // Skip rebase with -r since commit has no children
2921 let output = work_dir.run_jj(["rebase", "-r", "d", "-d", "c"]);
2922 insta::assert_snapshot!(output, @r"
2923 ------- stderr -------
2924 Skipped rebase of 1 commits that were already in place
2925 Nothing changed.
2926 [EOF]
2927 ");
2928 insta::assert_snapshot!(get_long_log_output(&work_dir), @r"
2929 @ f lylxulpl 88f778c5: e
2930 ○ e kmkuslsw 48dd9e3f: c
2931 │ ○ d znkkpsqq 92438fc9: c
2932 ├─╯
2933 ○ c vruxwmqv c41e416e: b1 b2
2934 ├─╮
2935 │ ○ b2 royxmykx 903ab0d6: a
2936 ○ │ b1 zsuskuln 072d5ae1: a
2937 ├─╯
2938 ○ a rlvkpnrz 2443ea76
2939 ◆ zzzzzzzz 00000000
2940 [EOF]
2941 ");
2942
2943 // Skip rebase of commit, but rebases children onto destination with -r
2944 let output = work_dir.run_jj(["rebase", "-r", "e", "-d", "c"]);
2945 insta::assert_snapshot!(output, @r"
2946 ------- stderr -------
2947 Skipped rebase of 1 commits that were already in place
2948 Rebased 1 descendant commits
2949 Working copy (@) now at: lylxulpl 77cb229f f | f
2950 Parent commit (@-) : vruxwmqv c41e416e c | c
2951 Added 0 files, modified 0 files, removed 1 files
2952 [EOF]
2953 ");
2954 insta::assert_snapshot!(get_long_log_output(&work_dir), @r"
2955 @ f lylxulpl 77cb229f: c
2956 │ ○ e kmkuslsw 48dd9e3f: c
2957 ├─╯
2958 │ ○ d znkkpsqq 92438fc9: c
2959 ├─╯
2960 ○ c vruxwmqv c41e416e: b1 b2
2961 ├─╮
2962 │ ○ b2 royxmykx 903ab0d6: a
2963 ○ │ b1 zsuskuln 072d5ae1: a
2964 ├─╯
2965 ○ a rlvkpnrz 2443ea76
2966 ◆ zzzzzzzz 00000000
2967 [EOF]
2968 ");
2969}
2970
2971#[must_use]
2972fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
2973 let template = "bookmarks ++ surround(': ', '', parents.map(|c| c.bookmarks()))";
2974 work_dir.run_jj(["log", "-T", template])
2975}
2976
2977#[must_use]
2978fn get_long_log_output(work_dir: &TestWorkDir) -> CommandOutput {
2979 let template = "bookmarks ++ ' ' ++ change_id.shortest(8) ++ ' ' ++ commit_id.shortest(8) \
2980 ++ surround(': ', '', parents.map(|c| c.bookmarks()))";
2981 work_dir.run_jj(["log", "-T", template])
2982}