just playing with tangled
1// Copyright 2023 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_basics() {
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 create_commit(&work_dir, "c", &[]);
29 create_commit(&work_dir, "d", &["c"]);
30 create_commit(&work_dir, "e", &["a", "d"]);
31 // Test the setup
32 insta::assert_snapshot!(get_log_output(&work_dir), @r"
33 @ [znk] e
34 ├─╮
35 │ ○ [vru] d
36 │ ○ [roy] c
37 │ │ ○ [zsu] b
38 ├───╯
39 ○ │ [rlv] a
40 ├─╯
41 ◆ [zzz]
42 [EOF]
43 ");
44
45 let output = work_dir.run_jj(["abandon", "--retain-bookmarks", "d"]);
46 insta::assert_snapshot!(output, @r"
47 ------- stderr -------
48 Abandoned 1 commits:
49 vruxwmqv b7c62f28 d | d
50 Rebased 1 descendant commits onto parents of abandoned commits
51 Working copy (@) now at: znkkpsqq 11a2e10e e | e
52 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
53 Parent commit (@-) : royxmykx fe2e8e8b c d | c
54 Added 0 files, modified 0 files, removed 1 files
55 [EOF]
56 ");
57 insta::assert_snapshot!(get_log_output(&work_dir), @r"
58 @ [znk] e
59 ├─╮
60 │ ○ [roy] c d
61 │ │ ○ [zsu] b
62 ├───╯
63 ○ │ [rlv] a
64 ├─╯
65 ◆ [zzz]
66 [EOF]
67 ");
68
69 work_dir.run_jj(["undo"]).success();
70 let output = work_dir.run_jj(["abandon", "--retain-bookmarks"]); // abandons `e`
71 insta::assert_snapshot!(output, @r"
72 ------- stderr -------
73 Abandoned 1 commits:
74 znkkpsqq 5557ece3 e | e
75 Working copy (@) now at: nkmrtpmo d4f8ea73 (empty) (no description set)
76 Parent commit (@-) : rlvkpnrz 2443ea76 a e?? | a
77 Parent commit (@-) : vruxwmqv b7c62f28 d e?? | d
78 Added 0 files, modified 0 files, removed 1 files
79 [EOF]
80 ");
81 insta::assert_snapshot!(get_log_output(&work_dir), @r"
82 @ [nkm]
83 ├─╮
84 │ ○ [vru] d e??
85 │ ○ [roy] c
86 │ │ ○ [zsu] b
87 ├───╯
88 ○ │ [rlv] a e??
89 ├─╯
90 ◆ [zzz]
91 [EOF]
92 ");
93
94 work_dir.run_jj(["undo"]).success();
95 let output = work_dir.run_jj(["abandon", "descendants(d)"]);
96 insta::assert_snapshot!(output, @r"
97 ------- stderr -------
98 Abandoned 2 commits:
99 znkkpsqq 5557ece3 e | e
100 vruxwmqv b7c62f28 d | d
101 Deleted bookmarks: d, e
102 Working copy (@) now at: xtnwkqum fa4ee8e6 (empty) (no description set)
103 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
104 Parent commit (@-) : royxmykx fe2e8e8b c | c
105 Added 0 files, modified 0 files, removed 2 files
106 [EOF]
107 ");
108 insta::assert_snapshot!(get_log_output(&work_dir), @r"
109 @ [xtn]
110 ├─╮
111 │ ○ [roy] c
112 │ │ ○ [zsu] b
113 ├───╯
114 ○ │ [rlv] a
115 ├─╯
116 ◆ [zzz]
117 [EOF]
118 ");
119
120 // Test abandoning the same commit twice directly
121 work_dir.run_jj(["undo"]).success();
122 let output = work_dir.run_jj(["abandon", "-rb", "b"]);
123 insta::assert_snapshot!(output, @r"
124 ------- stderr -------
125 Abandoned 1 commits:
126 zsuskuln 1394f625 b | b
127 Deleted bookmarks: b
128 [EOF]
129 ");
130 insta::assert_snapshot!(get_log_output(&work_dir), @r"
131 @ [znk] e
132 ├─╮
133 │ ○ [vru] d
134 │ ○ [roy] c
135 ○ │ [rlv] a
136 ├─╯
137 ◆ [zzz]
138 [EOF]
139 ");
140
141 // Test abandoning the same commit twice indirectly
142 work_dir.run_jj(["undo"]).success();
143 let output = work_dir.run_jj(["abandon", "d::", "e"]);
144 insta::assert_snapshot!(output, @r"
145 ------- stderr -------
146 Abandoned 2 commits:
147 znkkpsqq 5557ece3 e | e
148 vruxwmqv b7c62f28 d | d
149 Deleted bookmarks: d, e
150 Working copy (@) now at: xlzxqlsl 14991aec (empty) (no description set)
151 Parent commit (@-) : rlvkpnrz 2443ea76 a | a
152 Parent commit (@-) : royxmykx fe2e8e8b c | c
153 Added 0 files, modified 0 files, removed 2 files
154 [EOF]
155 ");
156 insta::assert_snapshot!(get_log_output(&work_dir), @r"
157 @ [xlz]
158 ├─╮
159 │ ○ [roy] c
160 │ │ ○ [zsu] b
161 ├───╯
162 ○ │ [rlv] a
163 ├─╯
164 ◆ [zzz]
165 [EOF]
166 ");
167
168 let output = work_dir.run_jj(["abandon", "none()"]);
169 insta::assert_snapshot!(output, @r"
170 ------- stderr -------
171 No revisions to abandon.
172 [EOF]
173 ");
174}
175
176#[test]
177fn test_abandon_many() {
178 let test_env = TestEnvironment::default();
179 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
180 let work_dir = test_env.work_dir("repo");
181
182 for i in 0..10 {
183 work_dir.run_jj(["new", &format!("-mcommit{i}")]).success();
184 }
185
186 // The list of commits should be elided.
187 let output = work_dir.run_jj(["abandon", ".."]);
188 insta::assert_snapshot!(output, @r"
189 ------- stderr -------
190 Abandoned 11 commits:
191 kpqxywon 0b998aa3 (empty) commit9
192 znkkpsqq c37abefb (empty) commit8
193 yostqsxw 6256698f (empty) commit7
194 vruxwmqv 9350f605 (empty) commit6
195 yqosqzyt 196bd23d (empty) commit5
196 royxmykx bb676781 (empty) commit4
197 mzvwutvl 6f1e55a6 (empty) commit3
198 zsuskuln baf1311c (empty) commit2
199 kkmpptxz 5fc5f374 (empty) commit1
200 rlvkpnrz 9451b4ea (empty) commit0
201 ...
202 Working copy (@) now at: kmkuslsw 822a2cf5 (empty) (no description set)
203 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
204 [EOF]
205 ");
206}
207
208// This behavior illustrates https://github.com/jj-vcs/jj/issues/2600.
209// See also the corresponding test in `test_rebase_command`
210#[test]
211fn test_bug_2600() {
212 let test_env = TestEnvironment::default();
213 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
214 let work_dir = test_env.work_dir("repo");
215
216 // We will not touch "nottherootcommit". See the
217 // `test_bug_2600_rootcommit_special_case` for the one case where base being the
218 // child of the root commit changes the expected behavior.
219 create_commit(&work_dir, "nottherootcommit", &[]);
220 create_commit(&work_dir, "base", &["nottherootcommit"]);
221 create_commit(&work_dir, "a", &["base"]);
222 create_commit(&work_dir, "b", &["base", "a"]);
223 create_commit(&work_dir, "c", &["b"]);
224
225 // Test the setup
226 insta::assert_snapshot!(get_log_output(&work_dir), @r"
227 @ [znk] c
228 ○ [vru] b
229 ├─╮
230 │ ○ [roy] a
231 ├─╯
232 ○ [zsu] base
233 ○ [rlv] nottherootcommit
234 ◆ [zzz]
235 [EOF]
236 ");
237 let setup_opid = work_dir.current_operation_id();
238
239 work_dir.run_jj(["op", "restore", &setup_opid]).success();
240 let output = work_dir.run_jj(["abandon", "base"]);
241 insta::assert_snapshot!(output, @r"
242 ------- stderr -------
243 Abandoned 1 commits:
244 zsuskuln 73c929fc base | base
245 Deleted bookmarks: base
246 Rebased 3 descendant commits onto parents of abandoned commits
247 Working copy (@) now at: znkkpsqq 86e31bec c | c
248 Parent commit (@-) : vruxwmqv fd6eb121 b | b
249 Added 0 files, modified 0 files, removed 1 files
250 [EOF]
251 ");
252 // Commits "a" and "b" should both have "nottherootcommit" as parent, and "b"
253 // should keep "a" as second parent.
254 insta::assert_snapshot!(get_log_output(&work_dir), @r"
255 @ [znk] c
256 ○ [vru] b
257 ├─╮
258 │ ○ [roy] a
259 ├─╯
260 ○ [rlv] nottherootcommit
261 ◆ [zzz]
262 [EOF]
263 ");
264
265 work_dir.run_jj(["op", "restore", &setup_opid]).success();
266 let output = work_dir.run_jj(["abandon", "a"]);
267 insta::assert_snapshot!(output, @r"
268 ------- stderr -------
269 Abandoned 1 commits:
270 royxmykx 98f3b9ba a | a
271 Deleted bookmarks: a
272 Rebased 2 descendant commits onto parents of abandoned commits
273 Working copy (@) now at: znkkpsqq 683b9435 c | c
274 Parent commit (@-) : vruxwmqv c10cb7b4 b | b
275 Added 0 files, modified 0 files, removed 1 files
276 [EOF]
277 ");
278 // Commit "b" should have "base" as parent. It should not have two parent
279 // pointers to that commit even though it was a merge commit before we abandoned
280 // "a".
281 insta::assert_snapshot!(get_log_output(&work_dir), @r"
282 @ [znk] c
283 ○ [vru] b
284 ○ [zsu] base
285 ○ [rlv] nottherootcommit
286 ◆ [zzz]
287 [EOF]
288 ");
289
290 work_dir.run_jj(["op", "restore", &setup_opid]).success();
291 let output = work_dir.run_jj(["abandon", "b"]);
292 insta::assert_snapshot!(output, @r"
293 ------- stderr -------
294 Abandoned 1 commits:
295 vruxwmqv 8c0dced0 b | b
296 Deleted bookmarks: b
297 Rebased 1 descendant commits onto parents of abandoned commits
298 Working copy (@) now at: znkkpsqq 33a94991 c | c
299 Parent commit (@-) : zsuskuln 73c929fc base | base
300 Parent commit (@-) : royxmykx 98f3b9ba a | a
301 Added 0 files, modified 0 files, removed 1 files
302 [EOF]
303 ");
304 // Commit "c" should inherit the parents from the abndoned commit "b".
305 insta::assert_snapshot!(get_log_output(&work_dir), @r"
306 @ [znk] c
307 ├─╮
308 │ ○ [roy] a
309 ├─╯
310 ○ [zsu] base
311 ○ [rlv] nottherootcommit
312 ◆ [zzz]
313 [EOF]
314 ");
315
316 work_dir.run_jj(["op", "restore", &setup_opid]).success();
317 // ========= Reminder of the setup ===========
318 insta::assert_snapshot!(get_log_output(&work_dir), @r"
319 @ [znk] c
320 ○ [vru] b
321 ├─╮
322 │ ○ [roy] a
323 ├─╯
324 ○ [zsu] base
325 ○ [rlv] nottherootcommit
326 ◆ [zzz]
327 [EOF]
328 ");
329 let output = work_dir.run_jj(["abandon", "--retain-bookmarks", "a", "b"]);
330 insta::assert_snapshot!(output, @r"
331 ------- stderr -------
332 Abandoned 2 commits:
333 vruxwmqv 8c0dced0 b | b
334 royxmykx 98f3b9ba a | a
335 Rebased 1 descendant commits onto parents of abandoned commits
336 Working copy (@) now at: znkkpsqq 84fac1f8 c | c
337 Parent commit (@-) : zsuskuln 73c929fc a b base | base
338 Added 0 files, modified 0 files, removed 2 files
339 [EOF]
340 ");
341 // Commit "c" should have "base" as parent. As when we abandoned "a", it should
342 // not have two parent pointers to the same commit.
343 insta::assert_snapshot!(get_log_output(&work_dir), @r"
344 @ [znk] c
345 ○ [zsu] a b base
346 ○ [rlv] nottherootcommit
347 ◆ [zzz]
348 [EOF]
349 ");
350 let output = work_dir.run_jj(["bookmark", "list", "b"]);
351 insta::assert_snapshot!(output, @r"
352 b: zsuskuln 73c929fc base
353 [EOF]
354 ");
355}
356
357#[test]
358fn test_bug_2600_rootcommit_special_case() {
359 let test_env = TestEnvironment::default();
360 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
361 let work_dir = test_env.work_dir("repo");
362
363 // Set up like `test_bug_2600`, but without the `nottherootcommit` commit.
364 create_commit(&work_dir, "base", &[]);
365 create_commit(&work_dir, "a", &["base"]);
366 create_commit(&work_dir, "b", &["base", "a"]);
367 create_commit(&work_dir, "c", &["b"]);
368
369 // Setup
370 insta::assert_snapshot!(get_log_output(&work_dir), @r"
371 @ [vru] c
372 ○ [roy] b
373 ├─╮
374 │ ○ [zsu] a
375 ├─╯
376 ○ [rlv] base
377 ◆ [zzz]
378 [EOF]
379 ");
380
381 // Now, the test
382 let output = work_dir.run_jj(["abandon", "base"]);
383 insta::assert_snapshot!(output, @r"
384 ------- stderr -------
385 Error: The Git backend does not support creating merge commits with the root commit as one of the parents.
386 [EOF]
387 [exit status: 1]
388 ");
389}
390
391#[test]
392fn test_double_abandon() {
393 let test_env = TestEnvironment::default();
394 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
395 let work_dir = test_env.work_dir("repo");
396
397 create_commit(&work_dir, "a", &[]);
398 // Test the setup
399 insta::assert_snapshot!(work_dir.run_jj(["log", "--no-graph", "-r", "a"]), @r"
400 rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 2443ea76
401 a
402 [EOF]
403 ");
404
405 let commit_id = work_dir
406 .run_jj(["log", "--no-graph", "--color=never", "-T=commit_id", "-r=a"])
407 .success()
408 .stdout
409 .into_raw();
410
411 let output = work_dir.run_jj(["abandon", &commit_id]);
412 insta::assert_snapshot!(output, @r"
413 ------- stderr -------
414 Abandoned 1 commits:
415 rlvkpnrz 2443ea76 a | a
416 Deleted bookmarks: a
417 Working copy (@) now at: royxmykx f37b4afd (empty) (no description set)
418 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
419 Added 0 files, modified 0 files, removed 1 files
420 [EOF]
421 ");
422 let output = work_dir.run_jj(["abandon", &commit_id]);
423 insta::assert_snapshot!(output, @r"
424 ------- stderr -------
425 Abandoned 1 commits:
426 rlvkpnrz hidden 2443ea76 a
427 Nothing changed.
428 [EOF]
429 ");
430}
431
432#[test]
433fn test_abandon_restore_descendants() {
434 let test_env = TestEnvironment::default();
435 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
436 let work_dir = test_env.work_dir("repo");
437
438 work_dir.write_file("file", "foo\n");
439 work_dir.run_jj(["new"]).success();
440 work_dir.write_file("file", "bar\n");
441 work_dir.run_jj(["new"]).success();
442 work_dir.write_file("file", "baz\n");
443
444 // Remove the commit containing "bar"
445 let output = work_dir.run_jj(["abandon", "-r@-", "--restore-descendants"]);
446 insta::assert_snapshot!(output, @r"
447 ------- stderr -------
448 Abandoned 1 commits:
449 rlvkpnrz 225adef1 (no description set)
450 Rebased 1 descendant commits (while preserving their content) onto parents of abandoned commits
451 Working copy (@) now at: kkmpptxz a734deb0 (no description set)
452 Parent commit (@-) : qpvuntsm 485d52a9 (no description set)
453 [EOF]
454 ");
455 let output = work_dir.run_jj(["diff", "--git"]);
456 insta::assert_snapshot!(output, @r"
457 diff --git a/file b/file
458 index 257cc5642c..76018072e0 100644
459 --- a/file
460 +++ b/file
461 @@ -1,1 +1,1 @@
462 -foo
463 +baz
464 [EOF]
465 ");
466}
467
468#[test]
469fn test_abandon_tracking_bookmarks() {
470 let test_env = TestEnvironment::default();
471
472 test_env.run_jj_in(".", ["git", "init", "remote"]).success();
473 let remote_dir = test_env.work_dir("remote");
474 remote_dir
475 .run_jj(["bookmark", "set", "-r@", "foo"])
476 .success();
477 remote_dir.run_jj(["git", "export"]).success();
478
479 // Create colocated Git repo which may have @git tracking bookmarks
480 test_env
481 .run_jj_in(
482 ".",
483 [
484 "git",
485 "clone",
486 "--colocate",
487 "--config=git.auto-local-bookmark=true",
488 "remote/.jj/repo/store/git",
489 "local",
490 ],
491 )
492 .success();
493 let local_dir = test_env.work_dir("local");
494 local_dir
495 .run_jj(["bookmark", "set", "-r@", "bar"])
496 .success();
497 insta::assert_snapshot!(get_log_output(&local_dir), @r"
498 @ [zsu] bar
499 │ ○ [vvk] foo
500 ├─╯
501 ◆ [zzz]
502 [EOF]
503 ");
504
505 let output = local_dir.run_jj(["abandon", "foo"]);
506 insta::assert_snapshot!(output, @r"
507 ------- stderr -------
508 Abandoned 1 commits:
509 vvkvtnvv 230dd059 foo | (empty) (no description set)
510 Deleted bookmarks: foo
511 Warning: Remote bookmarks tracked by deleted bookmarks will be deleted on the next `jj git push`.
512 [EOF]
513 ");
514 let output = local_dir.run_jj(["abandon", "bar"]);
515 insta::assert_snapshot!(output, @r"
516 ------- stderr -------
517 Abandoned 1 commits:
518 zsuskuln f652c321 bar | (empty) (no description set)
519 Deleted bookmarks: bar
520 Working copy (@) now at: vruxwmqv 41658cf4 (empty) (no description set)
521 Parent commit (@-) : zzzzzzzz 00000000 (empty) (no description set)
522 [EOF]
523 ");
524}
525
526#[must_use]
527fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
528 let template = r#"separate(" ", "[" ++ change_id.short(3) ++ "]", bookmarks)"#;
529 work_dir.run_jj(["log", "-T", template])
530}