mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-05 15:32:49 +00:00
This reverts a01d0bf7738f "cli: resolve: leave executable bit unchanged when using external tool", and updates handling of resolved content. Fixes #6250
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 e069f073 conflict | conflict
|
||
Parent commit (@-) : zsuskuln aa493daf a | a
|
||
Parent commit (@-) : royxmykx db6a4daf 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 1a70c7c6 conflict | conflict
|
||
Parent commit (@-) : zsuskuln aa493daf a | a
|
||
Parent commit (@-) : royxmykx db6a4daf 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 608a2310 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln aa493daf a | a
|
||
Parent commit (@-) : royxmykx db6a4daf 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 608a2310 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 3166dfd2 conflict | conflict
|
||
Parent commit (@-) : zsuskuln aa493daf a | a
|
||
Parent commit (@-) : royxmykx db6a4daf 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 8e03fefa conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln aa493daf a | a
|
||
Parent commit (@-) : royxmykx db6a4daf 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 8e03fefa 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 a786ac2f conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln aa493daf a | a
|
||
Parent commit (@-) : royxmykx db6a4daf 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 a786ac2f 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 69cc0c2d conflict | (conflict) conflict
|
||
Parent commit (@-) : kmkuslsw 4601566f conflictA | (conflict) (empty) conflictA
|
||
Parent commit (@-) : lylxulpl 6f8d8381 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 69cc0c2d 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 eb159d56 conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl 08932848 a | a
|
||
Parent commit (@-) : yqosqzyt b69b3de6 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 eb159d56 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 4dccbb3c conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl 08932848 a | a
|
||
Parent commit (@-) : yqosqzyt b69b3de6 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 4dccbb3c 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 5b59d7f0 conflict | conflict
|
||
Parent commit (@-) : mzvwutvl 08932848 a | a
|
||
Parent commit (@-) : yqosqzyt b69b3de6 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 087b8637 conflict | conflict
|
||
Parent commit (@-) : mzvwutvl 08932848 a | a
|
||
Parent commit (@-) : yqosqzyt b69b3de6 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 5db5dbdf conflict
|
||
├─╮ (empty) conflict
|
||
│ ○ vruxwmqv test.user@example.com 2001-02-03 08:05:17 b 19e7d2ff
|
||
│ │ 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 4a44e1a9
|
||
├─╯ 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 dd88cb4a
|
||
│ 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 cab8be9b conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl 4a44e1a9 a | a
|
||
Parent commit (@-) : vruxwmqv 19e7d2ff 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 14b5c3d2 conflict | (conflict) conflict
|
||
Parent commit (@-) : mzvwutvl 4a44e1a9 a | a
|
||
Parent commit (@-) : vruxwmqv 19e7d2ff 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 5de68577 conflict | conflict
|
||
Parent commit (@-) : mzvwutvl 4a44e1a9 a | a
|
||
Parent commit (@-) : vruxwmqv 19e7d2ff 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 2b985546 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 64177fd4 a | a
|
||
Parent commit (@-) : royxmykx db442c1e 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 2b985546 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 fac9406d conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 64177fd4 a | a
|
||
Parent commit (@-) : royxmykx db442c1e 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 fac9406d 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 1b29631a conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 64177fd4 a | a
|
||
Parent commit (@-) : royxmykx db442c1e 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 1b29631a 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 309e981c conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln de7553ef a | a
|
||
Parent commit (@-) : royxmykx f68bc2f0 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 309e981c 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 d2f3f858 conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 9db7fdfb a | a
|
||
Parent commit (@-) : royxmykx d67e26e4 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 d2f3f858 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 0a54e8ed conflict | (conflict) conflict
|
||
Parent commit (@-) : zsuskuln 9db7fdfb a | a
|
||
Parent commit (@-) : royxmykx d67e26e4 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 0a54e8ed 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 c83edc52 conflict | conflict
|
||
Parent commit (@-) : zsuskuln 83b81681 a | a
|
||
Parent commit (@-) : royxmykx 4f6eff49 b | b
|
||
Parent commit (@-) : vruxwmqv 1e360aff 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 baf3a71d conflict | conflict
|
||
Parent commit (@-) : zsuskuln 83b81681 a | a
|
||
Parent commit (@-) : royxmykx 4f6eff49 b | b
|
||
Parent commit (@-) : vruxwmqv 1e360aff 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]
|
||
"#);
|
||
}
|