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::{escaped_fake_diff_editor_path, TestEnvironment};
16
17#[test]
18fn test_diffedit() {
19 let mut test_env = TestEnvironment::default();
20 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
21 let repo_path = test_env.env_root().join("repo");
22
23 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
24 test_env.jj_cmd_ok(&repo_path, &["new"]);
25 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
26 std::fs::write(repo_path.join("file3"), "a\n").unwrap();
27 test_env.jj_cmd_ok(&repo_path, &["new"]);
28 std::fs::remove_file(repo_path.join("file1")).unwrap();
29 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
30
31 let edit_script = test_env.set_up_fake_diff_editor();
32
33 // Test the setup; nothing happens if we make no changes
34 std::fs::write(
35 &edit_script,
36 "files-before file1 file2\0files-after JJ-INSTRUCTIONS file2",
37 )
38 .unwrap();
39 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit"]);
40 insta::assert_snapshot!(stdout, @"");
41 insta::assert_snapshot!(stderr, @r###"
42 Nothing changed.
43 "###);
44 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
45 insta::assert_snapshot!(stdout, @r###"
46 D file1
47 M file2
48 "###);
49
50 // Try again with ui.diff-instructions=false
51 std::fs::write(&edit_script, "files-before file1 file2\0files-after file2").unwrap();
52 let (stdout, stderr) = test_env.jj_cmd_ok(
53 &repo_path,
54 &["diffedit", "--config-toml=ui.diff-instructions=false"],
55 );
56 insta::assert_snapshot!(stdout, @"");
57 insta::assert_snapshot!(stderr, @r###"
58 Nothing changed.
59 "###);
60 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
61 insta::assert_snapshot!(stdout, @r###"
62 D file1
63 M file2
64 "###);
65
66 // Try again with --tool=<name>
67 std::fs::write(
68 &edit_script,
69 "files-before file1 file2\0files-after JJ-INSTRUCTIONS file2",
70 )
71 .unwrap();
72 let (stdout, stderr) = test_env.jj_cmd_ok(
73 &repo_path,
74 &[
75 "diffedit",
76 "--config-toml=ui.diff-editor='false'",
77 "--tool=fake-diff-editor",
78 ],
79 );
80 insta::assert_snapshot!(stdout, @"");
81 insta::assert_snapshot!(stderr, @r###"
82 Nothing changed.
83 "###);
84 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
85 insta::assert_snapshot!(stdout, @r###"
86 D file1
87 M file2
88 "###);
89
90 // Nothing happens if the diff-editor exits with an error
91 std::fs::write(&edit_script, "rm file2\0fail").unwrap();
92 insta::assert_snapshot!(&test_env.jj_cmd_failure(&repo_path, &["diffedit"]), @r###"
93 Error: Failed to edit diff
94 Caused by: Tool exited with a non-zero code (run with --debug to see the exact invocation). Exit code: 1.
95 "###);
96 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
97 insta::assert_snapshot!(stdout, @r###"
98 D file1
99 M file2
100 "###);
101
102 // Can edit changes to individual files
103 std::fs::write(&edit_script, "reset file2").unwrap();
104 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit"]);
105 insta::assert_snapshot!(stdout, @"");
106 insta::assert_snapshot!(stderr, @r###"
107 Created kkmpptxz cc387f43 (no description set)
108 Working copy now at: kkmpptxz cc387f43 (no description set)
109 Parent commit : rlvkpnrz 613028a4 (no description set)
110 Added 0 files, modified 1 files, removed 0 files
111 "###);
112 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
113 insta::assert_snapshot!(stdout, @r###"
114 D file1
115 "###);
116
117 // Changes to a commit are propagated to descendants
118 test_env.jj_cmd_ok(&repo_path, &["undo"]);
119 std::fs::write(&edit_script, "write file3\nmodified\n").unwrap();
120 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit", "-r", "@-"]);
121 insta::assert_snapshot!(stdout, @"");
122 insta::assert_snapshot!(stderr, @r###"
123 Created rlvkpnrz d842b979 (no description set)
124 Rebased 1 descendant commits
125 Working copy now at: kkmpptxz bc2b2dd6 (no description set)
126 Parent commit : rlvkpnrz d842b979 (no description set)
127 Added 0 files, modified 1 files, removed 0 files
128 "###);
129 let contents = String::from_utf8(std::fs::read(repo_path.join("file3")).unwrap()).unwrap();
130 insta::assert_snapshot!(contents, @r###"
131 modified
132 "###);
133
134 // Test diffedit --from @--
135 test_env.jj_cmd_ok(&repo_path, &["undo"]);
136 std::fs::write(
137 &edit_script,
138 "files-before file1\0files-after JJ-INSTRUCTIONS file2 file3\0reset file2",
139 )
140 .unwrap();
141 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit", "--from", "@--"]);
142 insta::assert_snapshot!(stdout, @"");
143 insta::assert_snapshot!(stderr, @r###"
144 Created kkmpptxz d78a207f (no description set)
145 Working copy now at: kkmpptxz d78a207f (no description set)
146 Parent commit : rlvkpnrz 613028a4 (no description set)
147 Added 0 files, modified 0 files, removed 1 files
148 "###);
149 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
150 insta::assert_snapshot!(stdout, @r###"
151 D file1
152 D file2
153 "###);
154}
155
156#[test]
157fn test_diffedit_new_file() {
158 let mut test_env = TestEnvironment::default();
159 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
160 let repo_path = test_env.env_root().join("repo");
161
162 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
163 test_env.jj_cmd_ok(&repo_path, &["new"]);
164 std::fs::remove_file(repo_path.join("file1")).unwrap();
165 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
166
167 let edit_script = test_env.set_up_fake_diff_editor();
168
169 // Test the setup; nothing happens if we make no changes
170 std::fs::write(
171 &edit_script,
172 "files-before file1\0files-after JJ-INSTRUCTIONS file2",
173 )
174 .unwrap();
175 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit"]);
176 insta::assert_snapshot!(stdout, @"");
177 insta::assert_snapshot!(stderr, @r###"
178 Nothing changed.
179 "###);
180 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
181 insta::assert_snapshot!(stdout, @r###"
182 D file1
183 A file2
184 "###);
185
186 // Creating `file1` on the right side is noticed by `jj diffedit`
187 std::fs::write(&edit_script, "write file1\nmodified\n").unwrap();
188 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit"]);
189 insta::assert_snapshot!(stdout, @"");
190 insta::assert_snapshot!(stderr, @r###"
191 Created rlvkpnrz 7b849299 (no description set)
192 Working copy now at: rlvkpnrz 7b849299 (no description set)
193 Parent commit : qpvuntsm 414e1614 (no description set)
194 Added 1 files, modified 0 files, removed 0 files
195 "###);
196 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
197 insta::assert_snapshot!(stdout, @r###"
198 M file1
199 A file2
200 "###);
201
202 // Creating a file that wasn't on either side is ignored by diffedit.
203 // TODO(ilyagr) We should decide whether we like this behavior.
204 //
205 // On one hand, it is unexpected and potentially a minor BUG. On the other
206 // hand, this prevents `jj` from loading any backup files the merge tool
207 // generates.
208 test_env.jj_cmd_ok(&repo_path, &["undo"]);
209 std::fs::write(&edit_script, "write new_file\nnew file\n").unwrap();
210 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit"]);
211 insta::assert_snapshot!(stdout, @"");
212 insta::assert_snapshot!(stderr, @r###"
213 Nothing changed.
214 "###);
215 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
216 insta::assert_snapshot!(stdout, @r###"
217 D file1
218 A file2
219 "###);
220}
221
222#[test]
223fn test_diffedit_3pane() {
224 let mut test_env = TestEnvironment::default();
225 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
226 let repo_path = test_env.env_root().join("repo");
227
228 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
229 test_env.jj_cmd_ok(&repo_path, &["new"]);
230 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
231 std::fs::write(repo_path.join("file3"), "a\n").unwrap();
232 test_env.jj_cmd_ok(&repo_path, &["new"]);
233 std::fs::remove_file(repo_path.join("file1")).unwrap();
234 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
235
236 // 2 configs for a 3-pane setup. In the first, "$right" is passed to what the
237 // fake diff editor considers the "after" state.
238 let config_with_right_as_after = format!(
239 r#"ui.diff-editor=["{}", "$left", "$right", "--ignore=$output"]"#,
240 escaped_fake_diff_editor_path()
241 );
242 let config_with_output_as_after = format!(
243 r#"ui.diff-editor=["{}", "$left", "$output", "--ignore=$right"]"#,
244 escaped_fake_diff_editor_path()
245 );
246 let edit_script = test_env.env_root().join("diff_edit_script");
247 std::fs::write(&edit_script, "").unwrap();
248 test_env.add_env_var("DIFF_EDIT_SCRIPT", edit_script.to_str().unwrap());
249
250 // Nothing happens if we make no changes
251 std::fs::write(
252 &edit_script,
253 "files-before file1 file2\0files-after JJ-INSTRUCTIONS file2",
254 )
255 .unwrap();
256 let (stdout, stderr) = test_env.jj_cmd_ok(
257 &repo_path,
258 &["diffedit", "--config-toml", &config_with_output_as_after],
259 );
260 insta::assert_snapshot!(stdout, @"");
261 insta::assert_snapshot!(stderr, @r###"
262 Nothing changed.
263 "###);
264 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
265 insta::assert_snapshot!(stdout, @r###"
266 D file1
267 M file2
268 "###);
269 // Nothing happens if we make no changes, `config_with_right_as_after` version
270 let (stdout, stderr) = test_env.jj_cmd_ok(
271 &repo_path,
272 &["diffedit", "--config-toml", &config_with_right_as_after],
273 );
274 insta::assert_snapshot!(stdout, @"");
275 insta::assert_snapshot!(stderr, @r###"
276 Nothing changed.
277 "###);
278 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
279 insta::assert_snapshot!(stdout, @r###"
280 D file1
281 M file2
282 "###);
283
284 // Can edit changes to individual files
285 std::fs::write(&edit_script, "reset file2").unwrap();
286 let (stdout, stderr) = test_env.jj_cmd_ok(
287 &repo_path,
288 &["diffedit", "--config-toml", &config_with_output_as_after],
289 );
290 insta::assert_snapshot!(stdout, @"");
291 insta::assert_snapshot!(stderr, @r###"
292 Created kkmpptxz 1930da4a (no description set)
293 Working copy now at: kkmpptxz 1930da4a (no description set)
294 Parent commit : rlvkpnrz 613028a4 (no description set)
295 Added 0 files, modified 1 files, removed 0 files
296 "###);
297 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
298 insta::assert_snapshot!(stdout, @r###"
299 D file1
300 "###);
301
302 // Can write something new to `file1`
303 test_env.jj_cmd_ok(&repo_path, &["undo"]);
304 std::fs::write(&edit_script, "write file1\nnew content").unwrap();
305 let (stdout, stderr) = test_env.jj_cmd_ok(
306 &repo_path,
307 &["diffedit", "--config-toml", &config_with_output_as_after],
308 );
309 insta::assert_snapshot!(stdout, @"");
310 insta::assert_snapshot!(stderr, @r###"
311 Created kkmpptxz ff2907b6 (no description set)
312 Working copy now at: kkmpptxz ff2907b6 (no description set)
313 Parent commit : rlvkpnrz 613028a4 (no description set)
314 Added 1 files, modified 0 files, removed 0 files
315 "###);
316 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
317 insta::assert_snapshot!(stdout, @r###"
318 M file1
319 M file2
320 "###);
321
322 // But nothing happens if we modify the right side
323 test_env.jj_cmd_ok(&repo_path, &["undo"]);
324 std::fs::write(&edit_script, "write file1\nnew content").unwrap();
325 let (stdout, stderr) = test_env.jj_cmd_ok(
326 &repo_path,
327 &["diffedit", "--config-toml", &config_with_right_as_after],
328 );
329 insta::assert_snapshot!(stdout, @"");
330 insta::assert_snapshot!(stderr, @r###"
331 Nothing changed.
332 "###);
333 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
334 insta::assert_snapshot!(stdout, @r###"
335 D file1
336 M file2
337 "###);
338
339 // TODO: test with edit_script of "reset file2". This fails on right side
340 // since the file is readonly.
341}
342
343#[test]
344fn test_diffedit_merge() {
345 let mut test_env = TestEnvironment::default();
346 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
347 let repo_path = test_env.env_root().join("repo");
348
349 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
350 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
351 test_env.jj_cmd_ok(&repo_path, &["new"]);
352 test_env.jj_cmd_ok(&repo_path, &["branch", "create", "b"]);
353 std::fs::write(repo_path.join("file1"), "b\n").unwrap();
354 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
355 test_env.jj_cmd_ok(&repo_path, &["new", "@-"]);
356 test_env.jj_cmd_ok(&repo_path, &["new"]);
357 std::fs::write(repo_path.join("file1"), "c\n").unwrap();
358 std::fs::write(repo_path.join("file2"), "c\n").unwrap();
359 test_env.jj_cmd_ok(&repo_path, &["new", "@", "b", "-m", "merge"]);
360 // Resolve the conflict in file1, but leave the conflict in file2
361 std::fs::write(repo_path.join("file1"), "d\n").unwrap();
362 std::fs::write(repo_path.join("file3"), "d\n").unwrap();
363 test_env.jj_cmd_ok(&repo_path, &["new"]);
364 // Test the setup
365 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-r", "@-", "-s"]);
366 insta::assert_snapshot!(stdout, @r###"
367 M file1
368 A file3
369 "###);
370
371 let edit_script = test_env.set_up_fake_diff_editor();
372
373 // Remove file1. The conflict remains in the working copy on top of the merge.
374 std::fs::write(
375 edit_script,
376 "files-before file1\0files-after JJ-INSTRUCTIONS file1 file3\0rm file1",
377 )
378 .unwrap();
379 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit", "-r", "@-"]);
380 insta::assert_snapshot!(stdout, @"");
381 insta::assert_snapshot!(stderr, @r###"
382 Created royxmykx b9539d6e (conflict) merge
383 Rebased 1 descendant commits
384 Working copy now at: yqosqzyt 0a24ed24 (conflict) (empty) (no description set)
385 Parent commit : royxmykx b9539d6e (conflict) merge
386 Added 0 files, modified 0 files, removed 1 files
387 There are unresolved conflicts at these paths:
388 file2 2-sided conflict
389 "###);
390 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s", "-r", "@-"]);
391 insta::assert_snapshot!(stdout, @r###"
392 D file1
393 A file3
394 "###);
395 assert!(!repo_path.join("file1").exists());
396 let stdout = test_env.jj_cmd_success(&repo_path, &["print", "file2"]);
397 insta::assert_snapshot!(stdout, @r###"
398 <<<<<<< Conflict 1 of 1
399 %%%%%%% Changes from base to side #1
400 -a
401 +c
402 +++++++ Contents of side #2
403 b
404 >>>>>>>
405 "###);
406}
407
408#[test]
409fn test_diffedit_old_restore_interactive_tests() {
410 let mut test_env = TestEnvironment::default();
411 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
412 let repo_path = test_env.env_root().join("repo");
413
414 std::fs::write(repo_path.join("file1"), "a\n").unwrap();
415 std::fs::write(repo_path.join("file2"), "a\n").unwrap();
416 test_env.jj_cmd_ok(&repo_path, &["new"]);
417 std::fs::remove_file(repo_path.join("file1")).unwrap();
418 std::fs::write(repo_path.join("file2"), "b\n").unwrap();
419 std::fs::write(repo_path.join("file3"), "b\n").unwrap();
420
421 let edit_script = test_env.set_up_fake_diff_editor();
422
423 // Nothing happens if we make no changes
424 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit", "--from", "@-"]);
425 insta::assert_snapshot!(stdout, @"");
426 insta::assert_snapshot!(stderr, @r###"
427 Nothing changed.
428 "###);
429 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
430 insta::assert_snapshot!(stdout, @r###"
431 D file1
432 M file2
433 A file3
434 "###);
435
436 // Nothing happens if the diff-editor exits with an error
437 std::fs::write(&edit_script, "rm file2\0fail").unwrap();
438 insta::assert_snapshot!(&test_env.jj_cmd_failure(&repo_path, &["diffedit", "--from", "@-"]), @r###"
439 Error: Failed to edit diff
440 Caused by: Tool exited with a non-zero code (run with --debug to see the exact invocation). Exit code: 1.
441 "###);
442 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
443 insta::assert_snapshot!(stdout, @r###"
444 D file1
445 M file2
446 A file3
447 "###);
448
449 // Can restore changes to individual files
450 std::fs::write(&edit_script, "reset file2\0reset file3").unwrap();
451 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit", "--from", "@-"]);
452 insta::assert_snapshot!(stdout, @"");
453 insta::assert_snapshot!(stderr, @r###"
454 Created rlvkpnrz abdbf627 (no description set)
455 Working copy now at: rlvkpnrz abdbf627 (no description set)
456 Parent commit : qpvuntsm 2375fa16 (no description set)
457 Added 0 files, modified 1 files, removed 1 files
458 "###);
459 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "-s"]);
460 insta::assert_snapshot!(stdout, @r###"
461 D file1
462 "###);
463
464 // Can make unrelated edits
465 test_env.jj_cmd_ok(&repo_path, &["undo"]);
466 std::fs::write(&edit_script, "write file3\nunrelated\n").unwrap();
467 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["diffedit", "--from", "@-"]);
468 insta::assert_snapshot!(stdout, @"");
469 insta::assert_snapshot!(stderr, @r###"
470 Created rlvkpnrz e31f7f33 (no description set)
471 Working copy now at: rlvkpnrz e31f7f33 (no description set)
472 Parent commit : qpvuntsm 2375fa16 (no description set)
473 Added 0 files, modified 1 files, removed 0 files
474 "###);
475 let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--git"]);
476 insta::assert_snapshot!(stdout, @r###"
477 diff --git a/file1 b/file1
478 deleted file mode 100644
479 index 7898192261..0000000000
480 --- a/file1
481 +++ /dev/null
482 @@ -1,1 +1,0 @@
483 -a
484 diff --git a/file2 b/file2
485 index 7898192261...6178079822 100644
486 --- a/file2
487 +++ b/file2
488 @@ -1,1 +1,1 @@
489 -a
490 +b
491 diff --git a/file3 b/file3
492 new file mode 100644
493 index 0000000000..c21c9352f7
494 --- /dev/null
495 +++ b/file3
496 @@ -1,0 +1,1 @@
497 +unrelated
498 "###);
499}