2022-11-26 23:57:50 +00:00
|
|
|
// Copyright 2021 The Jujutsu Authors
|
2021-10-13 19:53:35 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2023-08-11 05:06:38 +00:00
|
|
|
use jj_lib::backend::FileId;
|
|
|
|
use jj_lib::conflicts::{
|
|
|
|
extract_as_single_hunk, materialize_merge_result, parse_conflict, update_from_content,
|
|
|
|
};
|
2023-08-06 16:21:35 +00:00
|
|
|
use jj_lib::merge::Merge;
|
2023-06-28 14:12:40 +00:00
|
|
|
use jj_lib::repo::Repo;
|
|
|
|
use jj_lib::repo_path::RepoPath;
|
|
|
|
use jj_lib::store::Store;
|
2022-11-08 12:35:16 +00:00
|
|
|
use testutils::TestRepo;
|
2021-10-13 19:53:35 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_materialize_conflict_basic() {
|
2022-05-21 18:20:51 +00:00
|
|
|
let test_repo = TestRepo::init(false);
|
2022-02-05 23:34:27 +00:00
|
|
|
let store = test_repo.repo.store();
|
2021-10-13 19:53:35 +00:00
|
|
|
|
|
|
|
let path = RepoPath::from_internal_string("file");
|
|
|
|
let base_id = testutils::write_file(
|
2021-11-21 07:46:54 +00:00
|
|
|
store,
|
2021-10-13 19:53:35 +00:00
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2
|
|
|
|
line 3
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
);
|
|
|
|
let left_id = testutils::write_file(
|
2021-11-21 07:46:54 +00:00
|
|
|
store,
|
2021-10-13 19:53:35 +00:00
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2
|
2022-07-10 17:36:45 +00:00
|
|
|
left 3.1
|
|
|
|
left 3.2
|
|
|
|
left 3.3
|
2021-10-13 19:53:35 +00:00
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
);
|
|
|
|
let right_id = testutils::write_file(
|
2021-11-21 07:46:54 +00:00
|
|
|
store,
|
2021-10-13 19:53:35 +00:00
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2
|
2022-07-10 17:36:45 +00:00
|
|
|
right 3.1
|
2021-10-13 19:53:35 +00:00
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
);
|
|
|
|
|
conflicts: preserve order of adds in materialized conflict
We write conflict to the working copy by materializing them as
conflict markers in a file. When the file has been modified (or just
the mtime has changed), we parse the markers to reconstruct the
conflict. For example, let's say we see this conflict marker:
```
<<<<<<<
+++++++
b
%%%%%%%
-a
+c
>>>>>>>
```
Then we will create a hunk with ["a"] as removed and ["b", "c"] as
added.
Now, since commit b84be06c0822, when we materialize conflicts, we
minimize the diff part of the marker (the `%%%%%%%` part). The problem
is that that minimization may result in a different order of the
positive conflict terms. That's particularly bad because we do the
minimization per hunk, so we can end up reconstructing an input that
never existed.
This commit fixes the bug by only considering the next add and the one
after that, and emitting either only the first with `%%%%%%%`, or both
of them, with the first one in `++++++++` and the second one in
`%%%%%%%`.
Note that the recent fix to add context to modify/delete conflicts
means that when we parse modified such conflicts, we'll always
consider them resolved, since the expected adds/removes we pass will
not match what's actually in the file. That doesn't seem so bad, and
it's not obvious what the fix should be, so I'll leave that for later.
2023-02-18 06:29:30 +00:00
|
|
|
// The left side should come first. The diff should be use the smaller (right)
|
|
|
|
// side, and the left side should be a snapshot.
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone())],
|
|
|
|
vec![Some(left_id.clone()), Some(right_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2022-07-10 18:01:48 +00:00
|
|
|
insta::assert_snapshot!(
|
|
|
|
&materialize_conflict_string(store, &path, &conflict),
|
|
|
|
@r###"
|
|
|
|
line 1
|
|
|
|
line 2
|
|
|
|
<<<<<<<
|
2022-07-10 17:36:45 +00:00
|
|
|
+++++++
|
|
|
|
left 3.1
|
|
|
|
left 3.2
|
|
|
|
left 3.3
|
conflicts: preserve order of adds in materialized conflict
We write conflict to the working copy by materializing them as
conflict markers in a file. When the file has been modified (or just
the mtime has changed), we parse the markers to reconstruct the
conflict. For example, let's say we see this conflict marker:
```
<<<<<<<
+++++++
b
%%%%%%%
-a
+c
>>>>>>>
```
Then we will create a hunk with ["a"] as removed and ["b", "c"] as
added.
Now, since commit b84be06c0822, when we materialize conflicts, we
minimize the diff part of the marker (the `%%%%%%%` part). The problem
is that that minimization may result in a different order of the
positive conflict terms. That's particularly bad because we do the
minimization per hunk, so we can end up reconstructing an input that
never existed.
This commit fixes the bug by only considering the next add and the one
after that, and emitting either only the first with `%%%%%%%`, or both
of them, with the first one in `++++++++` and the second one in
`%%%%%%%`.
Note that the recent fix to add context to modify/delete conflicts
means that when we parse modified such conflicts, we'll always
consider them resolved, since the expected adds/removes we pass will
not match what's actually in the file. That doesn't seem so bad, and
it's not obvious what the fix should be, so I'll leave that for later.
2023-02-18 06:29:30 +00:00
|
|
|
%%%%%%%
|
|
|
|
-line 3
|
|
|
|
+right 3.1
|
2022-07-10 17:36:45 +00:00
|
|
|
>>>>>>>
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
"###
|
|
|
|
);
|
conflicts: preserve order of adds in materialized conflict
We write conflict to the working copy by materializing them as
conflict markers in a file. When the file has been modified (or just
the mtime has changed), we parse the markers to reconstruct the
conflict. For example, let's say we see this conflict marker:
```
<<<<<<<
+++++++
b
%%%%%%%
-a
+c
>>>>>>>
```
Then we will create a hunk with ["a"] as removed and ["b", "c"] as
added.
Now, since commit b84be06c0822, when we materialize conflicts, we
minimize the diff part of the marker (the `%%%%%%%` part). The problem
is that that minimization may result in a different order of the
positive conflict terms. That's particularly bad because we do the
minimization per hunk, so we can end up reconstructing an input that
never existed.
This commit fixes the bug by only considering the next add and the one
after that, and emitting either only the first with `%%%%%%%`, or both
of them, with the first one in `++++++++` and the second one in
`%%%%%%%`.
Note that the recent fix to add context to modify/delete conflicts
means that when we parse modified such conflicts, we'll always
consider them resolved, since the expected adds/removes we pass will
not match what's actually in the file. That doesn't seem so bad, and
it's not obvious what the fix should be, so I'll leave that for later.
2023-02-18 06:29:30 +00:00
|
|
|
// Swap the positive terms in the conflict. The diff should still use the right
|
|
|
|
// side, but now the right side should come first.
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone())],
|
|
|
|
vec![Some(right_id.clone()), Some(left_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2022-07-10 17:36:45 +00:00
|
|
|
insta::assert_snapshot!(
|
|
|
|
&materialize_conflict_string(store, &path, &conflict),
|
|
|
|
@r###"
|
|
|
|
line 1
|
|
|
|
line 2
|
|
|
|
<<<<<<<
|
2022-09-20 03:31:20 +00:00
|
|
|
%%%%%%%
|
2022-07-10 17:36:45 +00:00
|
|
|
-line 3
|
|
|
|
+right 3.1
|
2022-07-10 18:01:48 +00:00
|
|
|
+++++++
|
2022-07-10 17:36:45 +00:00
|
|
|
left 3.1
|
|
|
|
left 3.2
|
|
|
|
left 3.3
|
2022-07-10 18:01:48 +00:00
|
|
|
>>>>>>>
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
"###
|
2021-10-13 19:53:35 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-02-24 04:04:26 +00:00
|
|
|
#[test]
|
|
|
|
fn test_materialize_conflict_multi_rebase_conflicts() {
|
|
|
|
let test_repo = TestRepo::init(false);
|
|
|
|
let store = test_repo.repo.store();
|
|
|
|
|
|
|
|
// Create changes (a, b, c) on top of the base, and linearize them.
|
|
|
|
let path = RepoPath::from_internal_string("file");
|
|
|
|
let base_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2 base
|
|
|
|
line 3
|
|
|
|
",
|
|
|
|
);
|
|
|
|
let a_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2 a.1
|
|
|
|
line 2 a.2
|
|
|
|
line 2 a.3
|
|
|
|
line 3
|
|
|
|
",
|
|
|
|
);
|
|
|
|
let b_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2 b.1
|
|
|
|
line 2 b.2
|
|
|
|
line 3
|
|
|
|
",
|
|
|
|
);
|
|
|
|
let c_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2 c.1
|
|
|
|
line 3
|
|
|
|
",
|
|
|
|
);
|
|
|
|
|
|
|
|
// The order of (a, b, c) should be preserved. For all cases, the "a" side
|
|
|
|
// should be a snapshot.
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone()), Some(base_id.clone())],
|
|
|
|
vec![Some(a_id.clone()), Some(b_id.clone()), Some(c_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2023-02-24 04:04:26 +00:00
|
|
|
insta::assert_snapshot!(
|
|
|
|
&materialize_conflict_string(store, &path, &conflict),
|
|
|
|
@r###"
|
|
|
|
line 1
|
|
|
|
<<<<<<<
|
|
|
|
+++++++
|
|
|
|
line 2 a.1
|
|
|
|
line 2 a.2
|
|
|
|
line 2 a.3
|
|
|
|
%%%%%%%
|
|
|
|
-line 2 base
|
|
|
|
+line 2 b.1
|
|
|
|
+line 2 b.2
|
|
|
|
%%%%%%%
|
|
|
|
-line 2 base
|
|
|
|
+line 2 c.1
|
|
|
|
>>>>>>>
|
|
|
|
line 3
|
|
|
|
"###
|
|
|
|
);
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone()), Some(base_id.clone())],
|
|
|
|
vec![Some(c_id.clone()), Some(b_id.clone()), Some(a_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2023-02-24 04:04:26 +00:00
|
|
|
insta::assert_snapshot!(
|
|
|
|
&materialize_conflict_string(store, &path, &conflict),
|
|
|
|
@r###"
|
|
|
|
line 1
|
|
|
|
<<<<<<<
|
|
|
|
%%%%%%%
|
|
|
|
-line 2 base
|
|
|
|
+line 2 c.1
|
|
|
|
%%%%%%%
|
|
|
|
-line 2 base
|
|
|
|
+line 2 b.1
|
|
|
|
+line 2 b.2
|
|
|
|
+++++++
|
|
|
|
line 2 a.1
|
|
|
|
line 2 a.2
|
|
|
|
line 2 a.3
|
|
|
|
>>>>>>>
|
|
|
|
line 3
|
|
|
|
"###
|
|
|
|
);
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone()), Some(base_id.clone())],
|
|
|
|
vec![Some(c_id.clone()), Some(a_id.clone()), Some(b_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2023-02-24 04:04:26 +00:00
|
|
|
insta::assert_snapshot!(
|
|
|
|
&materialize_conflict_string(store, &path, &conflict),
|
|
|
|
@r###"
|
|
|
|
line 1
|
|
|
|
<<<<<<<
|
|
|
|
%%%%%%%
|
|
|
|
-line 2 base
|
|
|
|
+line 2 c.1
|
|
|
|
+++++++
|
|
|
|
line 2 a.1
|
|
|
|
line 2 a.2
|
|
|
|
line 2 a.3
|
|
|
|
%%%%%%%
|
|
|
|
-line 2 base
|
|
|
|
+line 2 b.1
|
|
|
|
+line 2 b.2
|
|
|
|
>>>>>>>
|
|
|
|
line 3
|
|
|
|
"###
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-02-18 06:02:43 +00:00
|
|
|
#[test]
|
|
|
|
fn test_materialize_parse_roundtrip() {
|
|
|
|
let test_repo = TestRepo::init(false);
|
|
|
|
let store = test_repo.repo.store();
|
|
|
|
|
|
|
|
let path = RepoPath::from_internal_string("file");
|
|
|
|
let base_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2
|
|
|
|
line 3
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
);
|
|
|
|
let left_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
"line 1 left
|
|
|
|
line 2 left
|
|
|
|
line 3
|
|
|
|
line 4
|
|
|
|
line 5 left
|
|
|
|
",
|
|
|
|
);
|
|
|
|
let right_id = testutils::write_file(
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
"line 1 right
|
|
|
|
line 2
|
|
|
|
line 3
|
|
|
|
line 4 right
|
|
|
|
line 5 right
|
|
|
|
",
|
|
|
|
);
|
|
|
|
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone())],
|
|
|
|
vec![Some(left_id.clone()), Some(right_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2023-08-11 20:52:32 +00:00
|
|
|
let materialized = materialize_conflict_string(store, &path, &conflict);
|
2023-02-18 06:02:43 +00:00
|
|
|
insta::assert_snapshot!(
|
2023-08-11 20:52:32 +00:00
|
|
|
materialized,
|
2023-02-18 06:02:43 +00:00
|
|
|
@r###"
|
|
|
|
<<<<<<<
|
conflicts: preserve order of adds in materialized conflict
We write conflict to the working copy by materializing them as
conflict markers in a file. When the file has been modified (or just
the mtime has changed), we parse the markers to reconstruct the
conflict. For example, let's say we see this conflict marker:
```
<<<<<<<
+++++++
b
%%%%%%%
-a
+c
>>>>>>>
```
Then we will create a hunk with ["a"] as removed and ["b", "c"] as
added.
Now, since commit b84be06c0822, when we materialize conflicts, we
minimize the diff part of the marker (the `%%%%%%%` part). The problem
is that that minimization may result in a different order of the
positive conflict terms. That's particularly bad because we do the
minimization per hunk, so we can end up reconstructing an input that
never existed.
This commit fixes the bug by only considering the next add and the one
after that, and emitting either only the first with `%%%%%%%`, or both
of them, with the first one in `++++++++` and the second one in
`%%%%%%%`.
Note that the recent fix to add context to modify/delete conflicts
means that when we parse modified such conflicts, we'll always
consider them resolved, since the expected adds/removes we pass will
not match what's actually in the file. That doesn't seem so bad, and
it's not obvious what the fix should be, so I'll leave that for later.
2023-02-18 06:29:30 +00:00
|
|
|
+++++++
|
|
|
|
line 1 left
|
|
|
|
line 2 left
|
2023-02-18 06:02:43 +00:00
|
|
|
%%%%%%%
|
|
|
|
-line 1
|
|
|
|
+line 1 right
|
|
|
|
line 2
|
|
|
|
>>>>>>>
|
|
|
|
line 3
|
|
|
|
<<<<<<<
|
|
|
|
%%%%%%%
|
|
|
|
line 4
|
|
|
|
-line 5
|
|
|
|
+line 5 left
|
|
|
|
+++++++
|
|
|
|
line 4 right
|
|
|
|
line 5 right
|
|
|
|
>>>>>>>
|
|
|
|
"###
|
|
|
|
);
|
|
|
|
|
conflicts: preserve order of adds in materialized conflict
We write conflict to the working copy by materializing them as
conflict markers in a file. When the file has been modified (or just
the mtime has changed), we parse the markers to reconstruct the
conflict. For example, let's say we see this conflict marker:
```
<<<<<<<
+++++++
b
%%%%%%%
-a
+c
>>>>>>>
```
Then we will create a hunk with ["a"] as removed and ["b", "c"] as
added.
Now, since commit b84be06c0822, when we materialize conflicts, we
minimize the diff part of the marker (the `%%%%%%%` part). The problem
is that that minimization may result in a different order of the
positive conflict terms. That's particularly bad because we do the
minimization per hunk, so we can end up reconstructing an input that
never existed.
This commit fixes the bug by only considering the next add and the one
after that, and emitting either only the first with `%%%%%%%`, or both
of them, with the first one in `++++++++` and the second one in
`%%%%%%%`.
Note that the recent fix to add context to modify/delete conflicts
means that when we parse modified such conflicts, we'll always
consider them resolved, since the expected adds/removes we pass will
not match what's actually in the file. That doesn't seem so bad, and
it's not obvious what the fix should be, so I'll leave that for later.
2023-02-18 06:29:30 +00:00
|
|
|
// The first add should always be from the left side
|
2023-02-18 06:02:43 +00:00
|
|
|
insta::assert_debug_snapshot!(
|
2023-08-12 16:39:08 +00:00
|
|
|
parse_conflict(materialized.as_bytes(), conflict.adds().len()),
|
2023-02-18 06:02:43 +00:00
|
|
|
@r###"
|
|
|
|
Some(
|
|
|
|
[
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [
|
|
|
|
"line 1\nline 2\n",
|
|
|
|
],
|
|
|
|
adds: [
|
|
|
|
"line 1 left\nline 2 left\n",
|
|
|
|
"line 1 right\nline 2\n",
|
|
|
|
],
|
|
|
|
},
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [],
|
|
|
|
adds: [
|
|
|
|
"line 3\n",
|
|
|
|
],
|
|
|
|
},
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [
|
|
|
|
"line 4\nline 5\n",
|
|
|
|
],
|
|
|
|
adds: [
|
|
|
|
"line 4\nline 5 left\n",
|
|
|
|
"line 4 right\nline 5 right\n",
|
|
|
|
],
|
|
|
|
},
|
2023-02-18 06:02:43 +00:00
|
|
|
],
|
|
|
|
)
|
|
|
|
"###);
|
|
|
|
}
|
|
|
|
|
2021-10-13 19:53:35 +00:00
|
|
|
#[test]
|
|
|
|
fn test_materialize_conflict_modify_delete() {
|
2022-05-21 18:20:51 +00:00
|
|
|
let test_repo = TestRepo::init(false);
|
2022-02-05 23:34:27 +00:00
|
|
|
let store = test_repo.repo.store();
|
2021-10-13 19:53:35 +00:00
|
|
|
|
|
|
|
let path = RepoPath::from_internal_string("file");
|
|
|
|
let base_id = testutils::write_file(
|
2021-11-21 07:46:54 +00:00
|
|
|
store,
|
2021-10-13 19:53:35 +00:00
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2
|
|
|
|
line 3
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
);
|
2023-02-17 17:54:26 +00:00
|
|
|
let modified_id = testutils::write_file(
|
2021-11-21 07:46:54 +00:00
|
|
|
store,
|
2021-10-13 19:53:35 +00:00
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2
|
2023-02-17 17:54:26 +00:00
|
|
|
modified
|
2021-10-13 19:53:35 +00:00
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
);
|
2023-02-17 17:54:26 +00:00
|
|
|
let deleted_id = testutils::write_file(
|
2021-11-21 07:46:54 +00:00
|
|
|
store,
|
2021-10-13 19:53:35 +00:00
|
|
|
&path,
|
|
|
|
"line 1
|
|
|
|
line 2
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
);
|
|
|
|
|
2023-02-17 17:54:26 +00:00
|
|
|
// left modifies a line, right deletes the same line.
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone())],
|
|
|
|
vec![Some(modified_id.clone()), Some(deleted_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2022-07-10 18:01:48 +00:00
|
|
|
insta::assert_snapshot!(&materialize_conflict_string(store, &path, &conflict), @r###"
|
|
|
|
line 1
|
|
|
|
line 2
|
|
|
|
<<<<<<<
|
|
|
|
+++++++
|
2023-02-17 17:54:26 +00:00
|
|
|
modified
|
conflicts: preserve order of adds in materialized conflict
We write conflict to the working copy by materializing them as
conflict markers in a file. When the file has been modified (or just
the mtime has changed), we parse the markers to reconstruct the
conflict. For example, let's say we see this conflict marker:
```
<<<<<<<
+++++++
b
%%%%%%%
-a
+c
>>>>>>>
```
Then we will create a hunk with ["a"] as removed and ["b", "c"] as
added.
Now, since commit b84be06c0822, when we materialize conflicts, we
minimize the diff part of the marker (the `%%%%%%%` part). The problem
is that that minimization may result in a different order of the
positive conflict terms. That's particularly bad because we do the
minimization per hunk, so we can end up reconstructing an input that
never existed.
This commit fixes the bug by only considering the next add and the one
after that, and emitting either only the first with `%%%%%%%`, or both
of them, with the first one in `++++++++` and the second one in
`%%%%%%%`.
Note that the recent fix to add context to modify/delete conflicts
means that when we parse modified such conflicts, we'll always
consider them resolved, since the expected adds/removes we pass will
not match what's actually in the file. That doesn't seem so bad, and
it's not obvious what the fix should be, so I'll leave that for later.
2023-02-18 06:29:30 +00:00
|
|
|
%%%%%%%
|
|
|
|
-line 3
|
2022-07-10 18:01:48 +00:00
|
|
|
>>>>>>>
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
"###
|
2021-10-13 19:53:35 +00:00
|
|
|
);
|
|
|
|
|
2023-02-17 17:54:26 +00:00
|
|
|
// right modifies a line, left deletes the same line.
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone())],
|
|
|
|
vec![Some(deleted_id.clone()), Some(modified_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2023-02-17 17:54:26 +00:00
|
|
|
insta::assert_snapshot!(&materialize_conflict_string(store, &path, &conflict), @r###"
|
2022-07-10 18:01:48 +00:00
|
|
|
line 1
|
|
|
|
line 2
|
|
|
|
<<<<<<<
|
2022-09-20 03:31:20 +00:00
|
|
|
%%%%%%%
|
2022-07-10 18:01:48 +00:00
|
|
|
-line 3
|
|
|
|
+++++++
|
2023-02-17 17:54:26 +00:00
|
|
|
modified
|
2022-07-10 18:01:48 +00:00
|
|
|
>>>>>>>
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
"###
|
2021-10-13 19:53:35 +00:00
|
|
|
);
|
2023-02-17 17:51:14 +00:00
|
|
|
|
|
|
|
// modify/delete conflict at the file level
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_id.clone())],
|
|
|
|
vec![Some(modified_id.clone()), None],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2023-02-17 17:51:14 +00:00
|
|
|
insta::assert_snapshot!(&materialize_conflict_string(store, &path, &conflict), @r###"
|
|
|
|
<<<<<<<
|
|
|
|
%%%%%%%
|
2023-02-17 18:05:47 +00:00
|
|
|
line 1
|
|
|
|
line 2
|
2023-02-17 17:51:14 +00:00
|
|
|
-line 3
|
|
|
|
+modified
|
2023-02-17 18:05:47 +00:00
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
+++++++
|
2023-02-17 17:51:14 +00:00
|
|
|
>>>>>>>
|
|
|
|
"###
|
|
|
|
);
|
2021-10-13 19:53:35 +00:00
|
|
|
}
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_conflict_resolved() {
|
|
|
|
assert_eq!(
|
|
|
|
parse_conflict(
|
|
|
|
b"line 1
|
|
|
|
line 2
|
|
|
|
line 3
|
|
|
|
line 4
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
2
|
|
|
|
),
|
|
|
|
None
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_conflict_simple() {
|
2023-02-18 23:01:25 +00:00
|
|
|
insta::assert_debug_snapshot!(
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
parse_conflict(
|
|
|
|
b"line 1
|
|
|
|
<<<<<<<
|
2022-09-20 03:31:20 +00:00
|
|
|
%%%%%%%
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
line 2
|
|
|
|
-line 3
|
|
|
|
+left
|
|
|
|
line 4
|
|
|
|
+++++++
|
|
|
|
right
|
|
|
|
>>>>>>>
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
2
|
|
|
|
),
|
2023-02-18 23:01:25 +00:00
|
|
|
@r###"
|
|
|
|
Some(
|
|
|
|
[
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [],
|
|
|
|
adds: [
|
|
|
|
"line 1\n",
|
|
|
|
],
|
|
|
|
},
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [
|
|
|
|
"line 2\nline 3\nline 4\n",
|
|
|
|
],
|
|
|
|
adds: [
|
|
|
|
"line 2\nleft\nline 4\n",
|
|
|
|
"right\n",
|
|
|
|
],
|
|
|
|
},
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [],
|
|
|
|
adds: [
|
|
|
|
"line 5\n",
|
|
|
|
],
|
|
|
|
},
|
2023-02-18 23:01:25 +00:00
|
|
|
],
|
|
|
|
)
|
|
|
|
"###
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_conflict_multi_way() {
|
2023-02-18 23:01:25 +00:00
|
|
|
insta::assert_debug_snapshot!(
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
parse_conflict(
|
|
|
|
b"line 1
|
|
|
|
<<<<<<<
|
2022-09-20 03:31:20 +00:00
|
|
|
%%%%%%%
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
line 2
|
|
|
|
-line 3
|
|
|
|
+left
|
|
|
|
line 4
|
|
|
|
+++++++
|
|
|
|
right
|
2022-09-20 03:31:20 +00:00
|
|
|
%%%%%%%
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
line 2
|
|
|
|
+forward
|
|
|
|
line 3
|
|
|
|
line 4
|
|
|
|
>>>>>>>
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
3
|
|
|
|
),
|
2023-02-18 23:01:25 +00:00
|
|
|
@r###"
|
|
|
|
Some(
|
|
|
|
[
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [],
|
|
|
|
adds: [
|
|
|
|
"line 1\n",
|
|
|
|
],
|
|
|
|
},
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [
|
|
|
|
"line 2\nline 3\nline 4\n",
|
|
|
|
"line 2\nline 3\nline 4\n",
|
|
|
|
],
|
|
|
|
adds: [
|
|
|
|
"line 2\nleft\nline 4\n",
|
|
|
|
"right\n",
|
|
|
|
"line 2\nforward\nline 3\nline 4\n",
|
|
|
|
],
|
|
|
|
},
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge {
|
2023-06-27 19:56:44 +00:00
|
|
|
removes: [],
|
|
|
|
adds: [
|
|
|
|
"line 5\n",
|
|
|
|
],
|
|
|
|
},
|
2023-02-18 23:01:25 +00:00
|
|
|
],
|
|
|
|
)
|
|
|
|
"###
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_conflict_different_wrong_arity() {
|
|
|
|
assert_eq!(
|
|
|
|
parse_conflict(
|
|
|
|
b"line 1
|
|
|
|
<<<<<<<
|
2022-09-20 03:31:20 +00:00
|
|
|
%%%%%%%
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
line 2
|
|
|
|
-line 3
|
|
|
|
+left
|
|
|
|
line 4
|
|
|
|
+++++++
|
|
|
|
right
|
|
|
|
>>>>>>>
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
3
|
|
|
|
),
|
|
|
|
None
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_conflict_malformed_marker() {
|
2022-09-20 03:31:20 +00:00
|
|
|
// The conflict marker is missing `%%%%%%%`
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
assert_eq!(
|
|
|
|
parse_conflict(
|
|
|
|
b"line 1
|
|
|
|
<<<<<<<
|
|
|
|
line 2
|
|
|
|
-line 3
|
|
|
|
+left
|
|
|
|
line 4
|
|
|
|
+++++++
|
|
|
|
right
|
|
|
|
>>>>>>>
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
2
|
|
|
|
),
|
|
|
|
None
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_conflict_malformed_diff() {
|
|
|
|
// The diff part is invalid (missing space before "line 4")
|
|
|
|
assert_eq!(
|
|
|
|
parse_conflict(
|
|
|
|
b"line 1
|
|
|
|
<<<<<<<
|
2022-09-20 03:31:20 +00:00
|
|
|
%%%%%%%
|
conflicts: add a function for parsing a materialized conflict
I initially made the working copy materialize conflicts in its
`check_out()` method. Then I changed it later (exactly a year ago, on
Halloween of 2020, actually) so that the working copy expected
conflicts to already have been materalized, which happens in
`MutableRepo::check_out`().
I think my reasoning then was that the file system cannot represent a
conflict. While it's true that the file system itself doesn't have
information to know whether a file represents a conflict, we can
record that ourselves. We already record whether a file is executable
or not and then preserve that if we're on a file system that isn't
able to record it. It's not that different to do the same for
conflicts if we're on a file system that doesn't understand conflicts
(i.e. all file systems).
The plan is to have the working copy remember whether a file
represents a conflict. When we check if it has changed, we parse the
file, including conflict markers, and recreate the conflict from
it. We should be able to do that losslessly (and we should adjust
formats to make it possible if we find cases where it's not).
Having the working copy preserve conflict states has several
advantages:
* Because conflicts are not materialized in the working copy, you can
rebase the conflicted commit and the working copy without causing
more conflicts (that's currently a UX bug I run into every now and
then).
* If you don't change anything in the working copy, it will be
unchanged compared to its parent, which means we'll automatically
abandon it if you update away from it.
* The user can choose to resolve only some of the conflicts in a file
and squash those in, and it'll work they way you'd hope.
* It should make it easier to implement support for external merge
tools (#18) without having them treat the working copy differently.
This patch prepares for that work by adding support for parsing
materialized conflicts.
2021-10-31 18:57:12 +00:00
|
|
|
line 2
|
|
|
|
-line 3
|
|
|
|
+left
|
|
|
|
line 4
|
|
|
|
+++++++
|
|
|
|
right
|
|
|
|
>>>>>>>
|
|
|
|
line 5
|
|
|
|
",
|
|
|
|
2
|
|
|
|
),
|
|
|
|
None
|
|
|
|
)
|
|
|
|
}
|
2021-10-29 03:55:36 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_conflict_from_content() {
|
2022-05-21 18:20:51 +00:00
|
|
|
let test_repo = TestRepo::init(false);
|
2022-02-05 23:34:27 +00:00
|
|
|
let store = test_repo.repo.store();
|
2021-10-29 03:55:36 +00:00
|
|
|
|
|
|
|
let path = RepoPath::from_internal_string("dir/file");
|
2021-11-21 07:46:54 +00:00
|
|
|
let base_file_id = testutils::write_file(store, &path, "line 1\nline 2\nline 3\n");
|
|
|
|
let left_file_id = testutils::write_file(store, &path, "left 1\nline 2\nleft 3\n");
|
|
|
|
let right_file_id = testutils::write_file(store, &path, "right 1\nline 2\nright 3\n");
|
2023-08-06 13:52:40 +00:00
|
|
|
let conflict = Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(base_file_id.clone())],
|
|
|
|
vec![Some(left_file_id.clone()), Some(right_file_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
);
|
2021-10-29 03:55:36 +00:00
|
|
|
|
|
|
|
// If the content is unchanged compared to the materialized value, we get the
|
|
|
|
// old conflict id back.
|
2023-08-11 20:52:32 +00:00
|
|
|
let materialized = materialize_conflict_string(store, &path, &conflict);
|
|
|
|
let result = update_from_content(&conflict, store, &path, materialized.as_bytes()).unwrap();
|
2023-08-11 21:48:10 +00:00
|
|
|
assert_eq!(result, conflict);
|
2021-10-29 03:55:36 +00:00
|
|
|
|
conflicts: fix bug when modifying modify/delete conflicts
Currently, if the user modifies a modify/delete conflict, we always
consider the result resolved. That happens because we materialize the
missing side of the conflict as an empty string but when we parse the
conflict, we expect only the number of sides in the input
conflict. For example, if the input is a regular modify/delete
conflict with one remove and one add, the materialized markers will
have one remove and two adds (one of them empty), but when we try to
parse it, we expect one remove and only one add. When we fail to parse
it, we consider it resolved.
This commit fixes the bug by using
`conflicts::Conflict<Option<TreeValue>>` and keeping track of which
sides were supposed to be empty. We could have fixed the bug without
switching to `conflicts::Conflict`, but we want to switch anyway, and
the fix happens naturally when switching.
2023-05-31 18:02:23 +00:00
|
|
|
// If the conflict is resolved, we get None back to indicate that.
|
2023-08-10 06:46:25 +00:00
|
|
|
let result =
|
|
|
|
update_from_content(&conflict, store, &path, b"resolved 1\nline 2\nresolved 3\n").unwrap();
|
2023-08-11 21:48:10 +00:00
|
|
|
let expected_file_id = testutils::write_file(store, &path, "resolved 1\nline 2\nresolved 3\n");
|
|
|
|
assert_eq!(result, Merge::normal(expected_file_id));
|
2021-10-29 03:55:36 +00:00
|
|
|
|
|
|
|
// If the conflict is partially resolved, we get a new conflict back.
|
2023-08-11 21:48:10 +00:00
|
|
|
let new_conflict = update_from_content(
|
2023-08-10 06:46:25 +00:00
|
|
|
&conflict,
|
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
b"resolved 1\nline 2\n<<<<<<<\n%%%%%%%\n-line 3\n+left 3\n+++++++\nright 3\n>>>>>>>\n",
|
|
|
|
)
|
|
|
|
.unwrap();
|
2023-06-10 21:47:32 +00:00
|
|
|
assert_ne!(new_conflict, conflict);
|
2021-10-29 03:55:36 +00:00
|
|
|
// Calculate expected new FileIds
|
2021-11-21 07:46:54 +00:00
|
|
|
let new_base_file_id = testutils::write_file(store, &path, "resolved 1\nline 2\nline 3\n");
|
|
|
|
let new_left_file_id = testutils::write_file(store, &path, "resolved 1\nline 2\nleft 3\n");
|
|
|
|
let new_right_file_id = testutils::write_file(store, &path, "resolved 1\nline 2\nright 3\n");
|
2021-10-29 03:55:36 +00:00
|
|
|
assert_eq!(
|
|
|
|
new_conflict,
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(new_base_file_id.clone())],
|
2023-05-31 20:55:17 +00:00
|
|
|
vec![
|
2023-08-11 05:06:38 +00:00
|
|
|
Some(new_left_file_id.clone()),
|
|
|
|
Some(new_right_file_id.clone())
|
2021-10-29 03:55:36 +00:00
|
|
|
]
|
2023-05-31 20:55:17 +00:00
|
|
|
)
|
|
|
|
);
|
2021-10-29 03:55:36 +00:00
|
|
|
}
|
2022-07-10 18:01:48 +00:00
|
|
|
|
conflicts: fix bug when modifying modify/delete conflicts
Currently, if the user modifies a modify/delete conflict, we always
consider the result resolved. That happens because we materialize the
missing side of the conflict as an empty string but when we parse the
conflict, we expect only the number of sides in the input
conflict. For example, if the input is a regular modify/delete
conflict with one remove and one add, the materialized markers will
have one remove and two adds (one of them empty), but when we try to
parse it, we expect one remove and only one add. When we fail to parse
it, we consider it resolved.
This commit fixes the bug by using
`conflicts::Conflict<Option<TreeValue>>` and keeping track of which
sides were supposed to be empty. We could have fixed the bug without
switching to `conflicts::Conflict`, but we want to switch anyway, and
the fix happens naturally when switching.
2023-05-31 18:02:23 +00:00
|
|
|
#[test]
|
|
|
|
fn test_update_conflict_from_content_modify_delete() {
|
|
|
|
let test_repo = TestRepo::init(false);
|
|
|
|
let store = test_repo.repo.store();
|
|
|
|
|
|
|
|
let path = RepoPath::from_internal_string("dir/file");
|
|
|
|
let before_file_id = testutils::write_file(store, &path, "line 1\nline 2 before\nline 3\n");
|
|
|
|
let after_file_id = testutils::write_file(store, &path, "line 1\nline 2 after\nline 3\n");
|
2023-08-11 05:06:38 +00:00
|
|
|
let conflict = Merge::new(vec![Some(before_file_id)], vec![Some(after_file_id), None]);
|
conflicts: fix bug when modifying modify/delete conflicts
Currently, if the user modifies a modify/delete conflict, we always
consider the result resolved. That happens because we materialize the
missing side of the conflict as an empty string but when we parse the
conflict, we expect only the number of sides in the input
conflict. For example, if the input is a regular modify/delete
conflict with one remove and one add, the materialized markers will
have one remove and two adds (one of them empty), but when we try to
parse it, we expect one remove and only one add. When we fail to parse
it, we consider it resolved.
This commit fixes the bug by using
`conflicts::Conflict<Option<TreeValue>>` and keeping track of which
sides were supposed to be empty. We could have fixed the bug without
switching to `conflicts::Conflict`, but we want to switch anyway, and
the fix happens naturally when switching.
2023-05-31 18:02:23 +00:00
|
|
|
|
|
|
|
// If the content is unchanged compared to the materialized value, we get the
|
|
|
|
// old conflict id back.
|
2023-08-11 20:52:32 +00:00
|
|
|
let materialized = materialize_conflict_string(store, &path, &conflict);
|
|
|
|
let result = update_from_content(&conflict, store, &path, materialized.as_bytes()).unwrap();
|
2023-08-11 21:48:10 +00:00
|
|
|
assert_eq!(result, conflict);
|
conflicts: fix bug when modifying modify/delete conflicts
Currently, if the user modifies a modify/delete conflict, we always
consider the result resolved. That happens because we materialize the
missing side of the conflict as an empty string but when we parse the
conflict, we expect only the number of sides in the input
conflict. For example, if the input is a regular modify/delete
conflict with one remove and one add, the materialized markers will
have one remove and two adds (one of them empty), but when we try to
parse it, we expect one remove and only one add. When we fail to parse
it, we consider it resolved.
This commit fixes the bug by using
`conflicts::Conflict<Option<TreeValue>>` and keeping track of which
sides were supposed to be empty. We could have fixed the bug without
switching to `conflicts::Conflict`, but we want to switch anyway, and
the fix happens naturally when switching.
2023-05-31 18:02:23 +00:00
|
|
|
|
|
|
|
// If the conflict is resolved, we get None back to indicate that.
|
2023-08-10 06:46:25 +00:00
|
|
|
let result = update_from_content(&conflict, store, &path, b"resolved\n").unwrap();
|
2023-08-11 21:48:10 +00:00
|
|
|
let expected_file_id = testutils::write_file(store, &path, "resolved\n");
|
|
|
|
assert_eq!(result, Merge::normal(expected_file_id));
|
conflicts: fix bug when modifying modify/delete conflicts
Currently, if the user modifies a modify/delete conflict, we always
consider the result resolved. That happens because we materialize the
missing side of the conflict as an empty string but when we parse the
conflict, we expect only the number of sides in the input
conflict. For example, if the input is a regular modify/delete
conflict with one remove and one add, the materialized markers will
have one remove and two adds (one of them empty), but when we try to
parse it, we expect one remove and only one add. When we fail to parse
it, we consider it resolved.
This commit fixes the bug by using
`conflicts::Conflict<Option<TreeValue>>` and keeping track of which
sides were supposed to be empty. We could have fixed the bug without
switching to `conflicts::Conflict`, but we want to switch anyway, and
the fix happens naturally when switching.
2023-05-31 18:02:23 +00:00
|
|
|
|
|
|
|
// If the conflict is modified, we get a new conflict back.
|
2023-08-11 21:48:10 +00:00
|
|
|
let new_conflict = update_from_content(&conflict,
|
conflicts: fix bug when modifying modify/delete conflicts
Currently, if the user modifies a modify/delete conflict, we always
consider the result resolved. That happens because we materialize the
missing side of the conflict as an empty string but when we parse the
conflict, we expect only the number of sides in the input
conflict. For example, if the input is a regular modify/delete
conflict with one remove and one add, the materialized markers will
have one remove and two adds (one of them empty), but when we try to
parse it, we expect one remove and only one add. When we fail to parse
it, we consider it resolved.
This commit fixes the bug by using
`conflicts::Conflict<Option<TreeValue>>` and keeping track of which
sides were supposed to be empty. We could have fixed the bug without
switching to `conflicts::Conflict`, but we want to switch anyway, and
the fix happens naturally when switching.
2023-05-31 18:02:23 +00:00
|
|
|
store,
|
|
|
|
&path,
|
|
|
|
b"<<<<<<<\n%%%%%%%\n line 1\n-line 2 before\n+line 2 modified after\n line 3\n+++++++\n>>>>>>>\n",
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
// Calculate expected new FileIds
|
|
|
|
let new_base_file_id = testutils::write_file(store, &path, "line 1\nline 2 before\nline 3\n");
|
|
|
|
let new_left_file_id =
|
|
|
|
testutils::write_file(store, &path, "line 1\nline 2 modified after\nline 3\n");
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
new_conflict,
|
2023-08-06 13:52:40 +00:00
|
|
|
Merge::new(
|
2023-08-11 05:06:38 +00:00
|
|
|
vec![Some(new_base_file_id.clone())],
|
|
|
|
vec![Some(new_left_file_id.clone()), None]
|
2023-05-31 20:55:17 +00:00
|
|
|
)
|
|
|
|
);
|
conflicts: fix bug when modifying modify/delete conflicts
Currently, if the user modifies a modify/delete conflict, we always
consider the result resolved. That happens because we materialize the
missing side of the conflict as an empty string but when we parse the
conflict, we expect only the number of sides in the input
conflict. For example, if the input is a regular modify/delete
conflict with one remove and one add, the materialized markers will
have one remove and two adds (one of them empty), but when we try to
parse it, we expect one remove and only one add. When we fail to parse
it, we consider it resolved.
This commit fixes the bug by using
`conflicts::Conflict<Option<TreeValue>>` and keeping track of which
sides were supposed to be empty. We could have fixed the bug without
switching to `conflicts::Conflict`, but we want to switch anyway, and
the fix happens naturally when switching.
2023-05-31 18:02:23 +00:00
|
|
|
}
|
|
|
|
|
2023-05-31 20:55:17 +00:00
|
|
|
fn materialize_conflict_string(
|
|
|
|
store: &Store,
|
|
|
|
path: &RepoPath,
|
2023-08-11 05:06:38 +00:00
|
|
|
conflict: &Merge<Option<FileId>>,
|
2023-05-31 20:55:17 +00:00
|
|
|
) -> String {
|
2022-07-10 18:01:48 +00:00
|
|
|
let mut result: Vec<u8> = vec![];
|
2023-08-11 05:06:38 +00:00
|
|
|
let contents = extract_as_single_hunk(conflict, store, path);
|
|
|
|
materialize_merge_result(&contents, &mut result).unwrap();
|
2022-07-10 18:01:48 +00:00
|
|
|
String::from_utf8(result).unwrap()
|
|
|
|
}
|