mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-16 20:54:27 +00:00
We haven't had any reports of problems from people who opted in. Since it's early in the release cycle now, let's now test it on everyone who builds from head, so we get almost a month of testing from those people before it's enabled by default in a released version. This impacts lots of test cases because the change-id header is added to the Git commit. Most are uninteresting. `test_git_fetch` now sees some divergent changes where it used to see only divergent bookmarks, which makes sense.
2081 lines
70 KiB
Rust
2081 lines
70 KiB
Rust
// Copyright 2022 The Jujutsu Authors
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// https://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
use std::path::Path;
|
||
|
||
use indoc::indoc;
|
||
|
||
use crate::common::create_commit_with_files;
|
||
use crate::common::CommandOutput;
|
||
use crate::common::TestEnvironment;
|
||
use crate::common::TestWorkDir;
|
||
|
||
#[must_use]
|
||
fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
|
||
work_dir.run_jj(["log", "-T", "bookmarks"])
|
||
}
|
||
|
||
#[test]
|
||
fn test_resolution() {
|
||
let mut test_env = TestEnvironment::default();
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
|
||
create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
|
||
create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
// Test the setup
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─╮
|
||
│ ○ b
|
||
○ │ a
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+a
|
||
+++++++ Contents of side #2
|
||
b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
|
||
// Check that output file starts out empty and resolve the conflict
|
||
std::fs::write(
|
||
&editor_script,
|
||
["dump editor0", "write\nresolution\n"].join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 741263c9 conflict | conflict
|
||
Parent commit (@-) : zsuskuln 45537d53 a | a
|
||
Parent commit (@-) : royxmykx 89d1b299 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(
|
||
std::fs::read_to_string(test_env.env_root().join("editor0")).unwrap(), @"");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
index 0000000000..88425ec521 100644
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base
|
||
-+a
|
||
-+++++++ Contents of side #2
|
||
-b
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
------- stderr -------
|
||
Error: No conflicts found at this revision
|
||
[EOF]
|
||
[exit status: 2]
|
||
");
|
||
|
||
// Try again with --tool=<name>
|
||
work_dir.run_jj(["undo"]).success();
|
||
std::fs::write(&editor_script, "write\nresolution\n").unwrap();
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
"--config=ui.merge-editor='false'",
|
||
"--tool=fake-editor",
|
||
]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 1f8a36f7 conflict | conflict
|
||
Parent commit (@-) : zsuskuln 45537d53 a | a
|
||
Parent commit (@-) : royxmykx 89d1b299 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
index 0000000000..88425ec521 100644
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base
|
||
-+a
|
||
-+++++++ Contents of side #2
|
||
-b
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
------- stderr -------
|
||
Error: No conflicts found at this revision
|
||
[EOF]
|
||
[exit status: 2]
|
||
");
|
||
|
||
// Check that the output file starts with conflict markers if
|
||
// `merge-tool-edits-conflict-markers=true`
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
std::fs::write(
|
||
&editor_script,
|
||
["dump editor1", "write\nresolution\n"].join("\0"),
|
||
)
|
||
.unwrap();
|
||
work_dir
|
||
.run_jj([
|
||
"resolve",
|
||
"--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true",
|
||
])
|
||
.success();
|
||
insta::assert_snapshot!(
|
||
std::fs::read_to_string(test_env.env_root().join("editor1")).unwrap(), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+a
|
||
+++++++ Contents of side #2
|
||
b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
index 0000000000..88425ec521 100644
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base
|
||
-+a
|
||
-+++++++ Contents of side #2
|
||
-b
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution
|
||
[EOF]
|
||
");
|
||
|
||
// Check that if merge tool leaves conflict markers in output file and
|
||
// `merge-tool-edits-conflict-markers=true`, these markers are properly parsed.
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
std::fs::write(
|
||
&editor_script,
|
||
[
|
||
"dump editor2",
|
||
indoc! {"
|
||
write
|
||
<<<<<<<
|
||
%%%%%%%
|
||
-some
|
||
+fake
|
||
+++++++
|
||
conflict
|
||
>>>>>>>
|
||
"},
|
||
]
|
||
.join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
"--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true",
|
||
]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 0d40d2b8 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 45537d53 a | a
|
||
Parent commit (@-) : royxmykx 89d1b299 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv 0d40d2b8 conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(
|
||
std::fs::read_to_string(test_env.env_root().join("editor2")).unwrap(), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+a
|
||
+++++++ Contents of side #2
|
||
b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
// Note the "Modified" below
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,7 +1,7 @@
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
--base
|
||
-+a
|
||
+-some
|
||
++fake
|
||
+++++++ Contents of side #2
|
||
-b
|
||
+conflict
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// Check that if merge tool leaves conflict markers in output file but
|
||
// `merge-tool-edits-conflict-markers=false` or is not specified,
|
||
// `jj` considers the conflict resolved.
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
std::fs::write(
|
||
&editor_script,
|
||
[
|
||
"dump editor3",
|
||
indoc! {"
|
||
write
|
||
<<<<<<<
|
||
%%%%%%%
|
||
-some
|
||
+fake
|
||
+++++++
|
||
conflict
|
||
>>>>>>>
|
||
"},
|
||
]
|
||
.join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 2cc7f5e3 conflict | conflict
|
||
Parent commit (@-) : zsuskuln 45537d53 a | a
|
||
Parent commit (@-) : royxmykx 89d1b299 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(
|
||
std::fs::read_to_string(test_env.env_root().join("editor3")).unwrap(), @"");
|
||
// Note the "Resolved" below
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
index 0000000000..0610716cc1 100644
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,7 +1,7 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base
|
||
-+a
|
||
-+++++++ Contents of side #2
|
||
-b
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+<<<<<<<
|
||
+%%%%%%%
|
||
+-some
|
||
++fake
|
||
++++++++
|
||
+conflict
|
||
+>>>>>>>
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
------- stderr -------
|
||
Error: No conflicts found at this revision
|
||
[EOF]
|
||
[exit status: 2]
|
||
");
|
||
|
||
// Check that merge tool can override conflict marker style setting, and that
|
||
// the merge tool can output Git-style conflict markers
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
std::fs::write(
|
||
&editor_script,
|
||
[
|
||
"dump editor4",
|
||
indoc! {"
|
||
write
|
||
<<<<<<<
|
||
some
|
||
|||||||
|
||
fake
|
||
=======
|
||
conflict
|
||
>>>>>>>
|
||
"},
|
||
]
|
||
.join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
"--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true",
|
||
"--config=merge-tools.fake-editor.conflict-marker-style=git",
|
||
]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv d5f058ec conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 45537d53 a | a
|
||
Parent commit (@-) : royxmykx 89d1b299 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv d5f058ec conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(
|
||
std::fs::read_to_string(test_env.env_root().join("editor4")).unwrap(), @r"
|
||
<<<<<<< Side #1 (Conflict 1 of 1)
|
||
a
|
||
||||||| Base
|
||
base
|
||
=======
|
||
b
|
||
>>>>>>> Side #2 (Conflict 1 of 1 ends)
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,7 +1,7 @@
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
--base
|
||
-+a
|
||
+-fake
|
||
++some
|
||
+++++++ Contents of side #2
|
||
-b
|
||
+conflict
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// Check that merge tool can leave conflict markers by returning exit code 1
|
||
// when using `merge-conflict-exit-codes = [1]`. The Git "diff3" conflict
|
||
// markers should also be parsed correctly.
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
std::fs::write(
|
||
&editor_script,
|
||
[
|
||
"dump editor5",
|
||
indoc! {"
|
||
write
|
||
<<<<<<<
|
||
some
|
||
|||||||
|
||
fake
|
||
=======
|
||
conflict
|
||
>>>>>>>
|
||
"},
|
||
"fail",
|
||
]
|
||
.join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
"--config=merge-tools.fake-editor.merge-conflict-exit-codes=[1]",
|
||
]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 6c205356 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 45537d53 a | a
|
||
Parent commit (@-) : royxmykx 89d1b299 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv 6c205356 conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(
|
||
std::fs::read_to_string(test_env.env_root().join("editor5")).unwrap(), @"");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,7 +1,7 @@
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
--base
|
||
-+a
|
||
+-fake
|
||
++some
|
||
+++++++ Contents of side #2
|
||
-b
|
||
+conflict
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// Check that an error is reported if a merge tool indicated it would leave
|
||
// conflict markers, but the output file didn't contain valid conflict markers.
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
std::fs::write(
|
||
&editor_script,
|
||
[
|
||
indoc! {"
|
||
write
|
||
<<<<<<< this isn't diff3 style!
|
||
some
|
||
=======
|
||
conflict
|
||
>>>>>>>
|
||
"},
|
||
"fail",
|
||
]
|
||
.join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
"--config=merge-tools.fake-editor.merge-conflict-exit-codes=[1]",
|
||
]);
|
||
insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Error: Failed to resolve conflicts
|
||
Caused by: Tool exited with exit status: 1, but did not produce valid conflict markers (run with --debug to see the exact invocation)
|
||
[EOF]
|
||
[exit status: 1]
|
||
");
|
||
|
||
// TODO: Check that running `jj new` and then `jj resolve -r conflict` works
|
||
// correctly.
|
||
}
|
||
|
||
fn check_resolve_produces_input_file(
|
||
test_env: &mut TestEnvironment,
|
||
root: impl AsRef<Path>,
|
||
filename: &str,
|
||
role: &str,
|
||
expected_content: &str,
|
||
) {
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
let work_dir = test_env.work_dir(root);
|
||
std::fs::write(editor_script, format!("expect\n{expected_content}")).unwrap();
|
||
|
||
let merge_arg_config = format!(r#"merge-tools.fake-editor.merge-args=["${role}"]"#);
|
||
// This error means that fake-editor exited successfully but did not modify the
|
||
// output file.
|
||
let output = work_dir.run_jj(["resolve", "--config", &merge_arg_config, filename]);
|
||
insta::allow_duplicates! {
|
||
insta::assert_snapshot!(
|
||
output.normalize_stderr_with(|s| s.replacen(filename, "$FILENAME", 1)), @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: $FILENAME
|
||
Error: Failed to resolve conflicts
|
||
Caused by: The output file is either unchanged or empty after the editor quit (run with --debug to see the exact invocation).
|
||
[EOF]
|
||
[exit status: 1]
|
||
");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_normal_conflict_input_files() {
|
||
let mut test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
|
||
create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
|
||
create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
// Test the setup
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─╮
|
||
│ ○ b
|
||
○ │ a
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+a
|
||
+++++++ Contents of side #2
|
||
b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", "b\n");
|
||
}
|
||
|
||
#[test]
|
||
fn test_baseless_conflict_input_files() {
|
||
let mut test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(&work_dir, "base", &[], &[]);
|
||
create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
|
||
create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
// Test the setup
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─╮
|
||
│ ○ b
|
||
○ │ a
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
+a
|
||
+++++++ Contents of side #2
|
||
b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", "b\n");
|
||
}
|
||
|
||
#[test]
|
||
fn test_too_many_parents() {
|
||
let test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
|
||
create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
|
||
create_commit_with_files(&work_dir, "b", &["base"], &[("file", "b\n")]);
|
||
create_commit_with_files(&work_dir, "c", &["base"], &[("file", "c\n")]);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b", "c"], &[]);
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 3-sided conflict
|
||
[EOF]
|
||
");
|
||
// Test warning color
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r"
|
||
file [38;5;1m3-sided[38;5;3m conflict[39m
|
||
[EOF]
|
||
");
|
||
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output, @r#"
|
||
------- stderr -------
|
||
Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message.
|
||
Error: Failed to resolve conflicts
|
||
Caused by: The conflict at "file" has 3 sides. At most 2 sides are supported.
|
||
[EOF]
|
||
[exit status: 1]
|
||
"#);
|
||
}
|
||
|
||
#[test]
|
||
fn test_simplify_conflict_sides() {
|
||
let mut test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
// Creates a 4-sided conflict, with fileA and fileB having different conflicts:
|
||
// fileA: A - B + C - B + B - B + B
|
||
// fileB: A - A + A - A + B - C + D
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"base",
|
||
&[],
|
||
&[("fileA", "base\n"), ("fileB", "base\n")],
|
||
);
|
||
create_commit_with_files(&work_dir, "a1", &["base"], &[("fileA", "1\n")]);
|
||
create_commit_with_files(&work_dir, "a2", &["base"], &[("fileA", "2\n")]);
|
||
create_commit_with_files(&work_dir, "b1", &["base"], &[("fileB", "1\n")]);
|
||
create_commit_with_files(&work_dir, "b2", &["base"], &[("fileB", "2\n")]);
|
||
create_commit_with_files(&work_dir, "conflictA", &["a1", "a2"], &[]);
|
||
create_commit_with_files(&work_dir, "conflictB", &["b1", "b2"], &[]);
|
||
create_commit_with_files(&work_dir, "conflict", &["conflictA", "conflictB"], &[]);
|
||
|
||
// Even though the tree-level conflict is a 4-sided conflict, each file is
|
||
// materialized as a 2-sided conflict.
|
||
insta::assert_snapshot!(work_dir.run_jj(["debug", "tree"]), @r#"
|
||
fileA: Ok(Conflicted([Some(File { id: FileId("d00491fd7e5bb6fa28c517a0bb32b8b506539d4d"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("0cfbf08886fca9a91cb753ec8734c84fcbe52c9f"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false })]))
|
||
fileB: Ok(Conflicted([Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("d00491fd7e5bb6fa28c517a0bb32b8b506539d4d"), executable: false }), Some(File { id: FileId("df967b96a579e45a18b8251732d16804b2e56a55"), executable: false }), Some(File { id: FileId("0cfbf08886fca9a91cb753ec8734c84fcbe52c9f"), executable: false })]))
|
||
[EOF]
|
||
"#);
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
fileA 2-sided conflict
|
||
fileB 2-sided conflict
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("fileA"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+1
|
||
+++++++ Contents of side #2
|
||
2
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("fileB"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+1
|
||
+++++++ Contents of side #2
|
||
2
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
|
||
// Conflict should be simplified before being handled by external merge tool.
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "base", "base\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "left", "1\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "fileA", "right", "2\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "base", "base\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "left", "1\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "fileB", "right", "2\n");
|
||
|
||
// Check that simplified conflicts are still parsed as conflicts after editing
|
||
// when `merge-tool-edits-conflict-markers=true`.
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
std::fs::write(
|
||
editor_script,
|
||
indoc! {"
|
||
write
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base_edited
|
||
+1_edited
|
||
+++++++ Contents of side #2
|
||
2_edited
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
"},
|
||
)
|
||
.unwrap();
|
||
let work_dir = test_env.work_dir("repo");
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
"--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true",
|
||
"fileB",
|
||
]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: fileB
|
||
Working copy (@) now at: nkmrtpmo 25c5dd0b conflict | (conflict) conflict
|
||
Parent commit (@-) : kmkuslsw ccb05364 conflictA | (conflict) (empty) conflictA
|
||
Parent commit (@-) : lylxulpl d9bc60cb conflictB | (conflict) (empty) conflictB
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
fileA 2-sided conflict
|
||
fileB 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
nkmrtpmo 25c5dd0b conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new nkmrtpmo
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("fileB"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base_edited
|
||
+1_edited
|
||
+++++++ Contents of side #2
|
||
2_edited
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
fileA 2-sided conflict
|
||
fileB 2-sided conflict
|
||
[EOF]
|
||
");
|
||
}
|
||
|
||
#[test]
|
||
fn test_edit_delete_conflict_input_files() {
|
||
let mut test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
|
||
create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
|
||
create_commit_with_files(&work_dir, "b", &["base"], &[]);
|
||
work_dir.remove_file("file");
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
// Test the setup
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─╮
|
||
│ ○ b
|
||
○ │ a
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict including 1 deletion
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
+++++++ Contents of side #1
|
||
a
|
||
%%%%%%% Changes from base to side #2
|
||
-base
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "base", "base\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "left", "a\n");
|
||
check_resolve_produces_input_file(&mut test_env, "repo", "file", "right", "");
|
||
}
|
||
|
||
#[test]
|
||
fn test_file_vs_dir() {
|
||
let test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
|
||
create_commit_with_files(&work_dir, "a", &["base"], &[("file", "a\n")]);
|
||
create_commit_with_files(&work_dir, "b", &["base"], &[]);
|
||
work_dir.remove_file("file");
|
||
work_dir.create_dir("file");
|
||
// Without a placeholder file, `jj` ignores an empty directory
|
||
work_dir.write_file("file/placeholder", "");
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─╮
|
||
│ ○ b
|
||
○ │ a
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict including a directory
|
||
[EOF]
|
||
");
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output, @r#"
|
||
------- stderr -------
|
||
Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message.
|
||
Error: Failed to resolve conflicts
|
||
Caused by: Only conflicts that involve normal files (not symlinks, etc.) are supported. Conflict summary for "file":
|
||
Conflict:
|
||
Removing file with id df967b96a579e45a18b8251732d16804b2e56a55
|
||
Adding file with id 78981922613b2afb6025042ff6bd878ac1994e85
|
||
Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877
|
||
[EOF]
|
||
[exit status: 1]
|
||
"#);
|
||
}
|
||
|
||
#[test]
|
||
fn test_description_with_dir_and_deletion() {
|
||
let test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(&work_dir, "base", &[], &[("file", "base\n")]);
|
||
create_commit_with_files(&work_dir, "edit", &["base"], &[("file", "b\n")]);
|
||
create_commit_with_files(&work_dir, "dir", &["base"], &[]);
|
||
work_dir.remove_file("file");
|
||
work_dir.create_dir("file");
|
||
// Without a placeholder file, `jj` ignores an empty directory
|
||
work_dir.write_file("file/placeholder", "");
|
||
create_commit_with_files(&work_dir, "del", &["base"], &[]);
|
||
work_dir.remove_file("file");
|
||
create_commit_with_files(&work_dir, "conflict", &["edit", "dir", "del"], &[]);
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─┬─╮
|
||
│ │ ○ del
|
||
│ ○ │ dir
|
||
│ ├─╯
|
||
○ │ edit
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 3-sided conflict including 1 deletion and a directory
|
||
[EOF]
|
||
");
|
||
// Test warning color. The deletion is fine, so it's not highlighted
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r"
|
||
file [38;5;1m3-sided[38;5;3m conflict including 1 deletion and [38;5;1ma directory[39m
|
||
[EOF]
|
||
");
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output, @r#"
|
||
------- stderr -------
|
||
Hint: Using default editor ':builtin'; run `jj config set --user ui.merge-editor :builtin` to disable this message.
|
||
Error: Failed to resolve conflicts
|
||
Caused by: Only conflicts that involve normal files (not symlinks, etc.) are supported. Conflict summary for "file":
|
||
Conflict:
|
||
Removing file with id df967b96a579e45a18b8251732d16804b2e56a55
|
||
Removing file with id df967b96a579e45a18b8251732d16804b2e56a55
|
||
Adding file with id 61780798228d17af2d34fce4cfbdf35556832472
|
||
Adding tree with id 133bb38fc4e4bf6b551f1f04db7e48f04cac2877
|
||
[EOF]
|
||
[exit status: 1]
|
||
"#);
|
||
}
|
||
|
||
#[test]
|
||
fn test_resolve_conflicts_with_executable() {
|
||
let mut test_env = TestEnvironment::default();
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
// Create a conflict in "file1" where all 3 terms are executables, and create a
|
||
// conflict in "file2" where one side set the executable bit.
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"base",
|
||
&[],
|
||
&[("file1", "base1\n"), ("file2", "base2\n")],
|
||
);
|
||
work_dir.run_jj(["file", "chmod", "x", "file1"]).success();
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"a",
|
||
&["base"],
|
||
&[("file1", "a1\n"), ("file2", "a2\n")],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"b",
|
||
&["base"],
|
||
&[("file1", "b1\n"), ("file2", "b2\n")],
|
||
);
|
||
work_dir.run_jj(["file", "chmod", "x", "file2"]).success();
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file1 2-sided conflict including an executable
|
||
file2 2-sided conflict including an executable
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file1"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base1
|
||
+a1
|
||
+++++++ Contents of side #2
|
||
b1
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
"
|
||
);
|
||
insta::assert_snapshot!(work_dir.read_file("file2"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base2
|
||
+a2
|
||
+++++++ Contents of side #2
|
||
b2
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
"
|
||
);
|
||
|
||
// Test resolving the conflict in "file1", which should produce an executable
|
||
std::fs::write(&editor_script, b"write\nresolution1\n").unwrap();
|
||
let output = work_dir.run_jj(["resolve", "file1"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file1
|
||
Working copy (@) now at: znkkpsqq 8ab9c54e conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl 86f7f0e3 a | a
|
||
Parent commit (@-) : yqosqzyt 36361412 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file2 2-sided conflict including an executable
|
||
New conflicts appeared in 1 commits:
|
||
znkkpsqq 8ab9c54e conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new znkkpsqq
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file1 b/file1
|
||
index 0000000000..95cc18629d 100755
|
||
--- a/file1
|
||
+++ b/file1
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base1
|
||
-+a1
|
||
-+++++++ Contents of side #2
|
||
-b1
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution1
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file2 2-sided conflict including an executable
|
||
[EOF]
|
||
");
|
||
|
||
// Test resolving the conflict in "file2", which should produce an executable
|
||
work_dir.run_jj(["undo"]).success();
|
||
std::fs::write(&editor_script, b"write\nresolution2\n").unwrap();
|
||
let output = work_dir.run_jj(["resolve", "file2"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file2
|
||
Working copy (@) now at: znkkpsqq d47830a6 conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl 86f7f0e3 a | a
|
||
Parent commit (@-) : yqosqzyt 36361412 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file1 2-sided conflict including an executable
|
||
New conflicts appeared in 1 commits:
|
||
znkkpsqq d47830a6 conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new znkkpsqq
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file2 b/file2
|
||
index 0000000000..775f078581 100755
|
||
--- a/file2
|
||
+++ b/file2
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base2
|
||
-+a2
|
||
-+++++++ Contents of side #2
|
||
-b2
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution2
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file1 2-sided conflict including an executable
|
||
[EOF]
|
||
");
|
||
|
||
// Pick "our" contents, but merges executable bits
|
||
work_dir.run_jj(["undo"]).success();
|
||
let output = work_dir.run_jj(["resolve", "--tool=:ours"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Working copy (@) now at: znkkpsqq d902c14b conflict | conflict
|
||
Parent commit (@-) : mzvwutvl 86f7f0e3 a | a
|
||
Parent commit (@-) : yqosqzyt 36361412 b | b
|
||
Added 0 files, modified 2 files, removed 0 files
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file1 b/file1
|
||
index 0000000000..da0f8ed91a 100755
|
||
--- a/file1
|
||
+++ b/file1
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base1
|
||
-+a1
|
||
-+++++++ Contents of side #2
|
||
-b1
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+a1
|
||
diff --git a/file2 b/file2
|
||
index 0000000000..c1827f07e1 100755
|
||
--- a/file2
|
||
+++ b/file2
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base2
|
||
-+a2
|
||
-+++++++ Contents of side #2
|
||
-b2
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+a2
|
||
[EOF]
|
||
");
|
||
|
||
// Pick "their" contents, but merges executable bits
|
||
work_dir.run_jj(["undo"]).success();
|
||
let output = work_dir.run_jj(["resolve", "--tool=:theirs"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Working copy (@) now at: znkkpsqq a340ca5f conflict | conflict
|
||
Parent commit (@-) : mzvwutvl 86f7f0e3 a | a
|
||
Parent commit (@-) : yqosqzyt 36361412 b | b
|
||
Added 0 files, modified 2 files, removed 0 files
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file1 b/file1
|
||
index 0000000000..c9c6af7f78 100755
|
||
--- a/file1
|
||
+++ b/file1
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base1
|
||
-+a1
|
||
-+++++++ Contents of side #2
|
||
b1
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
diff --git a/file2 b/file2
|
||
index 0000000000..e6bfff5c1d 100755
|
||
--- a/file2
|
||
+++ b/file2
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base2
|
||
-+a2
|
||
-+++++++ Contents of side #2
|
||
b2
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
[EOF]
|
||
");
|
||
}
|
||
|
||
#[test]
|
||
fn test_resolve_change_delete_executable() {
|
||
let mut test_env = TestEnvironment::default();
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
let file_template =
|
||
r#"separate(' ', path, if(conflict, "c", "-"), if(executable, "x", "-")) ++ "\n""#;
|
||
let file_list = |path: &str| work_dir.run_jj(["file", "list", "-T", file_template, path]);
|
||
|
||
// base a b
|
||
// file1: normal -> { exec, absent }
|
||
// file2: exec -> { absent, normal } (with content change)
|
||
// file3: absent -> { normal, exec }
|
||
// file4: normal -> { normal, absent } (with content change)
|
||
// file5: exec -> { absent, exec } (with content change)
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"base",
|
||
&[],
|
||
&[("file1", ""), ("file2", ""), ("file4", ""), ("file5", "")],
|
||
);
|
||
work_dir
|
||
.run_jj(["file", "chmod", "x", "file2", "file5"])
|
||
.success();
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"a",
|
||
&["base"],
|
||
&[("file1", ""), ("file3", ""), ("file4", "a4\n")],
|
||
);
|
||
work_dir.remove_file("file2");
|
||
work_dir.remove_file("file5");
|
||
work_dir.run_jj(["file", "chmod", "x", "file1"]).success();
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"b",
|
||
&["base"],
|
||
&[("file2", "b2\n"), ("file3", ""), ("file5", "b5\n")],
|
||
);
|
||
work_dir.remove_file("file1");
|
||
work_dir.remove_file("file4");
|
||
work_dir.run_jj(["file", "chmod", "n", "file2"]).success();
|
||
work_dir.run_jj(["file", "chmod", "x", "file3"]).success();
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
|
||
// Test the setup
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file1 2-sided conflict including 1 deletion and an executable
|
||
file2 2-sided conflict including 1 deletion and an executable
|
||
file3 2-sided conflict including an executable
|
||
file4 2-sided conflict including 1 deletion
|
||
file5 2-sided conflict including 1 deletion and an executable
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(file_list("all()"), @r"
|
||
file1 c -
|
||
file2 c -
|
||
file3 c -
|
||
file4 c -
|
||
file5 c x
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["log", "--git"]), @r"
|
||
@ kmkuslsw test.user@example.com 2001-02-03 08:05:18 conflict 7a7ac759 conflict
|
||
├─╮ (empty) conflict
|
||
│ ○ vruxwmqv test.user@example.com 2001-02-03 08:05:17 b 888b6cc3
|
||
│ │ b
|
||
│ │ diff --git a/file1 b/file1
|
||
│ │ deleted file mode 100644
|
||
│ │ index e69de29bb2..0000000000
|
||
│ │ diff --git a/file2 b/file2
|
||
│ │ old mode 100755
|
||
│ │ new mode 100644
|
||
│ │ index e69de29bb2..e6bfff5c1d
|
||
│ │ --- a/file2
|
||
│ │ +++ b/file2
|
||
│ │ @@ -0,0 +1,1 @@
|
||
│ │ +b2
|
||
│ │ diff --git a/file3 b/file3
|
||
│ │ new file mode 100755
|
||
│ │ index 0000000000..e69de29bb2
|
||
│ │ diff --git a/file4 b/file4
|
||
│ │ deleted file mode 100644
|
||
│ │ index e69de29bb2..0000000000
|
||
│ │ diff --git a/file5 b/file5
|
||
│ │ index e69de29bb2..90a5159bf0 100755
|
||
│ │ --- a/file5
|
||
│ │ +++ b/file5
|
||
│ │ @@ -0,0 +1,1 @@
|
||
│ │ +b5
|
||
○ │ mzvwutvl test.user@example.com 2001-02-03 08:05:13 a e2d3924b
|
||
├─╯ a
|
||
│ diff --git a/file1 b/file1
|
||
│ old mode 100644
|
||
│ new mode 100755
|
||
│ diff --git a/file2 b/file2
|
||
│ deleted file mode 100755
|
||
│ index e69de29bb2..0000000000
|
||
│ diff --git a/file3 b/file3
|
||
│ new file mode 100644
|
||
│ index 0000000000..e69de29bb2
|
||
│ diff --git a/file4 b/file4
|
||
│ index e69de29bb2..88ba23dca8 100644
|
||
│ --- a/file4
|
||
│ +++ b/file4
|
||
│ @@ -0,0 +1,1 @@
|
||
│ +a4
|
||
│ diff --git a/file5 b/file5
|
||
│ deleted file mode 100755
|
||
│ index e69de29bb2..0000000000
|
||
○ rlvkpnrz test.user@example.com 2001-02-03 08:05:10 base f747aa1f
|
||
│ base
|
||
│ diff --git a/file1 b/file1
|
||
│ new file mode 100644
|
||
│ index 0000000000..e69de29bb2
|
||
│ diff --git a/file2 b/file2
|
||
│ new file mode 100755
|
||
│ index 0000000000..e69de29bb2
|
||
│ diff --git a/file4 b/file4
|
||
│ new file mode 100644
|
||
│ index 0000000000..e69de29bb2
|
||
│ diff --git a/file5 b/file5
|
||
│ new file mode 100755
|
||
│ index 0000000000..e69de29bb2
|
||
◆ zzzzzzzz root() 00000000
|
||
[EOF]
|
||
");
|
||
|
||
// Exec bit conflict can be resolved by chmod
|
||
let output = work_dir.run_jj(["resolve", "file1"]);
|
||
insta::assert_snapshot!(output, @r#"
|
||
------- stderr -------
|
||
Error: Failed to resolve conflicts
|
||
Caused by: "file1" has conflicts in executable bit
|
||
Conflict:
|
||
Removing file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||
Adding executable file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||
Hint: Use `jj file chmod` to update the executable bit.
|
||
[EOF]
|
||
[exit status: 1]
|
||
"#);
|
||
let output = work_dir.run_jj(["file", "chmod", "--quiet", "x", "file1"]);
|
||
insta::assert_snapshot!(output, @"");
|
||
|
||
// Exec bit conflict can be resolved by chmod, then content conflict
|
||
let output = work_dir.run_jj(["resolve", "file2"]);
|
||
insta::assert_snapshot!(output, @r#"
|
||
------- stderr -------
|
||
Error: Failed to resolve conflicts
|
||
Caused by: "file2" has conflicts in executable bit
|
||
Conflict:
|
||
Removing executable file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||
Adding file with id e6bfff5c1d0f0ecd501552b43a1e13d8008abc31
|
||
Hint: Use `jj file chmod` to update the executable bit.
|
||
[EOF]
|
||
[exit status: 1]
|
||
"#);
|
||
let output = work_dir.run_jj(["file", "chmod", "--quiet", "n", "file2"]);
|
||
insta::assert_snapshot!(output, @"");
|
||
std::fs::write(&editor_script, "write\nresolved\n").unwrap();
|
||
let output = work_dir.run_jj(["resolve", "file2"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file2
|
||
Working copy (@) now at: kmkuslsw 1323520e conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl e2d3924b a | a
|
||
Parent commit (@-) : vruxwmqv 888b6cc3 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file3 2-sided conflict including an executable
|
||
file4 2-sided conflict including 1 deletion
|
||
file5 2-sided conflict including 1 deletion and an executable
|
||
[EOF]
|
||
");
|
||
|
||
// Exec bit conflict can be resolved by chmod
|
||
let output = work_dir.run_jj(["resolve", "file3"]);
|
||
insta::assert_snapshot!(output, @r#"
|
||
------- stderr -------
|
||
Error: Failed to resolve conflicts
|
||
Caused by: "file3" has conflicts in executable bit
|
||
Conflict:
|
||
Adding file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||
Adding executable file with id e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||
Hint: Use `jj file chmod` to update the executable bit.
|
||
[EOF]
|
||
[exit status: 1]
|
||
"#);
|
||
let output = work_dir.run_jj(["file", "chmod", "--quiet", "x", "file3"]);
|
||
insta::assert_snapshot!(output, @"");
|
||
|
||
// Take modified content, the executable bit should be kept as "-"
|
||
let output = work_dir.run_jj(["resolve", "file4", "--tool=:ours"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Working copy (@) now at: kmkuslsw 630e8689 conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl e2d3924b a | a
|
||
Parent commit (@-) : vruxwmqv 888b6cc3 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file5 2-sided conflict including 1 deletion and an executable
|
||
[EOF]
|
||
");
|
||
|
||
// Take modified content, the executable bit should be kept as "x"
|
||
let output = work_dir.run_jj(["resolve", "file5", "--tool=:theirs"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Working copy (@) now at: kmkuslsw 7337267a conflict | conflict
|
||
Parent commit (@-) : mzvwutvl e2d3924b a | a
|
||
Parent commit (@-) : vruxwmqv 888b6cc3 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Existing conflicts were resolved or abandoned from 1 commits.
|
||
[EOF]
|
||
");
|
||
|
||
insta::assert_snapshot!(file_list("all()"), @r"
|
||
file2 - -
|
||
file3 - x
|
||
file4 - -
|
||
file5 - x
|
||
[EOF]
|
||
");
|
||
}
|
||
|
||
#[test]
|
||
fn test_resolve_long_conflict_markers() {
|
||
let mut test_env = TestEnvironment::default();
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
// Makes it easier to read the diffs between conflicts
|
||
test_env.add_config("ui.conflict-marker-style = 'snapshot'");
|
||
|
||
// Create a conflict which requires long conflict markers to be materialized
|
||
create_commit_with_files(&work_dir, "base", &[], &[("file", "======= base\n")]);
|
||
create_commit_with_files(&work_dir, "a", &["base"], &[("file", "<<<<<<< a\n")]);
|
||
create_commit_with_files(&work_dir, "b", &["base"], &[("file", ">>>>>>> b\n")]);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @r"
|
||
<<<<<<<<<<< Conflict 1 of 1
|
||
+++++++++++ Contents of side #1
|
||
<<<<<<< a
|
||
----------- Contents of base
|
||
======= base
|
||
+++++++++++ Contents of side #2
|
||
>>>>>>> b
|
||
>>>>>>>>>>> Conflict 1 of 1 ends
|
||
"
|
||
);
|
||
// Allow signaling that conflict markers were produced even if not editing
|
||
// conflict markers materialized in the output file
|
||
test_env.add_config("merge-tools.fake-editor.merge-conflict-exit-codes = [1]");
|
||
|
||
// By default, conflict markers of length 7 or longer are parsed for
|
||
// compatibility with Git merge tools
|
||
std::fs::write(
|
||
&editor_script,
|
||
indoc! {b"
|
||
write
|
||
<<<<<<<
|
||
A
|
||
|||||||
|
||
BASE
|
||
=======
|
||
B
|
||
>>>>>>>
|
||
\0fail
|
||
"},
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 1e254ee3 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 10d994ef a | a
|
||
Parent commit (@-) : royxmykx 7f215575 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv 1e254ee3 conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,8 +1,8 @@
|
||
-<<<<<<<<<<< Conflict 1 of 1
|
||
-+++++++++++ Contents of side #1
|
||
-<<<<<<< a
|
||
------------ Contents of base
|
||
-======= base
|
||
-+++++++++++ Contents of side #2
|
||
->>>>>>> b
|
||
->>>>>>>>>>> Conflict 1 of 1 ends
|
||
+<<<<<<< Conflict 1 of 1
|
||
++++++++ Contents of side #1
|
||
+A
|
||
+------- Contents of base
|
||
+BASE
|
||
++++++++ Contents of side #2
|
||
+B
|
||
+>>>>>>> Conflict 1 of 1 ends
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// If the merge tool edits the output file with materialized markers, the
|
||
// markers must match the length of the materialized markers to be parsed
|
||
work_dir.run_jj(["undo"]).success();
|
||
std::fs::write(
|
||
&editor_script,
|
||
indoc! {b"
|
||
dump editor
|
||
\0write
|
||
<<<<<<<<<<<
|
||
<<<<<<< A
|
||
|||||||||||
|
||
======= BASE
|
||
===========
|
||
>>>>>>> B
|
||
>>>>>>>>>>>
|
||
\0fail
|
||
"},
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
"--config=merge-tools.fake-editor.merge-tool-edits-conflict-markers=true",
|
||
]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 2481a401 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 10d994ef a | a
|
||
Parent commit (@-) : royxmykx 7f215575 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv 2481a401 conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(
|
||
std::fs::read_to_string(test_env.env_root().join("editor")).unwrap(), @r"
|
||
<<<<<<<<<<< Conflict 1 of 1
|
||
+++++++++++ Contents of side #1
|
||
<<<<<<< a
|
||
----------- Contents of base
|
||
======= base
|
||
+++++++++++ Contents of side #2
|
||
>>>>>>> b
|
||
>>>>>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,8 +1,8 @@
|
||
<<<<<<<<<<< Conflict 1 of 1
|
||
+++++++++++ Contents of side #1
|
||
-<<<<<<< a
|
||
+<<<<<<< A
|
||
----------- Contents of base
|
||
-======= base
|
||
+======= BASE
|
||
+++++++++++ Contents of side #2
|
||
->>>>>>> b
|
||
+>>>>>>> B
|
||
>>>>>>>>>>> Conflict 1 of 1 ends
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// If the merge tool accepts the marker length as an argument, then the conflict
|
||
// markers should be at least as long as "$marker_length"
|
||
work_dir.run_jj(["undo"]).success();
|
||
std::fs::write(
|
||
&editor_script,
|
||
indoc! {b"
|
||
expect-arg 0
|
||
11\0write
|
||
<<<<<<<<<<<
|
||
<<<<<<< A
|
||
|||||||||||
|
||
======= BASE
|
||
===========
|
||
>>>>>>> B
|
||
>>>>>>>>>>>
|
||
\0fail
|
||
"},
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj([
|
||
"resolve",
|
||
r#"--config=merge-tools.fake-editor.merge-args=["$output", "$marker_length"]"#,
|
||
]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file
|
||
Working copy (@) now at: vruxwmqv 2cf0bfd3 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 10d994ef a | a
|
||
Parent commit (@-) : royxmykx 7f215575 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv 2cf0bfd3 conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file b/file
|
||
--- a/file
|
||
+++ b/file
|
||
@@ -1,8 +1,8 @@
|
||
<<<<<<<<<<< Conflict 1 of 1
|
||
+++++++++++ Contents of side #1
|
||
-<<<<<<< a
|
||
+<<<<<<< A
|
||
----------- Contents of base
|
||
-======= base
|
||
+======= BASE
|
||
+++++++++++ Contents of side #2
|
||
->>>>>>> b
|
||
+>>>>>>> B
|
||
>>>>>>>>>>> Conflict 1 of 1 ends
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
[EOF]
|
||
");
|
||
}
|
||
|
||
#[test]
|
||
fn test_multiple_conflicts() {
|
||
let mut test_env = TestEnvironment::default();
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"base",
|
||
&[],
|
||
&[
|
||
(
|
||
"this_file_has_a_very_long_name_to_test_padding",
|
||
"first base\n",
|
||
),
|
||
("another_file", "second base\n"),
|
||
],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"a",
|
||
&["base"],
|
||
&[
|
||
(
|
||
"this_file_has_a_very_long_name_to_test_padding",
|
||
"first a\n",
|
||
),
|
||
("another_file", "second a\n"),
|
||
],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"b",
|
||
&["base"],
|
||
&[
|
||
(
|
||
"this_file_has_a_very_long_name_to_test_padding",
|
||
"first b\n",
|
||
),
|
||
("another_file", "second b\n"),
|
||
],
|
||
);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
// Test the setup
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─╮
|
||
│ ○ b
|
||
○ │ a
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(
|
||
work_dir.read_file("this_file_has_a_very_long_name_to_test_padding"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-first base
|
||
+first a
|
||
+++++++ Contents of side #2
|
||
first b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("another_file"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-second base
|
||
+second a
|
||
+++++++ Contents of side #2
|
||
second b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
another_file 2-sided conflict
|
||
this_file_has_a_very_long_name_to_test_padding 2-sided conflict
|
||
[EOF]
|
||
");
|
||
// Test colors
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list", "--color=always"]), @r"
|
||
another_file [38;5;3m2-sided conflict[39m
|
||
this_file_has_a_very_long_name_to_test_padding [38;5;3m2-sided conflict[39m
|
||
[EOF]
|
||
");
|
||
|
||
// Check that we can manually pick which of the conflicts to resolve first
|
||
std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap();
|
||
let output = work_dir.run_jj(["resolve", "another_file"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: another_file
|
||
Working copy (@) now at: vruxwmqv d3584f6e conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 2c821f70 a | a
|
||
Parent commit (@-) : royxmykx 4c2029de b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
this_file_has_a_very_long_name_to_test_padding 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv d3584f6e conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/another_file b/another_file
|
||
index 0000000000..a9fcc7d486 100644
|
||
--- a/another_file
|
||
+++ b/another_file
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--second base
|
||
-+second a
|
||
-+++++++ Contents of side #2
|
||
-second b
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution another_file
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
this_file_has_a_very_long_name_to_test_padding 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// Repeat the above with the `--quiet` option.
|
||
work_dir.run_jj(["undo"]).success();
|
||
std::fs::write(&editor_script, "expect\n\0write\nresolution another_file\n").unwrap();
|
||
let output = work_dir.run_jj(["resolve", "--quiet", "another_file"]);
|
||
insta::assert_snapshot!(output, @"");
|
||
|
||
// Without a path, `jj resolve` should call the merge tool multiple times
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
std::fs::write(
|
||
&editor_script,
|
||
[
|
||
"expect\n",
|
||
"write\nfirst resolution for auto-chosen file\n",
|
||
"next invocation\n",
|
||
"expect\n",
|
||
"write\nsecond resolution for auto-chosen file\n",
|
||
]
|
||
.join("\0"),
|
||
)
|
||
.unwrap();
|
||
work_dir.run_jj(["resolve"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/another_file b/another_file
|
||
index 0000000000..7903e1c1c7 100644
|
||
--- a/another_file
|
||
+++ b/another_file
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--second base
|
||
-+second a
|
||
-+++++++ Contents of side #2
|
||
-second b
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+first resolution for auto-chosen file
|
||
diff --git a/this_file_has_a_very_long_name_to_test_padding b/this_file_has_a_very_long_name_to_test_padding
|
||
index 0000000000..f8c72adf17 100644
|
||
--- a/this_file_has_a_very_long_name_to_test_padding
|
||
+++ b/this_file_has_a_very_long_name_to_test_padding
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--first base
|
||
-+first a
|
||
-+++++++ Contents of side #2
|
||
-first b
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+second resolution for auto-chosen file
|
||
[EOF]
|
||
");
|
||
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
------- stderr -------
|
||
Error: No conflicts found at this revision
|
||
[EOF]
|
||
[exit status: 2]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve"]), @r"
|
||
------- stderr -------
|
||
Error: No conflicts found at this revision
|
||
[EOF]
|
||
[exit status: 2]
|
||
");
|
||
}
|
||
|
||
#[test]
|
||
fn test_multiple_conflicts_with_error() {
|
||
let mut test_env = TestEnvironment::default();
|
||
let editor_script = test_env.set_up_fake_editor();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
// Create two conflicted files, and one non-conflicted file
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"base",
|
||
&[],
|
||
&[
|
||
("file1", "base1\n"),
|
||
("file2", "base2\n"),
|
||
("file3", "base3\n"),
|
||
],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"a",
|
||
&["base"],
|
||
&[("file1", "a1\n"), ("file2", "a2\n")],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"b",
|
||
&["base"],
|
||
&[("file1", "b1\n"), ("file2", "b2\n")],
|
||
);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b"], &[]);
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file1 2-sided conflict
|
||
file2 2-sided conflict
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file1"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base1
|
||
+a1
|
||
+++++++ Contents of side #2
|
||
b1
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
"
|
||
);
|
||
insta::assert_snapshot!(work_dir.read_file("file2"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base2
|
||
+a2
|
||
+++++++ Contents of side #2
|
||
b2
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
"
|
||
);
|
||
|
||
// Test resolving one conflict, then exiting without resolving the second one
|
||
std::fs::write(
|
||
&editor_script,
|
||
["write\nresolution1\n", "next invocation\n"].join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file1
|
||
Resolving conflicts in: file2
|
||
Working copy (@) now at: vruxwmqv 98296abe conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 6c31698c a | a
|
||
Parent commit (@-) : royxmykx ba0a5538 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file2 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv 98296abe conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
Error: Stopped due to error after resolving 1 conflicts
|
||
Caused by: The output file is either unchanged or empty after the editor quit (run with --debug to see the exact invocation).
|
||
[EOF]
|
||
[exit status: 1]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file1 b/file1
|
||
index 0000000000..95cc18629d 100644
|
||
--- a/file1
|
||
+++ b/file1
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base1
|
||
-+a1
|
||
-+++++++ Contents of side #2
|
||
-b1
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution1
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file2 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// Test resolving one conflict, then failing during the second resolution
|
||
work_dir.run_jj(["undo"]).success();
|
||
std::fs::write(
|
||
&editor_script,
|
||
["write\nresolution1\n", "next invocation\n", "fail"].join("\0"),
|
||
)
|
||
.unwrap();
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file1
|
||
Resolving conflicts in: file2
|
||
Working copy (@) now at: vruxwmqv 7daa6406 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 6c31698c a | a
|
||
Parent commit (@-) : royxmykx ba0a5538 b | b
|
||
Added 0 files, modified 1 files, removed 0 files
|
||
Warning: There are unresolved conflicts at these paths:
|
||
file2 2-sided conflict
|
||
New conflicts appeared in 1 commits:
|
||
vruxwmqv 7daa6406 conflict | (conflict) conflict
|
||
Hint: To resolve the conflicts, start by updating to it:
|
||
jj new vruxwmqv
|
||
Then use `jj resolve`, or edit the conflict markers in the file directly.
|
||
Once the conflicts are resolved, you may want to inspect the result with `jj diff`.
|
||
Then run `jj squash` to move the resolution into the conflicted commit.
|
||
Error: Stopped due to error after resolving 1 conflicts
|
||
Caused by: Tool exited with exit status: 1 (run with --debug to see the exact invocation)
|
||
[EOF]
|
||
[exit status: 1]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @r"
|
||
diff --git a/file1 b/file1
|
||
index 0000000000..95cc18629d 100644
|
||
--- a/file1
|
||
+++ b/file1
|
||
@@ -1,7 +1,1 @@
|
||
-<<<<<<< Conflict 1 of 1
|
||
-%%%%%%% Changes from base to side #1
|
||
--base1
|
||
-+a1
|
||
-+++++++ Contents of side #2
|
||
-b1
|
||
->>>>>>> Conflict 1 of 1 ends
|
||
+resolution1
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file2 2-sided conflict
|
||
[EOF]
|
||
");
|
||
|
||
// Test immediately failing to resolve any conflict
|
||
work_dir.run_jj(["undo"]).success();
|
||
std::fs::write(&editor_script, "fail").unwrap();
|
||
let output = work_dir.run_jj(["resolve"]);
|
||
insta::assert_snapshot!(output.normalize_stderr_exit_status(), @r"
|
||
------- stderr -------
|
||
Resolving conflicts in: file1
|
||
Error: Failed to resolve conflicts
|
||
Caused by: Tool exited with exit status: 1 (run with --debug to see the exact invocation)
|
||
[EOF]
|
||
[exit status: 1]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file1 2-sided conflict
|
||
file2 2-sided conflict
|
||
[EOF]
|
||
");
|
||
}
|
||
|
||
#[test]
|
||
fn test_resolve_with_contents_of_side() {
|
||
let test_env = TestEnvironment::default();
|
||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||
let work_dir = test_env.work_dir("repo");
|
||
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"base",
|
||
&[],
|
||
&[("file", "base\n"), ("other", "base\n")],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"a",
|
||
&["base"],
|
||
&[("file", "a\n"), ("other", "base\n")],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"b",
|
||
&["base"],
|
||
&[("file", "base\n"), ("other", "left\n")],
|
||
);
|
||
create_commit_with_files(
|
||
&work_dir,
|
||
"c",
|
||
&["base"],
|
||
&[("file", "b\n"), ("other", "right\n")],
|
||
);
|
||
create_commit_with_files(&work_dir, "conflict", &["a", "b", "c"], &[]);
|
||
// Test the setup
|
||
insta::assert_snapshot!(get_log_output(&work_dir), @r"
|
||
@ conflict
|
||
├─┬─╮
|
||
│ │ ○ c
|
||
│ ○ │ b
|
||
│ ├─╯
|
||
○ │ a
|
||
├─╯
|
||
○ base
|
||
◆
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r"
|
||
file 2-sided conflict
|
||
other 2-sided conflict
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+a
|
||
+++++++ Contents of side #2
|
||
b
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("other"), @r"
|
||
<<<<<<< Conflict 1 of 1
|
||
%%%%%%% Changes from base to side #1
|
||
-base
|
||
+left
|
||
+++++++ Contents of side #2
|
||
right
|
||
>>>>>>> Conflict 1 of 1 ends
|
||
");
|
||
|
||
// Check that ":ours" merge tool works correctly
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
let output = work_dir.run_jj(["resolve", "--tool", ":ours"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Working copy (@) now at: znkkpsqq 5410a03a conflict | conflict
|
||
Parent commit (@-) : zsuskuln 72dced6e a | a
|
||
Parent commit (@-) : royxmykx e5747f42 b | b
|
||
Parent commit (@-) : vruxwmqv dd35236a c | c
|
||
Added 0 files, modified 2 files, removed 0 files
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @"a");
|
||
insta::assert_snapshot!(work_dir.read_file("other"), @"left");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r#"
|
||
------- stderr -------
|
||
Error: No conflicts found at this revision
|
||
[EOF]
|
||
[exit status: 2]
|
||
"#);
|
||
|
||
// Check that ":theirs" merge tool works correctly
|
||
work_dir.run_jj(["undo"]).success();
|
||
insta::assert_snapshot!(work_dir.run_jj(["diff", "--git"]), @"");
|
||
let output = work_dir.run_jj(["resolve", "--tool", ":theirs"]);
|
||
insta::assert_snapshot!(output, @r"
|
||
------- stderr -------
|
||
Working copy (@) now at: znkkpsqq c07b2e9e conflict | conflict
|
||
Parent commit (@-) : zsuskuln 72dced6e a | a
|
||
Parent commit (@-) : royxmykx e5747f42 b | b
|
||
Parent commit (@-) : vruxwmqv dd35236a c | c
|
||
Added 0 files, modified 2 files, removed 0 files
|
||
[EOF]
|
||
");
|
||
insta::assert_snapshot!(work_dir.read_file("file"), @"b");
|
||
insta::assert_snapshot!(work_dir.read_file("other"), @"right");
|
||
insta::assert_snapshot!(work_dir.run_jj(["resolve", "--list"]), @r#"
|
||
------- stderr -------
|
||
Error: No conflicts found at this revision
|
||
[EOF]
|
||
[exit status: 2]
|
||
"#);
|
||
}
|