just playing with tangled
1// Copyright 2022 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::path::Path;
16
17use indoc::indoc;
18
19use crate::common::TestEnvironment;
20
21fn create_commit(
22 test_env: &TestEnvironment,
23 repo_path: &Path,
24 name: &str,
25 parents: &[&str],
26 files: &[(&str, &str)],
27) {
28 if parents.is_empty() {
29 test_env.jj_cmd_ok(repo_path, &["new", "root()", "-m", name]);
30 } else {
31 let mut args = vec!["new", "-m", name];
32 args.extend(parents);
33 test_env.jj_cmd_ok(repo_path, &args);
34 }
35 for (name, content) in files {
36 std::fs::write(repo_path.join(name), content).unwrap();
37 }
38 test_env.jj_cmd_ok(repo_path, &["branch", "create", name]);
39}
40
41fn get_log_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
42 test_env.jj_cmd_success(repo_path, &["log", "-T", "branches"])
43}
44
45#[test]
46fn test_resolution() {
47 let mut test_env = TestEnvironment::default();
48 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
49 let repo_path = test_env.env_root().join("repo");
50
51 create_commit(&test_env, &repo_path, "base", &[], &[("file", "base\n")]);
52 create_commit(&test_env, &repo_path, "a", &["base"], &[("file", "a\n")]);
53 create_commit(&test_env, &repo_path, "b", &["base"], &[("file", "b\n")]);
54 create_commit(&test_env, &repo_path, "conflict", &["a", "b"], &[]);
55 // Test the setup
56 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
57 @ conflict
58 ├─╮
59 │ ◉ b
60 ◉ │ a
61 ├─╯
62 ◉ base
63 ◉
64 "###);
65 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
66 @r###"
67 file 2-sided conflict
68 "###);
69 insta::assert_snapshot!(
70 std::fs::read_to_string(repo_path.join("file")).unwrap()
71 , @r###"
72 <<<<<<< Conflict 1 of 1
73 %%%%%%% Changes from base to side #1
74 -base
75 +a
76 +++++++ Contents of side #2
77 b
78 >>>>>>>
79 "###);
80
81 let editor_script = test_env.set_up_fake_editor();
82 // Check that output file starts out empty and resolve the conflict
83 std::fs::write(
84 &editor_script,
85 ["dump editor0", "write\nresolution\n"].join("\0"),
86 )
87 .unwrap();
88 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["resolve"]);
89 insta::assert_snapshot!(stdout, @"");
90 insta::assert_snapshot!(stderr, @r###"
91 Resolving conflicts in: file
92 Working copy now at: vruxwmqv e069f073 conflict | conflict
93 Parent commit : zsuskuln aa493daf a | a
94 Parent commit : royxmykx db6a4daf b | b
95 Added 0 files, modified 1 files, removed 0 files
96 "###);
97 insta::assert_snapshot!(
98 std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @r###"
99 "###);
100 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
101 @r###"
102 diff --git a/file b/file
103 index 0000000000...88425ec521 100644
104 --- a/file
105 +++ b/file
106 @@ -1,7 +1,1 @@
107 -<<<<<<< Conflict 1 of 1
108 -%%%%%%% Changes from base to side #1
109 --base
110 -+a
111 -+++++++ Contents of side #2
112 -b
113 ->>>>>>>
114 +resolution
115 "###);
116 insta::assert_snapshot!(test_env.jj_cmd_cli_error(&repo_path, &["resolve", "--list"]),
117 @r###"
118 Error: No conflicts found at this revision
119 "###);
120
121 // Try again with --tool=<name>
122 test_env.jj_cmd_ok(&repo_path, &["undo"]);
123 std::fs::write(&editor_script, "write\nresolution\n").unwrap();
124 let (stdout, stderr) = test_env.jj_cmd_ok(
125 &repo_path,
126 &[
127 "resolve",
128 "--config-toml=ui.merge-editor='false'",
129 "--tool=fake-editor",
130 ],
131 );
132 insta::assert_snapshot!(stdout, @"");
133 insta::assert_snapshot!(stderr, @r###"
134 Resolving conflicts in: file
135 Working copy now at: vruxwmqv 1a70c7c6 conflict | conflict
136 Parent commit : zsuskuln aa493daf a | a
137 Parent commit : royxmykx db6a4daf b | b
138 Added 0 files, modified 1 files, removed 0 files
139 "###);
140 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
141 @r###"
142 diff --git a/file b/file
143 index 0000000000...88425ec521 100644
144 --- a/file
145 +++ b/file
146 @@ -1,7 +1,1 @@
147 -<<<<<<< Conflict 1 of 1
148 -%%%%%%% Changes from base to side #1
149 --base
150 -+a
151 -+++++++ Contents of side #2
152 -b
153 ->>>>>>>
154 +resolution
155 "###);
156 insta::assert_snapshot!(test_env.jj_cmd_cli_error(&repo_path, &["resolve", "--list"]),
157 @r###"
158 Error: No conflicts found at this revision
159 "###);
160
161 // Check that the output file starts with conflict markers if
162 // `merge-tool-edits-conflict-markers=true`
163 test_env.jj_cmd_ok(&repo_path, &["undo"]);
164 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
165 @"");
166 std::fs::write(
167 &editor_script,
168 ["dump editor1", "write\nresolution\n"].join("\0"),
169 )
170 .unwrap();
171 test_env.jj_cmd_ok(
172 &repo_path,
173 &[
174 "resolve",
175 "--config-toml",
176 "merge-tools.fake-editor.merge-tool-edits-conflict-markers=true",
177 ],
178 );
179 insta::assert_snapshot!(
180 std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r###"
181 <<<<<<< Conflict 1 of 1
182 %%%%%%% Changes from base to side #1
183 -base
184 +a
185 +++++++ Contents of side #2
186 b
187 >>>>>>>
188 "###);
189 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
190 @r###"
191 diff --git a/file b/file
192 index 0000000000...88425ec521 100644
193 --- a/file
194 +++ b/file
195 @@ -1,7 +1,1 @@
196 -<<<<<<< Conflict 1 of 1
197 -%%%%%%% Changes from base to side #1
198 --base
199 -+a
200 -+++++++ Contents of side #2
201 -b
202 ->>>>>>>
203 +resolution
204 "###);
205
206 // Check that if merge tool leaves conflict markers in output file and
207 // `merge-tool-edits-conflict-markers=true`, these markers are properly parsed.
208 test_env.jj_cmd_ok(&repo_path, &["undo"]);
209 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
210 @"");
211 std::fs::write(
212 &editor_script,
213 [
214 "dump editor2",
215 indoc! {"
216 write
217 <<<<<<<
218 %%%%%%%
219 -some
220 +fake
221 +++++++
222 conflict
223 >>>>>>>
224 "},
225 ]
226 .join("\0"),
227 )
228 .unwrap();
229 let (stdout, stderr) = test_env.jj_cmd_ok(
230 &repo_path,
231 &[
232 "resolve",
233 "--config-toml",
234 "merge-tools.fake-editor.merge-tool-edits-conflict-markers=true",
235 ],
236 );
237 insta::assert_snapshot!(stdout, @"");
238 insta::assert_snapshot!(stderr, @r###"
239 Resolving conflicts in: file
240 New conflicts appeared in these commits:
241 vruxwmqv 7699b9c3 conflict | (conflict) conflict
242 To resolve the conflicts, start by updating to it:
243 jj new vruxwmqvtpmx
244 Then use `jj resolve`, or edit the conflict markers in the file directly.
245 Once the conflicts are resolved, you may want inspect the result with `jj diff`.
246 Then run `jj squash` to move the resolution into the conflicted commit.
247 Working copy now at: vruxwmqv 7699b9c3 conflict | (conflict) conflict
248 Parent commit : zsuskuln aa493daf a | a
249 Parent commit : royxmykx db6a4daf b | b
250 Added 0 files, modified 1 files, removed 0 files
251 There are unresolved conflicts at these paths:
252 file 2-sided conflict
253 "###);
254 insta::assert_snapshot!(
255 std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r###"
256 <<<<<<< Conflict 1 of 1
257 %%%%%%% Changes from base to side #1
258 -base
259 +a
260 +++++++ Contents of side #2
261 b
262 >>>>>>>
263 "###);
264 // Note the "Modified" below
265 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
266 @r###"
267 diff --git a/file b/file
268 --- a/file
269 +++ b/file
270 @@ -1,7 +1,7 @@
271 <<<<<<< Conflict 1 of 1
272 %%%%%%% Changes from base to side #1
273 --base
274 -+a
275 +-some
276 ++fake
277 +++++++ Contents of side #2
278 -b
279 +conflict
280 >>>>>>>
281 "###);
282 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
283 @r###"
284 file 2-sided conflict
285 "###);
286
287 // Check that if merge tool leaves conflict markers in output file but
288 // `merge-tool-edits-conflict-markers=false` or is not specified,
289 // `jj` considers the conflict resolved.
290 test_env.jj_cmd_ok(&repo_path, &["undo"]);
291 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
292 @"");
293 std::fs::write(
294 &editor_script,
295 [
296 "dump editor3",
297 indoc! {"
298 write
299 <<<<<<<
300 %%%%%%%
301 -some
302 +fake
303 +++++++
304 conflict
305 >>>>>>>
306 "},
307 ]
308 .join("\0"),
309 )
310 .unwrap();
311 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["resolve"]);
312 insta::assert_snapshot!(stdout, @"");
313 insta::assert_snapshot!(stderr, @r###"
314 Resolving conflicts in: file
315 Working copy now at: vruxwmqv 3166dfd2 conflict | conflict
316 Parent commit : zsuskuln aa493daf a | a
317 Parent commit : royxmykx db6a4daf b | b
318 Added 0 files, modified 1 files, removed 0 files
319 "###);
320 insta::assert_snapshot!(
321 std::fs::read_to_string(test_env.env_root().join("editor3")).unwrap(), @r###"
322 "###);
323 // Note the "Resolved" below
324 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
325 @r###"
326 diff --git a/file b/file
327 index 0000000000...0610716cc1 100644
328 --- a/file
329 +++ b/file
330 @@ -1,7 +1,7 @@
331 -<<<<<<< Conflict 1 of 1
332 -%%%%%%% Changes from base to side #1
333 --base
334 -+a
335 -+++++++ Contents of side #2
336 -b
337 +<<<<<<<
338 +%%%%%%%
339 +-some
340 ++fake
341 ++++++++
342 +conflict
343 >>>>>>>
344 "###);
345 insta::assert_snapshot!(test_env.jj_cmd_cli_error(&repo_path, &["resolve", "--list"]),
346 @r###"
347 Error: No conflicts found at this revision
348 "###);
349
350 // TODO: Check that running `jj new` and then `jj resolve -r conflict` works
351 // correctly.
352}
353
354fn check_resolve_produces_input_file(
355 test_env: &mut TestEnvironment,
356 repo_path: &Path,
357 role: &str,
358 expected_content: &str,
359) {
360 let editor_script = test_env.set_up_fake_editor();
361 std::fs::write(editor_script, format!("expect\n{expected_content}")).unwrap();
362
363 let merge_arg_config = format!(r#"merge-tools.fake-editor.merge-args = ["${role}"]"#);
364 // This error means that fake-editor exited successfully but did not modify the
365 // output file.
366 // We cannot use `insta::assert_snapshot!` here after insta 1.22 due to
367 // https://github.com/mitsuhiko/insta/commit/745b45b. Hopefully, this will again become possible
368 // in the future. See also https://github.com/mitsuhiko/insta/issues/313.
369 assert_eq!(
370 &test_env.jj_cmd_failure(repo_path, &["resolve", "--config-toml", &merge_arg_config]),
371 "Resolving conflicts in: file\nError: Failed to resolve conflicts\nCaused by: The output \
372 file is either unchanged or empty after the editor quit (run with --debug to see the \
373 exact invocation).\n"
374 );
375}
376
377#[test]
378fn test_normal_conflict_input_files() {
379 let mut test_env = TestEnvironment::default();
380 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
381 let repo_path = test_env.env_root().join("repo");
382
383 create_commit(&test_env, &repo_path, "base", &[], &[("file", "base\n")]);
384 create_commit(&test_env, &repo_path, "a", &["base"], &[("file", "a\n")]);
385 create_commit(&test_env, &repo_path, "b", &["base"], &[("file", "b\n")]);
386 create_commit(&test_env, &repo_path, "conflict", &["a", "b"], &[]);
387 // Test the setup
388 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
389 @ conflict
390 ├─╮
391 │ ◉ b
392 ◉ │ a
393 ├─╯
394 ◉ base
395 ◉
396 "###);
397 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
398 @r###"
399 file 2-sided conflict
400 "###);
401 insta::assert_snapshot!(
402 std::fs::read_to_string(repo_path.join("file")).unwrap()
403 , @r###"
404 <<<<<<< Conflict 1 of 1
405 %%%%%%% Changes from base to side #1
406 -base
407 +a
408 +++++++ Contents of side #2
409 b
410 >>>>>>>
411 "###);
412
413 check_resolve_produces_input_file(&mut test_env, &repo_path, "base", "base\n");
414 check_resolve_produces_input_file(&mut test_env, &repo_path, "left", "a\n");
415 check_resolve_produces_input_file(&mut test_env, &repo_path, "right", "b\n");
416}
417
418#[test]
419fn test_baseless_conflict_input_files() {
420 let mut test_env = TestEnvironment::default();
421 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
422 let repo_path = test_env.env_root().join("repo");
423
424 create_commit(&test_env, &repo_path, "base", &[], &[]);
425 create_commit(&test_env, &repo_path, "a", &["base"], &[("file", "a\n")]);
426 create_commit(&test_env, &repo_path, "b", &["base"], &[("file", "b\n")]);
427 create_commit(&test_env, &repo_path, "conflict", &["a", "b"], &[]);
428 // Test the setup
429 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
430 @ conflict
431 ├─╮
432 │ ◉ b
433 ◉ │ a
434 ├─╯
435 ◉ base
436 ◉
437 "###);
438 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
439 @r###"
440 file 2-sided conflict
441 "###);
442 insta::assert_snapshot!(
443 std::fs::read_to_string(repo_path.join("file")).unwrap()
444 , @r###"
445 <<<<<<< Conflict 1 of 1
446 %%%%%%% Changes from base to side #1
447 +a
448 +++++++ Contents of side #2
449 b
450 >>>>>>>
451 "###);
452
453 check_resolve_produces_input_file(&mut test_env, &repo_path, "base", "");
454 check_resolve_produces_input_file(&mut test_env, &repo_path, "left", "a\n");
455 check_resolve_produces_input_file(&mut test_env, &repo_path, "right", "b\n");
456}
457
458#[test]
459fn test_too_many_parents() {
460 let test_env = TestEnvironment::default();
461 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
462 let repo_path = test_env.env_root().join("repo");
463
464 create_commit(&test_env, &repo_path, "base", &[], &[("file", "base\n")]);
465 create_commit(&test_env, &repo_path, "a", &["base"], &[("file", "a\n")]);
466 create_commit(&test_env, &repo_path, "b", &["base"], &[("file", "b\n")]);
467 create_commit(&test_env, &repo_path, "c", &["base"], &[("file", "c\n")]);
468 create_commit(&test_env, &repo_path, "conflict", &["a", "b", "c"], &[]);
469 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
470 @r###"
471 file 3-sided conflict
472 "###);
473 // Test warning color
474 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list", "--color=always"]),
475 @r###"
476 file [38;5;1m3-sided[38;5;3m conflict[39m
477 "###);
478
479 let error = test_env.jj_cmd_failure(&repo_path, &["resolve"]);
480 insta::assert_snapshot!(error, @r###"
481 Hint: Using default editor ':builtin-tui'; run `jj config set --user ui.merge-editor :builtin` to disable this message.
482 Resolving conflicts in: file
483 Error: Failed to resolve conflicts
484 Caused by: The conflict at "file" has 3 sides. At most 2 sides are supported.
485 "###);
486}
487
488#[test]
489fn test_edit_delete_conflict_input_files() {
490 let mut test_env = TestEnvironment::default();
491 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
492 let repo_path = test_env.env_root().join("repo");
493
494 create_commit(&test_env, &repo_path, "base", &[], &[("file", "base\n")]);
495 create_commit(&test_env, &repo_path, "a", &["base"], &[("file", "a\n")]);
496 create_commit(&test_env, &repo_path, "b", &["base"], &[]);
497 std::fs::remove_file(repo_path.join("file")).unwrap();
498 create_commit(&test_env, &repo_path, "conflict", &["a", "b"], &[]);
499 // Test the setup
500 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
501 @ conflict
502 ├─╮
503 │ ◉ b
504 ◉ │ a
505 ├─╯
506 ◉ base
507 ◉
508 "###);
509 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
510 @r###"
511 file 2-sided conflict including 1 deletion
512 "###);
513 insta::assert_snapshot!(
514 std::fs::read_to_string(repo_path.join("file")).unwrap()
515 , @r###"
516 <<<<<<< Conflict 1 of 1
517 +++++++ Contents of side #1
518 a
519 %%%%%%% Changes from base to side #2
520 -base
521 >>>>>>>
522 "###);
523
524 check_resolve_produces_input_file(&mut test_env, &repo_path, "base", "base\n");
525 check_resolve_produces_input_file(&mut test_env, &repo_path, "left", "a\n");
526 check_resolve_produces_input_file(&mut test_env, &repo_path, "right", "");
527}
528
529#[test]
530fn test_file_vs_dir() {
531 let test_env = TestEnvironment::default();
532 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
533 let repo_path = test_env.env_root().join("repo");
534
535 create_commit(&test_env, &repo_path, "base", &[], &[("file", "base\n")]);
536 create_commit(&test_env, &repo_path, "a", &["base"], &[("file", "a\n")]);
537 create_commit(&test_env, &repo_path, "b", &["base"], &[]);
538 std::fs::remove_file(repo_path.join("file")).unwrap();
539 std::fs::create_dir(repo_path.join("file")).unwrap();
540 // Without a placeholder file, `jj` ignores an empty directory
541 std::fs::write(repo_path.join("file").join("placeholder"), "").unwrap();
542 create_commit(&test_env, &repo_path, "conflict", &["a", "b"], &[]);
543 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
544 @ conflict
545 ├─╮
546 │ ◉ b
547 ◉ │ a
548 ├─╯
549 ◉ base
550 ◉
551 "###);
552
553 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
554 @r###"
555 file 2-sided conflict including a directory
556 "###);
557 let error = test_env.jj_cmd_failure(&repo_path, &["resolve"]);
558 insta::assert_snapshot!(error, @r###"
559 Hint: Using default editor ':builtin-tui'; run `jj config set --user ui.merge-editor :builtin` to disable this message.
560 Resolving conflicts in: file
561 Error: Failed to resolve conflicts
562 Caused by: Only conflicts that involve normal files (not symlinks, not executable, etc.) are supported. Conflict summary for "file":
563 Conflict:
564 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55
565 Adding file with id 78981922613b2afb6025042ff6bd878ac1994e85
566 Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877
567
568 "###);
569}
570
571#[test]
572fn test_description_with_dir_and_deletion() {
573 let test_env = TestEnvironment::default();
574 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
575 let repo_path = test_env.env_root().join("repo");
576
577 create_commit(&test_env, &repo_path, "base", &[], &[("file", "base\n")]);
578 create_commit(&test_env, &repo_path, "edit", &["base"], &[("file", "b\n")]);
579 create_commit(&test_env, &repo_path, "dir", &["base"], &[]);
580 std::fs::remove_file(repo_path.join("file")).unwrap();
581 std::fs::create_dir(repo_path.join("file")).unwrap();
582 // Without a placeholder file, `jj` ignores an empty directory
583 std::fs::write(repo_path.join("file").join("placeholder"), "").unwrap();
584 create_commit(&test_env, &repo_path, "del", &["base"], &[]);
585 std::fs::remove_file(repo_path.join("file")).unwrap();
586 create_commit(
587 &test_env,
588 &repo_path,
589 "conflict",
590 &["edit", "dir", "del"],
591 &[],
592 );
593 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
594 @ conflict
595 ├─┬─╮
596 │ │ ◉ del
597 │ ◉ │ dir
598 │ ├─╯
599 ◉ │ edit
600 ├─╯
601 ◉ base
602 ◉
603 "###);
604
605 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
606 @r###"
607 file 3-sided conflict including 1 deletion and a directory
608 "###);
609 // Test warning color. The deletion is fine, so it's not highlighted
610 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list", "--color=always"]),
611 @r###"
612 file [38;5;1m3-sided[38;5;3m conflict including 1 deletion and [38;5;1ma directory[39m
613 "###);
614 let error = test_env.jj_cmd_failure(&repo_path, &["resolve"]);
615 insta::assert_snapshot!(error, @r###"
616 Hint: Using default editor ':builtin-tui'; run `jj config set --user ui.merge-editor :builtin` to disable this message.
617 Resolving conflicts in: file
618 Error: Failed to resolve conflicts
619 Caused by: Only conflicts that involve normal files (not symlinks, not executable, etc.) are supported. Conflict summary for "file":
620 Conflict:
621 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55
622 Removing file with id df967b96a579e45a18b8251732d16804b2e56a55
623 Adding file with id 61780798228d17af2d34fce4cfbdf35556832472
624 Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877
625
626 "###);
627}
628
629#[test]
630fn test_multiple_conflicts() {
631 let mut test_env = TestEnvironment::default();
632 test_env.jj_cmd_ok(test_env.env_root(), &["init", "repo", "--git"]);
633 let repo_path = test_env.env_root().join("repo");
634
635 create_commit(
636 &test_env,
637 &repo_path,
638 "base",
639 &[],
640 &[
641 (
642 "this_file_has_a_very_long_name_to_test_padding",
643 "first base\n",
644 ),
645 ("another_file", "second base\n"),
646 ],
647 );
648 create_commit(
649 &test_env,
650 &repo_path,
651 "a",
652 &["base"],
653 &[
654 (
655 "this_file_has_a_very_long_name_to_test_padding",
656 "first a\n",
657 ),
658 ("another_file", "second a\n"),
659 ],
660 );
661 create_commit(
662 &test_env,
663 &repo_path,
664 "b",
665 &["base"],
666 &[
667 (
668 "this_file_has_a_very_long_name_to_test_padding",
669 "first b\n",
670 ),
671 ("another_file", "second b\n"),
672 ],
673 );
674 create_commit(&test_env, &repo_path, "conflict", &["a", "b"], &[]);
675 // Test the setup
676 insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
677 @ conflict
678 ├─╮
679 │ ◉ b
680 ◉ │ a
681 ├─╯
682 ◉ base
683 ◉
684 "###);
685 insta::assert_snapshot!(
686 std::fs::read_to_string(repo_path.join("this_file_has_a_very_long_name_to_test_padding")).unwrap()
687 , @r###"
688 <<<<<<< Conflict 1 of 1
689 %%%%%%% Changes from base to side #1
690 -first base
691 +first a
692 +++++++ Contents of side #2
693 first b
694 >>>>>>>
695 "###);
696 insta::assert_snapshot!(
697 std::fs::read_to_string(repo_path.join("another_file")).unwrap()
698 , @r###"
699 <<<<<<< Conflict 1 of 1
700 %%%%%%% Changes from base to side #1
701 -second base
702 +second a
703 +++++++ Contents of side #2
704 second b
705 >>>>>>>
706 "###);
707 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
708 @r###"
709 another_file 2-sided conflict
710 this_file_has_a_very_long_name_to_test_padding 2-sided conflict
711 "###);
712 // Test colors
713 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list", "--color=always"]),
714 @r###"
715 another_file [38;5;3m2-sided conflict[39m
716 this_file_has_a_very_long_name_to_test_padding [38;5;3m2-sided conflict[39m
717 "###);
718
719 let editor_script = test_env.set_up_fake_editor();
720
721 // Check that we can manually pick which of the conflicts to resolve first
722 std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap();
723 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["resolve", "another_file"]);
724 insta::assert_snapshot!(stdout, @"");
725 insta::assert_snapshot!(stderr, @r###"
726 Resolving conflicts in: another_file
727 New conflicts appeared in these commits:
728 vruxwmqv 6a90e546 conflict | (conflict) conflict
729 To resolve the conflicts, start by updating to it:
730 jj new vruxwmqvtpmx
731 Then use `jj resolve`, or edit the conflict markers in the file directly.
732 Once the conflicts are resolved, you may want inspect the result with `jj diff`.
733 Then run `jj squash` to move the resolution into the conflicted commit.
734 Working copy now at: vruxwmqv 6a90e546 conflict | (conflict) conflict
735 Parent commit : zsuskuln de7553ef a | a
736 Parent commit : royxmykx f68bc2f0 b | b
737 Added 0 files, modified 1 files, removed 0 files
738 There are unresolved conflicts at these paths:
739 this_file_has_a_very_long_name_to_test_padding 2-sided conflict
740 "###);
741 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
742 @r###"
743 diff --git a/another_file b/another_file
744 index 0000000000...a9fcc7d486 100644
745 --- a/another_file
746 +++ b/another_file
747 @@ -1,7 +1,1 @@
748 -<<<<<<< Conflict 1 of 1
749 -%%%%%%% Changes from base to side #1
750 --second base
751 -+second a
752 -+++++++ Contents of side #2
753 -second b
754 ->>>>>>>
755 +resolution another_file
756 "###);
757 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
758 @r###"
759 this_file_has_a_very_long_name_to_test_padding 2-sided conflict
760 "###);
761
762 // Repeat the above with the `--quiet` option.
763 test_env.jj_cmd_ok(&repo_path, &["undo"]);
764 std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap();
765 let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["resolve", "--quiet", "another_file"]);
766 insta::assert_snapshot!(stdout, @"");
767 insta::assert_snapshot!(stderr, @"");
768
769 // For the rest of the test, we call `jj resolve` several times in a row to
770 // resolve each conflict in the order it chooses.
771 test_env.jj_cmd_ok(&repo_path, &["undo"]);
772 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
773 @"");
774 std::fs::write(
775 &editor_script,
776 "expect\n\0write\nfirst resolution for auto-chosen file\n",
777 )
778 .unwrap();
779 test_env.jj_cmd_ok(&repo_path, &["resolve"]);
780 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
781 @r###"
782 diff --git a/another_file b/another_file
783 index 0000000000...7903e1c1c7 100644
784 --- a/another_file
785 +++ b/another_file
786 @@ -1,7 +1,1 @@
787 -<<<<<<< Conflict 1 of 1
788 -%%%%%%% Changes from base to side #1
789 --second base
790 -+second a
791 -+++++++ Contents of side #2
792 -second b
793 ->>>>>>>
794 +first resolution for auto-chosen file
795 "###);
796 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["resolve", "--list"]),
797 @r###"
798 this_file_has_a_very_long_name_to_test_padding 2-sided conflict
799 "###);
800 std::fs::write(
801 &editor_script,
802 "expect\n\0write\nsecond resolution for auto-chosen file\n",
803 )
804 .unwrap();
805
806 test_env.jj_cmd_ok(&repo_path, &["resolve"]);
807 insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--git"]),
808 @r###"
809 diff --git a/another_file b/another_file
810 index 0000000000...7903e1c1c7 100644
811 --- a/another_file
812 +++ b/another_file
813 @@ -1,7 +1,1 @@
814 -<<<<<<< Conflict 1 of 1
815 -%%%%%%% Changes from base to side #1
816 --second base
817 -+second a
818 -+++++++ Contents of side #2
819 -second b
820 ->>>>>>>
821 +first resolution for auto-chosen file
822 diff --git a/this_file_has_a_very_long_name_to_test_padding b/this_file_has_a_very_long_name_to_test_padding
823 index 0000000000...f8c72adf17 100644
824 --- a/this_file_has_a_very_long_name_to_test_padding
825 +++ b/this_file_has_a_very_long_name_to_test_padding
826 @@ -1,7 +1,1 @@
827 -<<<<<<< Conflict 1 of 1
828 -%%%%%%% Changes from base to side #1
829 --first base
830 -+first a
831 -+++++++ Contents of side #2
832 -first b
833 ->>>>>>>
834 +second resolution for auto-chosen file
835 "###);
836
837 insta::assert_snapshot!(test_env.jj_cmd_cli_error(&repo_path, &["resolve", "--list"]),
838 @r###"
839 Error: No conflicts found at this revision
840 "###);
841 insta::assert_snapshot!(test_env.jj_cmd_cli_error(&repo_path, &["resolve"]),
842 @r###"
843 Error: No conflicts found at this revision
844 "###);
845}