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_with_files;
16use crate::common::CommandOutput;
17use crate::common::TestEnvironment;
18use crate::common::TestWorkDir;
19
20#[test]
21fn test_restore() {
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 work_dir.write_file("file1", "a\n");
27 work_dir.run_jj(["new"]).success();
28 work_dir.write_file("file2", "b\n");
29 work_dir.run_jj(["new"]).success();
30 work_dir.remove_file("file1");
31 work_dir.write_file("file2", "c\n");
32 work_dir.write_file("file3", "c\n");
33
34 // There is no `-r` argument
35 let output = work_dir.run_jj(["restore", "-r=@-"]);
36 insta::assert_snapshot!(output, @r"
37 ------- stderr -------
38 Error: `jj restore` does not have a `--revision`/`-r` option. If you'd like to modify
39 the *current* revision, use `--from`. If you'd like to modify a *different* revision,
40 use `--into` or `--changes-in`.
41 [EOF]
42 [exit status: 1]
43 ");
44
45 // Restores from parent by default
46 let output = work_dir.run_jj(["restore"]);
47 insta::assert_snapshot!(output, @r"
48 ------- stderr -------
49 Created kkmpptxz 370d81ea (empty) (no description set)
50 Working copy (@) now at: kkmpptxz 370d81ea (empty) (no description set)
51 Parent commit (@-) : rlvkpnrz ef160660 (no description set)
52 Added 1 files, modified 1 files, removed 1 files
53 [EOF]
54 ");
55 let output = work_dir.run_jj(["diff", "-s"]);
56 insta::assert_snapshot!(output, @"");
57
58 // Can restore another revision from its parents
59 work_dir.run_jj(["undo"]).success();
60 let output = work_dir.run_jj(["diff", "-s", "-r=@-"]);
61 insta::assert_snapshot!(output, @r"
62 A file2
63 [EOF]
64 ");
65 let output = work_dir.run_jj(["restore", "-c=@-"]);
66 insta::assert_snapshot!(output, @r"
67 ------- stderr -------
68 Created rlvkpnrz b9b6011e (empty) (no description set)
69 Rebased 1 descendant commits
70 Working copy (@) now at: kkmpptxz 5b361547 (conflict) (no description set)
71 Parent commit (@-) : rlvkpnrz b9b6011e (empty) (no description set)
72 Added 0 files, modified 1 files, removed 0 files
73 Warning: There are unresolved conflicts at these paths:
74 file2 2-sided conflict including 1 deletion
75 New conflicts appeared in 1 commits:
76 kkmpptxz 5b361547 (conflict) (no description set)
77 Hint: To resolve the conflicts, start by updating to it:
78 jj new kkmpptxz
79 Then use `jj resolve`, or edit the conflict markers in the file directly.
80 Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
81 Then run `jj squash` to move the resolution into the conflicted commit.
82 [EOF]
83 ");
84 let output = work_dir.run_jj(["diff", "-s", "-r=@-"]);
85 insta::assert_snapshot!(output, @"");
86
87 // Can restore this revision from another revision
88 work_dir.run_jj(["undo"]).success();
89 let output = work_dir.run_jj(["restore", "--from", "@--"]);
90 insta::assert_snapshot!(output, @r"
91 ------- stderr -------
92 Created kkmpptxz 1154634b (no description set)
93 Working copy (@) now at: kkmpptxz 1154634b (no description set)
94 Parent commit (@-) : rlvkpnrz ef160660 (no description set)
95 Added 1 files, modified 0 files, removed 2 files
96 [EOF]
97 ");
98 let output = work_dir.run_jj(["diff", "-s"]);
99 insta::assert_snapshot!(output, @r"
100 D file2
101 [EOF]
102 ");
103
104 // Can restore into other revision
105 work_dir.run_jj(["undo"]).success();
106 let output = work_dir.run_jj(["restore", "--into", "@-"]);
107 insta::assert_snapshot!(output, @r"
108 ------- stderr -------
109 Created rlvkpnrz ad805965 (no description set)
110 Rebased 1 descendant commits
111 Working copy (@) now at: kkmpptxz 3fcdcbf2 (empty) (no description set)
112 Parent commit (@-) : rlvkpnrz ad805965 (no description set)
113 [EOF]
114 ");
115 let output = work_dir.run_jj(["diff", "-s"]);
116 insta::assert_snapshot!(output, @"");
117 let output = work_dir.run_jj(["diff", "-s", "-r", "@-"]);
118 insta::assert_snapshot!(output, @r"
119 D file1
120 A file2
121 A file3
122 [EOF]
123 ");
124
125 // Can combine `--from` and `--into`
126 work_dir.run_jj(["undo"]).success();
127 let output = work_dir.run_jj(["restore", "--from", "@", "--into", "@-"]);
128 insta::assert_snapshot!(output, @r"
129 ------- stderr -------
130 Created rlvkpnrz f256040a (no description set)
131 Rebased 1 descendant commits
132 Working copy (@) now at: kkmpptxz 9c6f2083 (empty) (no description set)
133 Parent commit (@-) : rlvkpnrz f256040a (no description set)
134 [EOF]
135 ");
136 let output = work_dir.run_jj(["diff", "-s"]);
137 insta::assert_snapshot!(output, @"");
138 let output = work_dir.run_jj(["diff", "-s", "-r", "@-"]);
139 insta::assert_snapshot!(output, @r"
140 D file1
141 A file2
142 A file3
143 [EOF]
144 ");
145
146 // Can restore only specified paths
147 work_dir.run_jj(["undo"]).success();
148 let output = work_dir.run_jj(["restore", "file2", "file3"]);
149 insta::assert_snapshot!(output, @r"
150 ------- stderr -------
151 Created kkmpptxz 4ad35a2f (no description set)
152 Working copy (@) now at: kkmpptxz 4ad35a2f (no description set)
153 Parent commit (@-) : rlvkpnrz ef160660 (no description set)
154 Added 0 files, modified 1 files, removed 1 files
155 [EOF]
156 ");
157 let output = work_dir.run_jj(["diff", "-s"]);
158 insta::assert_snapshot!(output, @r"
159 D file1
160 [EOF]
161 ");
162}
163
164// Much of this test is copied from test_resolve_command
165#[test]
166fn test_restore_conflicted_merge() {
167 let test_env = TestEnvironment::default();
168 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
169 let work_dir = test_env.work_dir("repo");
170
171 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
172 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
173 create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]);
174 create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
175 // Test the setup
176 insta::assert_snapshot!(get_log_output(&work_dir), @r"
177 @ conflict
178 ├─╮
179 │ ○ b
180 ○ │ a
181 ├─╯
182 ○ base
183 ◆
184 [EOF]
185 ");
186 insta::assert_snapshot!(work_dir.read_file("file"), @r"
187 <<<<<<< Conflict 1 of 1
188 %%%%%%% Changes from base to side #1
189 -base
190 +a
191 +++++++ Contents of side #2
192 b
193 >>>>>>> Conflict 1 of 1 ends
194 ");
195
196 // Overwrite the file...
197 work_dir.write_file("file", "resolution");
198 insta::assert_snapshot!(work_dir.run_jj(["diff"]), @r"
199 Resolved conflict in file:
200 1 : <<<<<<< Conflict 1 of 1
201 2 : %%%%%%% Changes from base to side #1
202 3 : -base
203 4 : +a
204 5 : +++++++ Contents of side #2
205 6 : b
206 7 : >>>>>>> Conflict 1 of 1 ends
207 1: resolution
208 [EOF]
209 ");
210
211 // ...and restore it back again.
212 let output = work_dir.run_jj(["restore", "file"]);
213 insta::assert_snapshot!(output, @r"
214 ------- stderr -------
215 Created vruxwmqv 25a37060 conflict | (conflict) (empty) conflict
216 Working copy (@) now at: vruxwmqv 25a37060 conflict | (conflict) (empty) conflict
217 Parent commit (@-) : zsuskuln aa493daf a | a
218 Parent commit (@-) : royxmykx db6a4daf b | b
219 Added 0 files, modified 1 files, removed 0 files
220 Warning: There are unresolved conflicts at these paths:
221 file 2-sided conflict
222 [EOF]
223 ");
224 insta::assert_snapshot!(work_dir.read_file("file"), @r"
225 <<<<<<< Conflict 1 of 1
226 %%%%%%% Changes from base to side #1
227 -base
228 +a
229 +++++++ Contents of side #2
230 b
231 >>>>>>> Conflict 1 of 1 ends
232 ");
233 let output = work_dir.run_jj(["diff"]);
234 insta::assert_snapshot!(output, @"");
235
236 // The same, but without the `file` argument. Overwrite the file...
237 work_dir.write_file("file", "resolution");
238 insta::assert_snapshot!(work_dir.run_jj(["diff"]), @r"
239 Resolved conflict in file:
240 1 : <<<<<<< Conflict 1 of 1
241 2 : %%%%%%% Changes from base to side #1
242 3 : -base
243 4 : +a
244 5 : +++++++ Contents of side #2
245 6 : b
246 7 : >>>>>>> Conflict 1 of 1 ends
247 1: resolution
248 [EOF]
249 ");
250
251 // ... and restore it back again.
252 let output = work_dir.run_jj(["restore"]);
253 insta::assert_snapshot!(output, @r"
254 ------- stderr -------
255 Created vruxwmqv f2c82b9c conflict | (conflict) (empty) conflict
256 Working copy (@) now at: vruxwmqv f2c82b9c conflict | (conflict) (empty) conflict
257 Parent commit (@-) : zsuskuln aa493daf a | a
258 Parent commit (@-) : royxmykx db6a4daf b | b
259 Added 0 files, modified 1 files, removed 0 files
260 Warning: There are unresolved conflicts at these paths:
261 file 2-sided conflict
262 [EOF]
263 ");
264 insta::assert_snapshot!(work_dir.read_file("file"), @r"
265 <<<<<<< Conflict 1 of 1
266 %%%%%%% Changes from base to side #1
267 -base
268 +a
269 +++++++ Contents of side #2
270 b
271 >>>>>>> Conflict 1 of 1 ends
272 ");
273}
274
275#[test]
276fn test_restore_restore_descendants() {
277 let test_env = TestEnvironment::default();
278 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
279 let work_dir = test_env.work_dir("repo");
280
281 create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
282 create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
283 create_commit_with_files(
284 &work_dir,
285 "b",
286 &["base"],
287 &[("file", "b\n"), ("file2", "b\n")],
288 );
289 create_commit_with_files(&work_dir, "ab", &["a", "b"], &[("file", "ab\n")]);
290 // Test the setup
291 insta::assert_snapshot!(get_log_output(&work_dir), @r"
292 @ ab
293 ├─╮
294 │ ○ b
295 ○ │ a
296 ├─╯
297 ○ base
298 ◆
299 [EOF]
300 ");
301 insta::assert_snapshot!(work_dir.read_file("file"), @"ab");
302
303 // Commit "b" was not supposed to modify "file", restore it from its parent
304 // while preserving its child commit content.
305 let output = work_dir.run_jj(["restore", "-c", "b", "file", "--restore-descendants"]);
306 insta::assert_snapshot!(output, @r"
307 ------- stderr -------
308 Created royxmykx 3fd5aa05 b | b
309 Rebased 1 descendant commits (while preserving their content)
310 Working copy (@) now at: vruxwmqv bf5491a0 ab | ab
311 Parent commit (@-) : zsuskuln aa493daf a | a
312 Parent commit (@-) : royxmykx 3fd5aa05 b | b
313 [EOF]
314 ");
315
316 // Check that "a", "b", and "ab" have their expected content by diffing them.
317 // "ab" must have kept its content.
318 insta::assert_snapshot!(work_dir.run_jj(["diff", "--from=a", "--to=ab", "--git"]), @r"
319 diff --git a/file b/file
320 index 7898192261..81bf396956 100644
321 --- a/file
322 +++ b/file
323 @@ -1,1 +1,1 @@
324 -a
325 +ab
326 diff --git a/file2 b/file2
327 new file mode 100644
328 index 0000000000..6178079822
329 --- /dev/null
330 +++ b/file2
331 @@ -0,0 +1,1 @@
332 +b
333 [EOF]
334 ");
335 insta::assert_snapshot!(work_dir.run_jj(["diff", "--from=b", "--to=ab", "--git"]), @r"
336 diff --git a/file b/file
337 index df967b96a5..81bf396956 100644
338 --- a/file
339 +++ b/file
340 @@ -1,1 +1,1 @@
341 -base
342 +ab
343 [EOF]
344 ");
345}
346
347#[test]
348fn test_restore_interactive() {
349 let mut test_env = TestEnvironment::default();
350 let diff_editor = test_env.set_up_fake_diff_editor();
351 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
352 let work_dir = test_env.work_dir("repo");
353
354 create_commit_with_files(&work_dir, "a", &[], &[("file1", "a1\n"), ("file2", "a2\n")]);
355 create_commit_with_files(
356 &work_dir,
357 "b",
358 &["a"],
359 &[("file1", "b1\n"), ("file2", "b2\n"), ("file3", "b3\n")],
360 );
361 let output = work_dir.run_jj(["log", "--summary"]);
362 insta::assert_snapshot!(output, @r"
363 @ zsuskuln test.user@example.com 2001-02-03 08:05:11 b c0745ce2
364 │ b
365 │ M file1
366 │ M file2
367 │ A file3
368 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 186caaef
369 │ a
370 │ A file1
371 │ A file2
372 ◆ zzzzzzzz root() 00000000
373 [EOF]
374 ");
375
376 let diff_script = [
377 "files-before file1 file2 file3",
378 "files-after JJ-INSTRUCTIONS file1 file2",
379 "reset file2",
380 "dump JJ-INSTRUCTIONS instrs",
381 ]
382 .join("\0");
383 std::fs::write(diff_editor, diff_script).unwrap();
384
385 // Restore file1 and file3
386 let output = work_dir.run_jj(["restore", "-i", "--from=@-"]);
387 insta::assert_snapshot!(output, @r"
388 ------- stderr -------
389 Created zsuskuln bccde490 b | b
390 Working copy (@) now at: zsuskuln bccde490 b | b
391 Parent commit (@-) : rlvkpnrz 186caaef a | a
392 Added 0 files, modified 1 files, removed 1 files
393 [EOF]
394 ");
395
396 insta::assert_snapshot!(
397 std::fs::read_to_string(test_env.env_root().join("instrs")).unwrap(), @r"
398 You are restoring changes from: rlvkpnrz 186caaef a | a
399 to commit: zsuskuln c0745ce2 b | b
400
401 The diff initially shows all changes restored. Adjust the right side until it
402 shows the contents you want for the destination commit.
403 ");
404
405 let output = work_dir.run_jj(["log", "--summary"]);
406 insta::assert_snapshot!(output, @r"
407 @ zsuskuln test.user@example.com 2001-02-03 08:05:13 b bccde490
408 │ b
409 │ M file2
410 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 186caaef
411 │ a
412 │ A file1
413 │ A file2
414 ◆ zzzzzzzz root() 00000000
415 [EOF]
416 ");
417
418 // Try again with --tool, which should imply --interactive
419 work_dir.run_jj(["undo"]).success();
420 let output = work_dir.run_jj(["restore", "--tool=fake-diff-editor"]);
421 insta::assert_snapshot!(output, @r"
422 ------- stderr -------
423 Created zsuskuln 5921de19 b | b
424 Working copy (@) now at: zsuskuln 5921de19 b | b
425 Parent commit (@-) : rlvkpnrz 186caaef a | a
426 Added 0 files, modified 1 files, removed 1 files
427 [EOF]
428 ");
429
430 let output = work_dir.run_jj(["log", "--summary"]);
431 insta::assert_snapshot!(output, @r"
432 @ zsuskuln test.user@example.com 2001-02-03 08:05:16 b 5921de19
433 │ b
434 │ M file2
435 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 186caaef
436 │ a
437 │ A file1
438 │ A file2
439 ◆ zzzzzzzz root() 00000000
440 [EOF]
441 ");
442}
443
444#[test]
445fn test_restore_interactive_merge() {
446 let mut test_env = TestEnvironment::default();
447 let diff_editor = test_env.set_up_fake_diff_editor();
448 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
449 let work_dir = test_env.work_dir("repo");
450
451 create_commit_with_files(&work_dir, "a", &[], &[("file1", "a1\n")]);
452 create_commit_with_files(&work_dir, "b", &[], &[("file2", "b1\n")]);
453 create_commit_with_files(
454 &work_dir,
455 "c",
456 &["a", "b"],
457 &[("file1", "c1\n"), ("file2", "c2\n"), ("file3", "c3\n")],
458 );
459 let output = work_dir.run_jj(["log", "--summary"]);
460 insta::assert_snapshot!(output, @r"
461 @ royxmykx test.user@example.com 2001-02-03 08:05:13 c 34042291
462 ├─╮ c
463 │ │ M file1
464 │ │ M file2
465 │ │ A file3
466 │ ○ zsuskuln test.user@example.com 2001-02-03 08:05:11 b 29e70804
467 │ │ b
468 │ │ A file2
469 ○ │ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 79c1b823
470 ├─╯ a
471 │ A file1
472 ◆ zzzzzzzz root() 00000000
473 [EOF]
474 ");
475
476 let diff_script = [
477 "files-before file1 file2 file3",
478 "files-after JJ-INSTRUCTIONS file1 file2",
479 "reset file2",
480 "dump JJ-INSTRUCTIONS instrs",
481 ]
482 .join("\0");
483 std::fs::write(diff_editor, diff_script).unwrap();
484
485 // Restore file1 and file3
486 let output = work_dir.run_jj(["restore", "-i"]);
487 insta::assert_snapshot!(output, @r"
488 ------- stderr -------
489 Created royxmykx 72e0cbf4 c | c
490 Working copy (@) now at: royxmykx 72e0cbf4 c | c
491 Parent commit (@-) : rlvkpnrz 79c1b823 a | a
492 Parent commit (@-) : zsuskuln 29e70804 b | b
493 Added 0 files, modified 1 files, removed 1 files
494 [EOF]
495 ");
496
497 insta::assert_snapshot!(
498 std::fs::read_to_string(test_env.env_root().join("instrs")).unwrap(), @r"
499 You are restoring changes from: rlvkpnrz 79c1b823 a | a
500 zsuskuln 29e70804 b | b
501 to commit: royxmykx 34042291 c | c
502
503 The diff initially shows all changes restored. Adjust the right side until it
504 shows the contents you want for the destination commit.
505 ");
506
507 let output = work_dir.run_jj(["log", "--summary"]);
508 insta::assert_snapshot!(output, @r"
509 @ royxmykx test.user@example.com 2001-02-03 08:05:15 c 72e0cbf4
510 ├─╮ c
511 │ │ M file2
512 │ ○ zsuskuln test.user@example.com 2001-02-03 08:05:11 b 29e70804
513 │ │ b
514 │ │ A file2
515 ○ │ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 79c1b823
516 ├─╯ a
517 │ A file1
518 ◆ zzzzzzzz root() 00000000
519 [EOF]
520 ");
521}
522
523#[test]
524fn test_restore_interactive_with_paths() {
525 let mut test_env = TestEnvironment::default();
526 let diff_editor = test_env.set_up_fake_diff_editor();
527 test_env.run_jj_in(".", ["git", "init", "repo"]).success();
528 let work_dir = test_env.work_dir("repo");
529
530 create_commit_with_files(&work_dir, "a", &[], &[("file1", "a1\n"), ("file2", "a2\n")]);
531 create_commit_with_files(
532 &work_dir,
533 "b",
534 &["a"],
535 &[("file1", "b1\n"), ("file2", "b2\n"), ("file3", "b3\n")],
536 );
537 let output = work_dir.run_jj(["log", "--summary"]);
538 insta::assert_snapshot!(output, @r"
539 @ zsuskuln test.user@example.com 2001-02-03 08:05:11 b c0745ce2
540 │ b
541 │ M file1
542 │ M file2
543 │ A file3
544 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 186caaef
545 │ a
546 │ A file1
547 │ A file2
548 ◆ zzzzzzzz root() 00000000
549 [EOF]
550 ");
551
552 let diff_script = [
553 "files-before file1 file2",
554 "files-after JJ-INSTRUCTIONS file1 file2",
555 "reset file2",
556 ]
557 .join("\0");
558 std::fs::write(diff_editor, diff_script).unwrap();
559
560 // Restore file1 (file2 is reset by interactive editor)
561 let output = work_dir.run_jj(["restore", "-i", "file1", "file2"]);
562 insta::assert_snapshot!(output, @r"
563 ------- stderr -------
564 Created zsuskuln 7187da33 b | b
565 Working copy (@) now at: zsuskuln 7187da33 b | b
566 Parent commit (@-) : rlvkpnrz 186caaef a | a
567 Added 0 files, modified 1 files, removed 0 files
568 [EOF]
569 ");
570
571 let output = work_dir.run_jj(["log", "--summary"]);
572 insta::assert_snapshot!(output, @r"
573 @ zsuskuln test.user@example.com 2001-02-03 08:05:13 b 7187da33
574 │ b
575 │ M file2
576 │ A file3
577 ○ rlvkpnrz test.user@example.com 2001-02-03 08:05:09 a 186caaef
578 │ a
579 │ A file1
580 │ A file2
581 ◆ zzzzzzzz root() 00000000
582 [EOF]
583 ");
584}
585
586#[must_use]
587fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
588 work_dir.run_jj(["log", "-T", "bookmarks"])
589}