mirror of
https://github.com/martinvonz/jj.git
synced 2024-10-04 01:17:54 +03:00
Merge 58d9498b8e
into 2f9561a4b8
This commit is contained in:
commit
0f0ba2639f
@ -1072,7 +1072,7 @@ fn test_op_diff() {
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation f534dfc3151b 2001-02-03 04:05:16.000 +07:00 - 2001-02-03 04:05:16.000 +07:00 reconcile divergent operations
|
||||
To operation 8a21e297a587 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00 fetch from git remote(s) origin
|
||||
To operation 3fd1674a1e4b 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00 fetch from git remote(s) origin
|
||||
|
||||
Changed commits:
|
||||
○ Change qzxslznxxpoz
|
||||
@ -1084,8 +1084,8 @@ fn test_op_diff() {
|
||||
|
||||
Changed local bookmarks:
|
||||
bookmark-1:
|
||||
+ (added) slvtnnzx 4f856199 bookmark-1?? bookmark-1@origin | Commit 4
|
||||
+ (added) yuvsmzqk 3d9189bc bookmark-1?? | Commit 2
|
||||
+ (added) slvtnnzx 4f856199 bookmark-1?? bookmark-1@origin | Commit 4
|
||||
- (added) ulyvmwyz 1d843d1f Commit 1
|
||||
- (added) yuvsmzqk 3d9189bc bookmark-1?? | Commit 2
|
||||
|
||||
@ -1119,8 +1119,8 @@ fn test_op_diff() {
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation 8a21e297a587 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00 fetch from git remote(s) origin
|
||||
To operation ef314062f7f5 2001-02-03 04:05:22.000 +07:00 - 2001-02-03 04:05:22.000 +07:00 create bookmark bookmark-2 pointing to commit d487febd08e690ee775a4e0387e30d544307e409
|
||||
From operation 3fd1674a1e4b 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00 fetch from git remote(s) origin
|
||||
To operation 0f61bdf3f529 2001-02-03 04:05:22.000 +07:00 - 2001-02-03 04:05:22.000 +07:00 create bookmark bookmark-2 pointing to commit d487febd08e690ee775a4e0387e30d544307e409
|
||||
|
||||
Changed local bookmarks:
|
||||
bookmark-2:
|
||||
@ -1138,8 +1138,8 @@ fn test_op_diff() {
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation ef314062f7f5 2001-02-03 04:05:22.000 +07:00 - 2001-02-03 04:05:22.000 +07:00 create bookmark bookmark-2 pointing to commit d487febd08e690ee775a4e0387e30d544307e409
|
||||
To operation bc306c9bb67f 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00 track remote bookmark bookmark-2@origin
|
||||
From operation 0f61bdf3f529 2001-02-03 04:05:22.000 +07:00 - 2001-02-03 04:05:22.000 +07:00 create bookmark bookmark-2 pointing to commit d487febd08e690ee775a4e0387e30d544307e409
|
||||
To operation 2deacd662374 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00 track remote bookmark bookmark-2@origin
|
||||
|
||||
Changed remote bookmarks:
|
||||
bookmark-2@origin:
|
||||
@ -1159,8 +1159,8 @@ fn test_op_diff() {
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation ef314062f7f5 2001-02-03 04:05:22.000 +07:00 - 2001-02-03 04:05:22.000 +07:00 create bookmark bookmark-2 pointing to commit d487febd08e690ee775a4e0387e30d544307e409
|
||||
To operation bc306c9bb67f 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00 track remote bookmark bookmark-2@origin
|
||||
From operation 0f61bdf3f529 2001-02-03 04:05:22.000 +07:00 - 2001-02-03 04:05:22.000 +07:00 create bookmark bookmark-2 pointing to commit d487febd08e690ee775a4e0387e30d544307e409
|
||||
To operation 2deacd662374 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00 track remote bookmark bookmark-2@origin
|
||||
|
||||
Changed remote bookmarks:
|
||||
bookmark-2@origin:
|
||||
@ -1182,8 +1182,8 @@ fn test_op_diff() {
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation bc306c9bb67f 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00 track remote bookmark bookmark-2@origin
|
||||
To operation 1ae777a7acc8 2001-02-03 04:05:28.000 +07:00 - 2001-02-03 04:05:28.000 +07:00 new empty commit
|
||||
From operation 2deacd662374 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00 track remote bookmark bookmark-2@origin
|
||||
To operation b7b7b121ec95 2001-02-03 04:05:28.000 +07:00 - 2001-02-03 04:05:28.000 +07:00 new empty commit
|
||||
|
||||
Changed commits:
|
||||
○ Change wvuyspvkupzz
|
||||
@ -1202,14 +1202,14 @@ fn test_op_diff() {
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation 1ae777a7acc8 2001-02-03 04:05:28.000 +07:00 - 2001-02-03 04:05:28.000 +07:00 new empty commit
|
||||
To operation 5728693d7de3 2001-02-03 04:05:30.000 +07:00 - 2001-02-03 04:05:30.000 +07:00 point bookmark bookmark-1 to commit 358b82d6be53fa9b062325abb8bc820a8b34c68d
|
||||
From operation b7b7b121ec95 2001-02-03 04:05:28.000 +07:00 - 2001-02-03 04:05:28.000 +07:00 new empty commit
|
||||
To operation 159c56e25e28 2001-02-03 04:05:30.000 +07:00 - 2001-02-03 04:05:30.000 +07:00 point bookmark bookmark-1 to commit 358b82d6be53fa9b062325abb8bc820a8b34c68d
|
||||
|
||||
Changed local bookmarks:
|
||||
bookmark-1:
|
||||
+ wvuyspvk 358b82d6 bookmark-1* | (empty) new commit
|
||||
- (added) slvtnnzx 4f856199 bookmark-1@origin | Commit 4
|
||||
- (added) yuvsmzqk 3d9189bc Commit 2
|
||||
- (added) slvtnnzx 4f856199 bookmark-1@origin | Commit 4
|
||||
"#);
|
||||
|
||||
// Test deletion of local bookmark.
|
||||
@ -1221,8 +1221,8 @@ fn test_op_diff() {
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation 5728693d7de3 2001-02-03 04:05:30.000 +07:00 - 2001-02-03 04:05:30.000 +07:00 point bookmark bookmark-1 to commit 358b82d6be53fa9b062325abb8bc820a8b34c68d
|
||||
To operation 0f77d601f1cd 2001-02-03 04:05:32.000 +07:00 - 2001-02-03 04:05:32.000 +07:00 delete bookmark bookmark-2
|
||||
From operation 159c56e25e28 2001-02-03 04:05:30.000 +07:00 - 2001-02-03 04:05:30.000 +07:00 point bookmark bookmark-1 to commit 358b82d6be53fa9b062325abb8bc820a8b34c68d
|
||||
To operation e097b34430e2 2001-02-03 04:05:32.000 +07:00 - 2001-02-03 04:05:32.000 +07:00 delete bookmark bookmark-2
|
||||
|
||||
Changed local bookmarks:
|
||||
bookmark-2:
|
||||
@ -1244,8 +1244,8 @@ fn test_op_diff() {
|
||||
"#);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "diff"]);
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
From operation 0f77d601f1cd 2001-02-03 04:05:32.000 +07:00 - 2001-02-03 04:05:32.000 +07:00 delete bookmark bookmark-2
|
||||
To operation 65409e59d36a 2001-02-03 04:05:34.000 +07:00 - 2001-02-03 04:05:34.000 +07:00 push all tracked bookmarks to git remote origin
|
||||
From operation e097b34430e2 2001-02-03 04:05:32.000 +07:00 - 2001-02-03 04:05:32.000 +07:00 delete bookmark bookmark-2
|
||||
To operation ded46c10567e 2001-02-03 04:05:34.000 +07:00 - 2001-02-03 04:05:34.000 +07:00 push all tracked bookmarks to git remote origin
|
||||
|
||||
Changed commits:
|
||||
○ Change oupztwtkortx
|
||||
@ -1760,8 +1760,8 @@ fn test_op_show() {
|
||||
Abandoned 1 commits that are no longer reachable.
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
5eed0f5f5fbc test-username@host.example.com 2001-02-03 04:05:16.000 +07:00 - 2001-02-03 04:05:16.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
5201220404be test-username@host.example.com 2001-02-03 04:05:16.000 +07:00 - 2001-02-03 04:05:16.000 +07:00
|
||||
fetch from git remote(s) origin
|
||||
args: jj git fetch
|
||||
|
||||
@ -1775,8 +1775,8 @@ fn test_op_show() {
|
||||
|
||||
Changed local bookmarks:
|
||||
bookmark-1:
|
||||
+ (added) slvtnnzx 4f856199 bookmark-1?? bookmark-1@origin | Commit 4
|
||||
+ (added) yuvsmzqk 3d9189bc bookmark-1?? | Commit 2
|
||||
+ (added) slvtnnzx 4f856199 bookmark-1?? bookmark-1@origin | Commit 4
|
||||
- (added) ulyvmwyz 1d843d1f Commit 1
|
||||
- (added) yuvsmzqk 3d9189bc bookmark-1?? | Commit 2
|
||||
|
||||
@ -1790,7 +1790,7 @@ fn test_op_show() {
|
||||
bookmark-3@origin:
|
||||
+ untracked (absent)
|
||||
- untracked tqyxmszt hidden 3e785984 Commit 3
|
||||
"###);
|
||||
"#);
|
||||
|
||||
// Test creation of bookmark.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(
|
||||
@ -1809,8 +1809,8 @@ fn test_op_show() {
|
||||
Created 1 bookmarks pointing to qzxslznx d487febd bookmark-2 bookmark-2@origin | Commit 5
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
25b687bb01b6 test-username@host.example.com 2001-02-03 04:05:18.000 +07:00 - 2001-02-03 04:05:18.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
907c1f7dabb6 test-username@host.example.com 2001-02-03 04:05:18.000 +07:00 - 2001-02-03 04:05:18.000 +07:00
|
||||
create bookmark bookmark-2 pointing to commit d487febd08e690ee775a4e0387e30d544307e409
|
||||
args: jj bookmark create bookmark-2 -r bookmark-2@origin
|
||||
|
||||
@ -1818,7 +1818,7 @@ fn test_op_show() {
|
||||
bookmark-2:
|
||||
+ qzxslznx d487febd bookmark-2 bookmark-2@origin | Commit 5
|
||||
- (absent)
|
||||
"###);
|
||||
"#);
|
||||
|
||||
// Test tracking of a bookmark.
|
||||
let (stdout, stderr) =
|
||||
@ -1829,8 +1829,8 @@ fn test_op_show() {
|
||||
Started tracking 1 remote bookmarks.
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
48cf434c463d test-username@host.example.com 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
0dad32097131 test-username@host.example.com 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00
|
||||
track remote bookmark bookmark-2@origin
|
||||
args: jj bookmark track bookmark-2@origin
|
||||
|
||||
@ -1838,7 +1838,7 @@ fn test_op_show() {
|
||||
bookmark-2@origin:
|
||||
+ tracked qzxslznx d487febd bookmark-2 | Commit 5
|
||||
- untracked qzxslznx d487febd bookmark-2 | Commit 5
|
||||
"###);
|
||||
"#);
|
||||
|
||||
// Test creation of new commit.
|
||||
let (stdout, stderr) =
|
||||
@ -1850,8 +1850,8 @@ fn test_op_show() {
|
||||
Nothing changed.
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
48cf434c463d test-username@host.example.com 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
0dad32097131 test-username@host.example.com 2001-02-03 04:05:20.000 +07:00 - 2001-02-03 04:05:20.000 +07:00
|
||||
track remote bookmark bookmark-2@origin
|
||||
args: jj bookmark track bookmark-2@origin
|
||||
|
||||
@ -1859,7 +1859,7 @@ fn test_op_show() {
|
||||
bookmark-2@origin:
|
||||
+ tracked qzxslznx d487febd bookmark-2 | Commit 5
|
||||
- untracked qzxslznx d487febd bookmark-2 | Commit 5
|
||||
"###);
|
||||
"#);
|
||||
|
||||
// Test creation of new commit.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(
|
||||
@ -1874,8 +1874,8 @@ fn test_op_show() {
|
||||
Added 1 files, modified 0 files, removed 1 files
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
89aa6cca51fc test-username@host.example.com 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
67b538fbb233 test-username@host.example.com 2001-02-03 04:05:24.000 +07:00 - 2001-02-03 04:05:24.000 +07:00
|
||||
new empty commit
|
||||
args: jj new bookmark-1@origin -m 'new commit'
|
||||
|
||||
@ -1884,7 +1884,7 @@ fn test_op_show() {
|
||||
+ xznxytkn eb6c2b21 (empty) new commit
|
||||
○ Change sqpuoqvxutmz
|
||||
- sqpuoqvx hidden 9708515f (empty) (no description set)
|
||||
"###);
|
||||
"#);
|
||||
|
||||
// Test updating of local bookmark.
|
||||
let (stdout, stderr) =
|
||||
@ -1895,17 +1895,17 @@ fn test_op_show() {
|
||||
Moved 1 bookmarks to xznxytkn eb6c2b21 bookmark-1* | (empty) new commit
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
6d6bf9b35d8a test-username@host.example.com 2001-02-03 04:05:26.000 +07:00 - 2001-02-03 04:05:26.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
7c40ca18d764 test-username@host.example.com 2001-02-03 04:05:26.000 +07:00 - 2001-02-03 04:05:26.000 +07:00
|
||||
point bookmark bookmark-1 to commit eb6c2b21ec20a33ab6a1c44bc86c59d84ffd93ac
|
||||
args: jj bookmark set bookmark-1 -r @
|
||||
|
||||
Changed local bookmarks:
|
||||
bookmark-1:
|
||||
+ xznxytkn eb6c2b21 bookmark-1* | (empty) new commit
|
||||
- (added) slvtnnzx 4f856199 bookmark-1@origin | Commit 4
|
||||
- (added) yuvsmzqk 3d9189bc Commit 2
|
||||
"###);
|
||||
- (added) slvtnnzx 4f856199 bookmark-1@origin | Commit 4
|
||||
"#);
|
||||
|
||||
// Test deletion of local bookmark.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["bookmark", "delete", "bookmark-2"]);
|
||||
@ -1915,8 +1915,8 @@ fn test_op_show() {
|
||||
Deleted 1 bookmarks.
|
||||
"###);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
a50c95a6b180 test-username@host.example.com 2001-02-03 04:05:28.000 +07:00 - 2001-02-03 04:05:28.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
0a7142c0c732 test-username@host.example.com 2001-02-03 04:05:28.000 +07:00 - 2001-02-03 04:05:28.000 +07:00
|
||||
delete bookmark bookmark-2
|
||||
args: jj bookmark delete bookmark-2
|
||||
|
||||
@ -1924,7 +1924,7 @@ fn test_op_show() {
|
||||
bookmark-2:
|
||||
+ (absent)
|
||||
- qzxslznx d487febd bookmark-2@origin | Commit 5
|
||||
"###);
|
||||
"#);
|
||||
|
||||
// Test pushing to Git remote.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["git", "push", "--tracked"]);
|
||||
@ -1939,8 +1939,8 @@ fn test_op_show() {
|
||||
Parent commit : xznxytkn eb6c2b21 bookmark-1 | (empty) new commit
|
||||
"#);
|
||||
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "show"]);
|
||||
insta::assert_snapshot!(&stdout, @r###"
|
||||
5d994bb7d230 test-username@host.example.com 2001-02-03 04:05:30.000 +07:00 - 2001-02-03 04:05:30.000 +07:00
|
||||
insta::assert_snapshot!(&stdout, @r#"
|
||||
91e63226cfa5 test-username@host.example.com 2001-02-03 04:05:30.000 +07:00 - 2001-02-03 04:05:30.000 +07:00
|
||||
push all tracked bookmarks to git remote origin
|
||||
args: jj git push --tracked
|
||||
|
||||
@ -1955,7 +1955,7 @@ fn test_op_show() {
|
||||
bookmark-2@origin:
|
||||
+ untracked (absent)
|
||||
- tracked qzxslznx d487febd Commit 5
|
||||
"###);
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
827
lib/src/merge.rs
827
lib/src/merge.rs
@ -210,6 +210,11 @@ impl<T> Merge<T> {
|
||||
self.values.get(index * 2 + 1)
|
||||
}
|
||||
|
||||
/// Returns the `index`-th removed value mutably
|
||||
pub fn get_remove_mut(&mut self, index: usize) -> Option<&mut T> {
|
||||
self.values.get_mut(index * 2 + 1)
|
||||
}
|
||||
|
||||
/// Returns the `index`-th added value, which is considered belonging to the
|
||||
/// `index-1`-th diff pair. The zeroth add is a diff from the non-existent
|
||||
/// state.
|
||||
@ -217,6 +222,11 @@ impl<T> Merge<T> {
|
||||
self.values.get(index * 2)
|
||||
}
|
||||
|
||||
/// Returns the `index`-th added value mutably
|
||||
pub fn get_add_mut(&mut self, index: usize) -> Option<&mut T> {
|
||||
self.values.get_mut(index * 2)
|
||||
}
|
||||
|
||||
/// Removes the specified "removed"/"added" values. The removed slots are
|
||||
/// replaced by the last "removed"/"added" values.
|
||||
pub fn swap_remove(&mut self, remove_index: usize, add_index: usize) -> (T, T) {
|
||||
@ -256,53 +266,16 @@ impl<T> Merge<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a vector mapping of a value's index in the simplified merge to
|
||||
/// its original index in the unsimplified merge.
|
||||
///
|
||||
/// The merge is simplified by removing identical values in add and remove
|
||||
/// values.
|
||||
fn get_simplified_mapping(&self) -> Vec<usize>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
let unsimplified_len = self.values.len();
|
||||
let mut simplified_to_original_indices = (0..unsimplified_len).collect_vec();
|
||||
|
||||
let mut add_index = 0;
|
||||
while add_index < simplified_to_original_indices.len() {
|
||||
let add = &self.values[simplified_to_original_indices[add_index]];
|
||||
let mut remove_indices = simplified_to_original_indices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(1)
|
||||
.step_by(2);
|
||||
if let Some((remove_index, _)) = remove_indices
|
||||
.find(|&(_, original_remove_index)| &self.values[*original_remove_index] == add)
|
||||
{
|
||||
// Align the current "add" value to the `remove_index/2`-th diff, then
|
||||
// delete the diff pair.
|
||||
simplified_to_original_indices.swap(remove_index + 1, add_index);
|
||||
simplified_to_original_indices.drain(remove_index..remove_index + 2);
|
||||
} else {
|
||||
add_index += 2;
|
||||
}
|
||||
}
|
||||
|
||||
simplified_to_original_indices
|
||||
}
|
||||
|
||||
/// Simplify the merge by joining diffs like A->B and B->C into A->C.
|
||||
/// Also drops trivial diffs like A->A.
|
||||
pub fn simplify(mut self) -> Self
|
||||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
let mapping = self.get_simplified_mapping();
|
||||
// Reorder values based on their new indices in the simplified merge.
|
||||
self.values = mapping
|
||||
.iter()
|
||||
.map(|index| self.values[*index].clone())
|
||||
.collect();
|
||||
if self.values.len() == 1 {
|
||||
return self;
|
||||
}
|
||||
self.values = simplify_internal(self.values.to_vec()).into();
|
||||
self
|
||||
}
|
||||
|
||||
@ -311,7 +284,7 @@ impl<T> Merge<T> {
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
let mapping = self.get_simplified_mapping();
|
||||
let mapping = get_simplified_mapping(&self.values);
|
||||
assert_eq!(mapping.len(), simplified.values.len());
|
||||
for (index, value) in mapping.into_iter().zip(simplified.values.into_iter()) {
|
||||
self.values[index] = value;
|
||||
@ -657,6 +630,73 @@ fn borrow_tree_value<T: Borrow<TreeValue> + ?Sized>(term: Option<&T>) -> Option<
|
||||
term.map(|value| value.borrow())
|
||||
}
|
||||
|
||||
/// Returns a vector mapping of a value's index in the simplified merge to
|
||||
/// its original index in the unsimplified merge.
|
||||
///
|
||||
/// The merge is simplified by removing identical values in add and remove
|
||||
/// values.
|
||||
fn get_simplified_mapping<T>(values: &[T]) -> Vec<usize>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
let unsimplified_len = values.len();
|
||||
let mut simplified_to_original_indices = (0..unsimplified_len).collect_vec();
|
||||
|
||||
let mut add_index = 0;
|
||||
while add_index < simplified_to_original_indices.len() {
|
||||
let add = &values[simplified_to_original_indices[add_index]];
|
||||
let mut remove_indices = simplified_to_original_indices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(1)
|
||||
.step_by(2);
|
||||
if let Some((remove_index, _)) = remove_indices
|
||||
.find(|&(_, original_remove_index)| &values[*original_remove_index] == add)
|
||||
{
|
||||
// Align the current "add" value to the `remove_index/2`-th diff, then
|
||||
// delete the diff pair.
|
||||
simplified_to_original_indices.swap(remove_index - 1, add_index);
|
||||
simplified_to_original_indices.drain(remove_index - 1..remove_index + 1);
|
||||
} else {
|
||||
add_index += 2;
|
||||
}
|
||||
}
|
||||
|
||||
simplified_to_original_indices
|
||||
}
|
||||
|
||||
fn simplify_internal<T>(values: Vec<T>) -> Vec<T>
|
||||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
let mapping = get_simplified_mapping(&values);
|
||||
// Reorder values based on their new indices in the simplified merge.
|
||||
mapping.iter().map(|index| values[*index].clone()).collect()
|
||||
}
|
||||
|
||||
fn get_optimize_for_distance_swaps<T>(
|
||||
values: &[T],
|
||||
distance: impl Fn(&T, &T) -> usize,
|
||||
) -> Vec<(usize, usize)> {
|
||||
let mut new_removes_order: Vec<usize> = (0..values.len() / 2).map(|x| 2 * x + 1).collect_vec();
|
||||
let mut swaps: Vec<(usize, usize)> = Vec::with_capacity(values.len() / 2);
|
||||
for pair_index in 0..values.len() / 2 {
|
||||
let best_remove_index = (pair_index..values.len() / 2)
|
||||
.min_by_key(|remove_index| {
|
||||
distance(
|
||||
&values[2 * pair_index],
|
||||
&values[new_removes_order[*remove_index]],
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
if pair_index != best_remove_index {
|
||||
new_removes_order.swap(pair_index, best_remove_index);
|
||||
swaps.push((pair_index * 2 + 1, best_remove_index * 2 + 1));
|
||||
}
|
||||
}
|
||||
swaps
|
||||
}
|
||||
|
||||
fn describe_conflict_term(value: &TreeValue) -> String {
|
||||
match value {
|
||||
TreeValue::File {
|
||||
@ -686,6 +726,300 @@ fn describe_conflict_term(value: &TreeValue) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(ilyagr): DiffOfMerges might not need to be public, might need renaming
|
||||
|
||||
/// A sequence of diffs forming a "conflicted diff"
|
||||
///
|
||||
/// Conceptually, this is very similar to a merge, except it has the same number
|
||||
/// of positive terms (corresponding to "adds" in a merge) and negative terms
|
||||
/// (corresponding to "removes" in a merge).
|
||||
///
|
||||
/// This can represent a diff of two merges, with corresponding terms cancelling
|
||||
/// out. The adds of the negative (left-hand side) merge will then become a
|
||||
/// negative term of the DiffOfMerges. However, in this case, it is not
|
||||
/// remembered which term comes from which merge. By itself, a sequence of diffs
|
||||
/// resulting from a diff of two merges cannot tell the difference between the
|
||||
/// a) right-hand merge adding a diff (X-Y) to the left-hand side, or b) the
|
||||
/// right-hand merge subtracting the opposite diff (Y-X) that was present on the
|
||||
/// left-hand side.
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub struct DiffOfMerges<T> {
|
||||
/// Alternates between positive and negative terms, starting with positive.
|
||||
values: Vec<T>,
|
||||
}
|
||||
impl<T> DiffOfMerges<T> {
|
||||
/// Creates a `DiffOfMerges` from the given values, in which positive and
|
||||
/// negative terms alternate.
|
||||
pub fn from_vec(values: impl Into<Vec<T>>) -> Self {
|
||||
let values = values.into();
|
||||
assert!(
|
||||
values.len() & 1 == 0,
|
||||
"must have equal numbers of adds and removes"
|
||||
);
|
||||
DiffOfMerges { values }
|
||||
}
|
||||
|
||||
/// Creates a new merge object from the given removes and adds.
|
||||
// TODO: Rename to `from_negatives_positives` (?)
|
||||
pub fn from_removes_adds(
|
||||
removes: impl IntoIterator<Item = T>,
|
||||
adds: impl IntoIterator<Item = T>,
|
||||
) -> Self {
|
||||
let removes = removes.into_iter();
|
||||
let adds = adds.into_iter();
|
||||
let mut values = Vec::with_capacity(removes.size_hint().0 * 2);
|
||||
for diff in removes.zip_longest(adds) {
|
||||
let (remove, add) = diff
|
||||
.both()
|
||||
.expect("must have the same number of adds and removes");
|
||||
values.extend([remove, add]);
|
||||
}
|
||||
DiffOfMerges { values }
|
||||
}
|
||||
|
||||
/// Diff of the `left` conflict ("from" side) and the `right` conflict
|
||||
/// ("to") side
|
||||
pub fn diff_of(left: Merge<T>, right: Merge<T>) -> DiffOfMerges<T> {
|
||||
// The last element of right's vector is an add and should stay an add.
|
||||
// The first element of left's vector should become a remove.
|
||||
let mut result_vec = right.values.into_vec();
|
||||
result_vec.append(&mut left.values.into_vec());
|
||||
DiffOfMerges::from_vec(result_vec)
|
||||
}
|
||||
|
||||
/// Simplify the diff by removing equal positive and negative terms.
|
||||
///
|
||||
/// The positive terms are allowed to be reordered freely, as are the
|
||||
/// negative terms. The pairing of them into diffs is **not** preserved.
|
||||
pub fn simplify(mut self) -> Self
|
||||
where
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
self.values = simplify_internal(self.values);
|
||||
self
|
||||
}
|
||||
|
||||
/// Pairs of diffs presented as (positive term, negative term)
|
||||
///
|
||||
/// Note that operations such as simplifications do *not* preserve these
|
||||
/// pairs.
|
||||
pub fn pairs(&self) -> impl ExactSizeIterator<Item = (&T, &T)> {
|
||||
zip(
|
||||
self.values.iter().step_by(2),
|
||||
self.values.iter().skip(1).step_by(2),
|
||||
)
|
||||
}
|
||||
|
||||
/// Rearranges the removes so that every pair of add and remove has as small
|
||||
/// a distance as possible.
|
||||
///
|
||||
/// TODO: Currently, this uses inefficient greedy algorithm that may not
|
||||
/// give optimal results
|
||||
///
|
||||
/// TODO: Consider generalizing this so that it's also used to optimize
|
||||
/// conflict presentation when materializing conflicts.
|
||||
pub fn rearranged_to_optimize_for_distance(self, distance: impl Fn(&T, &T) -> usize) -> Self {
|
||||
let Self { mut values } = self;
|
||||
let swaps = get_optimize_for_distance_swaps(&values, distance);
|
||||
for (i, j) in swaps {
|
||||
values.swap(i, j);
|
||||
}
|
||||
Self::from_vec(values)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given two conflicts, compute the corresponding set of
|
||||
/// [`DiffExplanationAtom<T>`].
|
||||
///
|
||||
/// The diffs in the set are picked to minimize `distance`.
|
||||
///
|
||||
/// This returns a Vec, but the order of the resulting set is not significant.
|
||||
//
|
||||
// TODO(ilyagr): There is a question of whether we want to bias the diffs
|
||||
// towards the diffs that match the way the conflicts are presented to the user,
|
||||
// or to bias to the diffs that come from the rebased commits after a rebase.
|
||||
// Note that this can have implication on how `blame` works. I currently think
|
||||
// that we should try to improve the optimization algorithm first and see how
|
||||
// far that gets us.
|
||||
pub fn explain_diff_of_merges<T: PartialEq + Clone>(
|
||||
left: Merge<T>,
|
||||
right: Merge<T>,
|
||||
distance: impl Fn(&T, &T) -> usize,
|
||||
) -> Vec<DiffExplanationAtom<T>> {
|
||||
let left = left.simplify();
|
||||
let right = right.simplify();
|
||||
let optimized_diff_of_merges = DiffOfMerges::diff_of(left.clone(), right.clone())
|
||||
.simplify()
|
||||
.rearranged_to_optimize_for_distance(distance);
|
||||
let mut left_seen = left.map(|_| false);
|
||||
let mut right_seen = right.map(|_| false);
|
||||
|
||||
let mut result = optimized_diff_of_merges
|
||||
.pairs()
|
||||
.map(|(add, remove)| {
|
||||
// The order of `if`-s does not matter since the diff_of_merges is simplified as
|
||||
// is each conflict, so the "add" could not come from both conflicts.
|
||||
let add_comes_from_right = if let Some((ix, _)) = right
|
||||
.adds()
|
||||
.enumerate()
|
||||
.find(|(ix, elt)| !right_seen.get_add(*ix).unwrap() && *elt == add)
|
||||
{
|
||||
*right_seen.get_add_mut(ix).unwrap() = true;
|
||||
true
|
||||
} else if let Some((ix, _)) = left
|
||||
.removes()
|
||||
.enumerate()
|
||||
.find(|(ix, elt)| !left_seen.get_remove(*ix).unwrap() && *elt == add)
|
||||
{
|
||||
*left_seen.get_remove_mut(ix).unwrap() = true;
|
||||
false
|
||||
} else {
|
||||
panic!(
|
||||
"adds of (right - left) should be either adds on the right or removes on the \
|
||||
left."
|
||||
)
|
||||
};
|
||||
|
||||
let remove_comes_from_right = if let Some((ix, _)) = right
|
||||
.removes()
|
||||
.enumerate()
|
||||
.find(|(ix, elt)| !right_seen.get_remove(*ix).unwrap() && *elt == remove)
|
||||
{
|
||||
*right_seen.get_remove_mut(ix).unwrap() = true;
|
||||
true
|
||||
} else if let Some((ix, _)) = left
|
||||
.adds()
|
||||
.enumerate()
|
||||
.find(|(ix, elt)| !left_seen.get_add(*ix).unwrap() && *elt == remove)
|
||||
{
|
||||
*left_seen.get_add_mut(ix).unwrap() = true;
|
||||
false
|
||||
} else {
|
||||
panic!(
|
||||
"removes of (right - left) should be either removes on the right or adds on \
|
||||
the left."
|
||||
)
|
||||
};
|
||||
|
||||
// TODO(ilyagr): Consider refactoring this to have fewer unnecessary clones.
|
||||
match (add_comes_from_right, remove_comes_from_right) {
|
||||
(true, true) => DiffExplanationAtom::AddedConflictDiff {
|
||||
conflict_add: add.clone(),
|
||||
conflict_remove: remove.clone(),
|
||||
},
|
||||
(false, false) => DiffExplanationAtom::RemovedConflictDiff {
|
||||
/* Instead of adding an upside-down conflict, consider this a removal of the
|
||||
* opposite conflict */
|
||||
conflict_add: remove.clone(),
|
||||
conflict_remove: add.clone(),
|
||||
},
|
||||
(true, false) => DiffExplanationAtom::ChangedConflictAdd {
|
||||
left_version: remove.clone(),
|
||||
right_version: add.clone(),
|
||||
},
|
||||
(false, true) => DiffExplanationAtom::ChangedConflictRemove {
|
||||
left_version: remove.clone(),
|
||||
right_version: add.clone(),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
// Since the conflicts were simplified, and any sides we didn't yet see must
|
||||
// have cancelled out in the diff,they are present on both left and right.
|
||||
// So, we can forget about the left side.
|
||||
|
||||
// TODO(ilyagr): We might be able to have more structure, e.g. have an
|
||||
// `UnchangedConflictDiff` category if there is both an unchanged add and an
|
||||
// unchanged remove *and* they are reasonably close. This should be considered
|
||||
// separately for showing to the user (where it might look confusingly similar
|
||||
// to other diffs that mean something else) and for blame/absorb.
|
||||
result.extend(
|
||||
zip(right.adds(), right_seen.adds())
|
||||
.filter(|&(_elt, seen)| (!seen))
|
||||
.map(|(elt, _seen)| DiffExplanationAtom::UnchangedConflictAdd(elt.clone())),
|
||||
);
|
||||
result.extend(
|
||||
zip(right.removes(), right_seen.removes())
|
||||
.filter(|&(_elt, seen)| (!seen))
|
||||
.map(|(elt, _seen)| DiffExplanationAtom::UnchangedConflictRemove(elt.clone())),
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
/// A statement about a difference of two conflicts of type `Merge<T>`
|
||||
///
|
||||
/// A (conceptually unordered) set of `DiffExplanationAtom<T>` describes a
|
||||
/// conflict `left: Merge<T>` and a sequence of operations to turn it into a
|
||||
/// different conflict `right: Merge<T>`. This is designed so that this
|
||||
/// information can be presented to the user.
|
||||
///
|
||||
/// This description is far from unique. We usually pick one by trying to
|
||||
/// minimize the complexity of the diffs in those operations that involve diffs.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum DiffExplanationAtom<T> {
|
||||
/// Adds one more pair of an add + a remove to the conflict, making it more
|
||||
/// complicated (more sides than before)
|
||||
AddedConflictDiff {
|
||||
/// Add of the added diff.
|
||||
///
|
||||
/// This is an "add" of the right conflict that is not an "add" of the
|
||||
/// left conflict.
|
||||
conflict_add: T,
|
||||
/// Remove of the added diff
|
||||
///
|
||||
/// This is a "remove" of the right conflict that is not a "remove" of
|
||||
/// the left conflict.
|
||||
conflict_remove: T,
|
||||
},
|
||||
/// Removes one of the add + remove pairs in the conflict, making it less
|
||||
/// complicated (fewer sides than before)
|
||||
///
|
||||
/// In terms of conflict theory, this is equivalent to adding the opposite
|
||||
/// diff and then simplifying the conflict. However, for the user, the
|
||||
/// difference between these presentations is significant.
|
||||
RemovedConflictDiff {
|
||||
/// Add of the removed diff
|
||||
///
|
||||
/// This is an "add" of the left conflict that is not an "add" of the
|
||||
/// right conflict.
|
||||
conflict_add: T,
|
||||
/// Remove of the removed diff
|
||||
///
|
||||
/// This is a "remove" of the left conflict that is not a "remove" of
|
||||
/// the right conflict.
|
||||
conflict_remove: T,
|
||||
},
|
||||
/// Modifies one of the adds of the conflict
|
||||
///
|
||||
/// This does not change the number of sides in the conflict.
|
||||
ChangedConflictAdd {
|
||||
/// Add of the left conflict
|
||||
left_version: T,
|
||||
/// Add of the right conflict
|
||||
right_version: T,
|
||||
},
|
||||
/// Modifies one of the removes of the conflict
|
||||
///
|
||||
/// This does not change the number of sides in the conflict.
|
||||
// TODO(ilyagr): While this operation is very natural from the perspective of conflict
|
||||
// theory, I find it hard to come up with an example where it is an
|
||||
// intuitive result of some operation. If it's too unintuitive to users, we could try to
|
||||
// replace it with a composition of "removed diff" and "added diff".
|
||||
ChangedConflictRemove {
|
||||
/// Remove of the left conflict
|
||||
left_version: T,
|
||||
/// Remove of the right conflict
|
||||
right_version: T,
|
||||
},
|
||||
/// Declares that both the left and the right conflict contain this value as
|
||||
/// an "add"
|
||||
UnchangedConflictAdd(T),
|
||||
/// Declares that both the left and the right conflict contain this value as
|
||||
/// a "remove"
|
||||
UnchangedConflictRemove(T),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -694,6 +1028,10 @@ mod tests {
|
||||
Merge::from_removes_adds(removes.to_vec(), adds.to_vec())
|
||||
}
|
||||
|
||||
fn d<T: Clone>(removes: &[T], adds: &[T]) -> DiffOfMerges<T> {
|
||||
DiffOfMerges::from_removes_adds(removes.to_vec(), adds.to_vec())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trivial_merge() {
|
||||
assert_eq!(trivial_merge(&[], &[0]), Some(&0));
|
||||
@ -813,182 +1151,104 @@ mod tests {
|
||||
#[test]
|
||||
fn test_get_simplified_mapping() {
|
||||
// 1-way merge
|
||||
assert_eq!(c(&[], &[0]).get_simplified_mapping(), vec![0]);
|
||||
assert_eq!(get_simplified_mapping(&[0]), vec![0]);
|
||||
// 3-way merge
|
||||
assert_eq!(c(&[0], &[0, 0]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(c(&[0], &[0, 1]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(c(&[0], &[1, 0]).get_simplified_mapping(), vec![0]);
|
||||
assert_eq!(c(&[0], &[1, 1]).get_simplified_mapping(), vec![0, 1, 2]);
|
||||
assert_eq!(c(&[0], &[1, 2]).get_simplified_mapping(), vec![0, 1, 2]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 0]), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 1]), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 0]), vec![0]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 1]), vec![0, 1, 2]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 2]), vec![0, 1, 2]);
|
||||
// 5-way merge
|
||||
assert_eq!(c(&[0, 0], &[0, 0, 0]).get_simplified_mapping(), vec![4]);
|
||||
assert_eq!(c(&[0, 0], &[0, 0, 1]).get_simplified_mapping(), vec![4]);
|
||||
assert_eq!(c(&[0, 0], &[0, 1, 0]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 0, 0, 0]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 0, 0, 1]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 1, 0, 0]), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 1, 0, 1]), vec![2, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 1, 0, 2]), vec![2, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 0, 0, 0]), vec![0]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 0, 0, 1]), vec![0, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 0, 0, 2]), vec![0, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 1, 0, 0]), vec![2, 3, 0],);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[0, 1, 1]).get_simplified_mapping(),
|
||||
vec![2, 3, 4]
|
||||
get_simplified_mapping(&[1, 0, 1, 0, 1]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[0, 1, 2]).get_simplified_mapping(),
|
||||
vec![2, 3, 4]
|
||||
get_simplified_mapping(&[1, 0, 1, 0, 2]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(c(&[0, 0], &[1, 0, 0]).get_simplified_mapping(), vec![0]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 2, 0, 0]), vec![2, 3, 0],);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 0, 1]).get_simplified_mapping(),
|
||||
vec![0, 3, 4]
|
||||
get_simplified_mapping(&[1, 0, 2, 0, 1]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 0, 2]).get_simplified_mapping(),
|
||||
vec![0, 3, 4]
|
||||
get_simplified_mapping(&[1, 0, 2, 0, 2]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 1, 0]).get_simplified_mapping(),
|
||||
vec![0, 3, 2]
|
||||
get_simplified_mapping(&[1, 0, 2, 0, 3]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 0, 1, 0]), vec![2, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 0, 1, 1]), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 0, 1, 2]), vec![2, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 1, 1, 0]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 1, 1, 1]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 1, 1, 2]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 2, 1, 0]), vec![2, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 2, 1, 1]), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 2, 1, 2]), vec![2, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[0, 0, 2, 1, 3]), vec![2, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 0, 1, 0]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 0, 1, 1]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 0, 1, 2]), vec![4]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 0]), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 1]), vec![2, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 1, 1, 2]), vec![2, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 0]), vec![2]);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 1]), vec![2, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 2]), vec![2, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[1, 0, 2, 1, 3]), vec![2, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 0, 1, 0]), vec![0, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 0, 1, 1]), vec![0]);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 0, 1, 2]), vec![0, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 0, 1, 3]), vec![0, 3, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 1, 1, 0]), vec![0]);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 1, 1, 1]), vec![0, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 1, 1, 2]), vec![0, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 1, 1, 3]), vec![0, 1, 4],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 2, 1, 0]), vec![2, 3, 0],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 2, 1, 1]), vec![0, 1, 2],);
|
||||
assert_eq!(
|
||||
get_simplified_mapping(&[2, 0, 2, 1, 2]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 1, 1]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
get_simplified_mapping(&[2, 0, 2, 1, 3]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 3, 1, 0]), vec![2, 3, 0],);
|
||||
assert_eq!(get_simplified_mapping(&[2, 0, 3, 1, 1]), vec![0, 1, 2],);
|
||||
assert_eq!(
|
||||
get_simplified_mapping(&[2, 0, 3, 1, 2]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 1, 2]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
get_simplified_mapping(&[2, 0, 3, 1, 3]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 2, 0]).get_simplified_mapping(),
|
||||
vec![0, 3, 2]
|
||||
get_simplified_mapping(&[2, 0, 3, 1, 4]),
|
||||
vec![0, 1, 2, 3, 4],
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 2, 1]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 2, 2]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 0], &[1, 2, 3]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[0, 0, 0]).get_simplified_mapping(),
|
||||
vec![2, 3, 4]
|
||||
);
|
||||
assert_eq!(c(&[0, 1], &[0, 0, 1]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[0, 0, 2]).get_simplified_mapping(),
|
||||
vec![2, 3, 4]
|
||||
);
|
||||
assert_eq!(c(&[0, 1], &[0, 1, 0]).get_simplified_mapping(), vec![4]);
|
||||
assert_eq!(c(&[0, 1], &[0, 1, 1]).get_simplified_mapping(), vec![4]);
|
||||
assert_eq!(c(&[0, 1], &[0, 1, 2]).get_simplified_mapping(), vec![4]);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[0, 2, 0]).get_simplified_mapping(),
|
||||
vec![2, 3, 4]
|
||||
);
|
||||
assert_eq!(c(&[0, 1], &[0, 2, 1]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[0, 2, 2]).get_simplified_mapping(),
|
||||
vec![2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[0, 2, 3]).get_simplified_mapping(),
|
||||
vec![2, 3, 4]
|
||||
);
|
||||
assert_eq!(c(&[0, 1], &[1, 0, 0]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(c(&[0, 1], &[1, 0, 1]).get_simplified_mapping(), vec![4]);
|
||||
assert_eq!(c(&[0, 1], &[1, 0, 2]).get_simplified_mapping(), vec![4]);
|
||||
assert_eq!(c(&[0, 1], &[1, 1, 0]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[1, 1, 1]).get_simplified_mapping(),
|
||||
vec![4, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[1, 1, 2]).get_simplified_mapping(),
|
||||
vec![4, 1, 2]
|
||||
);
|
||||
assert_eq!(c(&[0, 1], &[1, 2, 0]).get_simplified_mapping(), vec![2]);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[1, 2, 1]).get_simplified_mapping(),
|
||||
vec![4, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[1, 2, 2]).get_simplified_mapping(),
|
||||
vec![4, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[1, 2, 3]).get_simplified_mapping(),
|
||||
vec![4, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 0, 0]).get_simplified_mapping(),
|
||||
vec![0, 3, 4]
|
||||
);
|
||||
assert_eq!(c(&[0, 1], &[2, 0, 1]).get_simplified_mapping(), vec![0]);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 0, 2]).get_simplified_mapping(),
|
||||
vec![0, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 0, 3]).get_simplified_mapping(),
|
||||
vec![0, 3, 4]
|
||||
);
|
||||
assert_eq!(c(&[0, 1], &[2, 1, 0]).get_simplified_mapping(), vec![0]);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 1, 1]).get_simplified_mapping(),
|
||||
vec![0, 1, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 1, 2]).get_simplified_mapping(),
|
||||
vec![0, 1, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 1, 3]).get_simplified_mapping(),
|
||||
vec![0, 1, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 2, 0]).get_simplified_mapping(),
|
||||
vec![0, 3, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 2, 1]).get_simplified_mapping(),
|
||||
vec![0, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 2, 2]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 2, 3]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 3, 0]).get_simplified_mapping(),
|
||||
vec![0, 3, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 3, 1]).get_simplified_mapping(),
|
||||
vec![0, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 3, 2]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 3, 3]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 3, 4]).get_simplified_mapping(),
|
||||
vec![0, 1, 2, 3, 4]
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1, 2], &[3, 4, 5, 0]).get_simplified_mapping(),
|
||||
vec![0, 3, 4, 5, 2]
|
||||
get_simplified_mapping(&[3, 0, 4, 1, 5, 2, 0]),
|
||||
vec![2, 3, 4, 5, 0],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify() {
|
||||
fn test_simplify_merge() {
|
||||
// 1-way merge
|
||||
assert_eq!(c(&[], &[0]).simplify(), c(&[], &[0]));
|
||||
// 3-way merge
|
||||
@ -1009,7 +1269,7 @@ mod tests {
|
||||
assert_eq!(c(&[0, 0], &[1, 1, 0]).simplify(), c(&[0], &[1, 1]));
|
||||
assert_eq!(c(&[0, 0], &[1, 1, 1]).simplify(), c(&[0, 0], &[1, 1, 1]));
|
||||
assert_eq!(c(&[0, 0], &[1, 1, 2]).simplify(), c(&[0, 0], &[1, 1, 2]));
|
||||
assert_eq!(c(&[0, 0], &[1, 2, 0]).simplify(), c(&[0], &[1, 2]));
|
||||
assert_eq!(c(&[0, 0], &[1, 2, 0]).simplify(), c(&[0], &[2, 1]));
|
||||
assert_eq!(c(&[0, 0], &[1, 2, 1]).simplify(), c(&[0, 0], &[1, 2, 1]));
|
||||
assert_eq!(c(&[0, 0], &[1, 2, 2]).simplify(), c(&[0, 0], &[1, 2, 2]));
|
||||
assert_eq!(c(&[0, 0], &[1, 2, 3]).simplify(), c(&[0, 0], &[1, 2, 3]));
|
||||
@ -1028,11 +1288,11 @@ mod tests {
|
||||
assert_eq!(c(&[0, 1], &[1, 0, 2]).simplify(), c(&[], &[2]));
|
||||
assert_eq!(c(&[0, 1], &[1, 1, 0]).simplify(), c(&[], &[1]));
|
||||
assert_eq!(c(&[0, 1], &[1, 1, 1]).simplify(), c(&[0], &[1, 1]));
|
||||
assert_eq!(c(&[0, 1], &[1, 1, 2]).simplify(), c(&[0], &[2, 1]));
|
||||
assert_eq!(c(&[0, 1], &[1, 1, 2]).simplify(), c(&[0], &[1, 2]));
|
||||
assert_eq!(c(&[0, 1], &[1, 2, 0]).simplify(), c(&[], &[2]));
|
||||
assert_eq!(c(&[0, 1], &[1, 2, 1]).simplify(), c(&[0], &[1, 2]));
|
||||
assert_eq!(c(&[0, 1], &[1, 2, 1]).simplify(), c(&[0], &[2, 1]));
|
||||
assert_eq!(c(&[0, 1], &[1, 2, 2]).simplify(), c(&[0], &[2, 2]));
|
||||
assert_eq!(c(&[0, 1], &[1, 2, 3]).simplify(), c(&[0], &[3, 2]));
|
||||
assert_eq!(c(&[0, 1], &[1, 2, 3]).simplify(), c(&[0], &[2, 3]));
|
||||
assert_eq!(c(&[0, 1], &[2, 0, 0]).simplify(), c(&[1], &[2, 0]));
|
||||
assert_eq!(c(&[0, 1], &[2, 0, 1]).simplify(), c(&[], &[2]));
|
||||
assert_eq!(c(&[0, 1], &[2, 0, 2]).simplify(), c(&[1], &[2, 2]));
|
||||
@ -1045,14 +1305,60 @@ mod tests {
|
||||
assert_eq!(c(&[0, 1], &[2, 2, 1]).simplify(), c(&[0], &[2, 2]));
|
||||
assert_eq!(c(&[0, 1], &[2, 2, 2]).simplify(), c(&[0, 1], &[2, 2, 2]));
|
||||
assert_eq!(c(&[0, 1], &[2, 2, 3]).simplify(), c(&[0, 1], &[2, 2, 3]));
|
||||
assert_eq!(c(&[0, 1], &[2, 3, 0]).simplify(), c(&[1], &[2, 3]));
|
||||
assert_eq!(c(&[0, 1], &[2, 3, 0]).simplify(), c(&[1], &[3, 2]));
|
||||
assert_eq!(c(&[0, 1], &[2, 3, 1]).simplify(), c(&[0], &[2, 3]));
|
||||
assert_eq!(c(&[0, 1], &[2, 3, 2]).simplify(), c(&[0, 1], &[2, 3, 2]));
|
||||
assert_eq!(c(&[0, 1], &[2, 3, 3]).simplify(), c(&[0, 1], &[2, 3, 3]));
|
||||
assert_eq!(c(&[0, 1], &[2, 3, 4]).simplify(), c(&[0, 1], &[2, 3, 4]));
|
||||
assert_eq!(
|
||||
c(&[0, 1, 2], &[3, 4, 5, 0]).simplify(),
|
||||
c(&[1, 2], &[3, 5, 4])
|
||||
c(&[1, 2], &[4, 5, 3])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simplify_diff_of_merges() {
|
||||
assert_eq!(d::<usize>(&[], &[]).simplify(), d(&[], &[]));
|
||||
assert_eq!(d(&[0], &[0]).simplify(), d(&[], &[]));
|
||||
assert_eq!(d(&[1], &[0]).simplify(), d(&[1], &[0]));
|
||||
assert_eq!(d(&[0, 0], &[0, 0]).simplify(), d(&[], &[]));
|
||||
assert_eq!(d(&[0, 1], &[1, 0]).simplify(), d(&[], &[]));
|
||||
assert_eq!(d(&[0, 1], &[0, 1]).simplify(), d(&[], &[]));
|
||||
assert_eq!(d(&[1, 1], &[0, 1]).simplify(), d(&[1], &[0]));
|
||||
assert_eq!(d(&[1, 0], &[0, 2]).simplify(), d(&[1], &[2]));
|
||||
assert_eq!(d(&[1, 0], &[3, 2]).simplify(), d(&[1, 0], &[3, 2]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rearrange_for_distance() {
|
||||
let dist = |x: &usize, y: &usize| x.abs_diff(*y);
|
||||
assert_eq!(
|
||||
d::<usize>(&[], &[]).rearranged_to_optimize_for_distance(dist),
|
||||
d(&[], &[])
|
||||
);
|
||||
assert_eq!(
|
||||
d(&[1], &[2]).rearranged_to_optimize_for_distance(dist),
|
||||
d(&[1], &[2])
|
||||
);
|
||||
assert_eq!(
|
||||
d(&[1, 20], &[2, 21]).rearranged_to_optimize_for_distance(dist),
|
||||
d(&[1, 20], &[2, 21])
|
||||
);
|
||||
assert_eq!(
|
||||
d(&[1, 20], &[21, 2]).rearranged_to_optimize_for_distance(dist),
|
||||
d(&[1, 20], &[2, 21])
|
||||
);
|
||||
assert_eq!(
|
||||
d(&[1, 20, 200], &[2, 201, 21]).rearranged_to_optimize_for_distance(dist),
|
||||
d(&[1, 20, 200], &[2, 21, 201])
|
||||
);
|
||||
assert_eq!(
|
||||
d(&[1, 20, 200], &[201, 21, 2]).rearranged_to_optimize_for_distance(dist),
|
||||
d(&[1, 20, 200], &[2, 21, 201])
|
||||
);
|
||||
assert_eq!(
|
||||
d(&[1, 20, 200], &[201, 2, 21]).rearranged_to_optimize_for_distance(dist),
|
||||
d(&[1, 20, 200], &[2, 21, 201])
|
||||
);
|
||||
}
|
||||
|
||||
@ -1087,7 +1393,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[1, 0], &[0, 0, 0]).update_from_simplified(c(&[3], &[2, 1])),
|
||||
c(&[3, 0], &[0, 1, 2])
|
||||
c(&[3, 0], &[0, 2, 1])
|
||||
);
|
||||
assert_eq!(
|
||||
c(&[0, 1], &[2, 3, 4]).update_from_simplified(c(&[1, 2], &[3, 4, 5])),
|
||||
@ -1271,4 +1577,121 @@ mod tests {
|
||||
c(&[3, 2, 1, 6], &[4, 5, 0, 7, 8])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explain_diff_of_merges() {
|
||||
let dist = |x: &usize, y: &usize| x.abs_diff(*y);
|
||||
insta::assert_debug_snapshot!(
|
||||
explain_diff_of_merges(c(&[], &[1]), c(&[], &[1]), dist),
|
||||
@r#"
|
||||
[
|
||||
UnchangedConflictAdd(
|
||||
1,
|
||||
),
|
||||
]
|
||||
"#
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
explain_diff_of_merges(c(&[], &[1]), c(&[], &[2]), dist),
|
||||
@r#"
|
||||
[
|
||||
ChangedConflictAdd {
|
||||
left_version: 1,
|
||||
right_version: 2,
|
||||
},
|
||||
]
|
||||
"#
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
// One of the conflicts gets simplified to the same case as above
|
||||
explain_diff_of_merges(c(&[], &[1]), c(&[1], &[1,2]), dist),
|
||||
@r#"
|
||||
[
|
||||
ChangedConflictAdd {
|
||||
left_version: 1,
|
||||
right_version: 2,
|
||||
},
|
||||
]
|
||||
"#
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
explain_diff_of_merges(c(&[], &[1]), c(&[0], &[1, 2]), dist),
|
||||
@r#"
|
||||
[
|
||||
AddedConflictDiff {
|
||||
conflict_add: 2,
|
||||
conflict_remove: 0,
|
||||
},
|
||||
UnchangedConflictAdd(
|
||||
1,
|
||||
),
|
||||
]
|
||||
"#
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
explain_diff_of_merges(c(&[0], &[1,2]), c(&[], &[1]), dist),
|
||||
@r#"
|
||||
[
|
||||
RemovedConflictDiff {
|
||||
conflict_add: 2,
|
||||
conflict_remove: 0,
|
||||
},
|
||||
UnchangedConflictAdd(
|
||||
1,
|
||||
),
|
||||
]
|
||||
"#
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
explain_diff_of_merges(c(&[0], &[1,2]), c(&[3], &[1,2]), dist),
|
||||
@r#"
|
||||
[
|
||||
ChangedConflictRemove {
|
||||
left_version: 3,
|
||||
right_version: 0,
|
||||
},
|
||||
UnchangedConflictAdd(
|
||||
1,
|
||||
),
|
||||
UnchangedConflictAdd(
|
||||
2,
|
||||
),
|
||||
]
|
||||
"#
|
||||
);
|
||||
// TODO: Should the unchanged add+remove become "UnchangedConflictDiff" for
|
||||
// nicer presentation?
|
||||
insta::assert_debug_snapshot!(
|
||||
explain_diff_of_merges(c(&[0], &[1,2]), c(&[0], &[1,3]), dist),
|
||||
@r#"
|
||||
[
|
||||
ChangedConflictAdd {
|
||||
left_version: 2,
|
||||
right_version: 3,
|
||||
},
|
||||
UnchangedConflictAdd(
|
||||
1,
|
||||
),
|
||||
UnchangedConflictRemove(
|
||||
0,
|
||||
),
|
||||
]
|
||||
"#
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
// Simplifies
|
||||
explain_diff_of_merges(c(&[0], &[1,2]), c(&[2], &[1,2]), dist),
|
||||
@r#"
|
||||
[
|
||||
RemovedConflictDiff {
|
||||
conflict_add: 2,
|
||||
conflict_remove: 0,
|
||||
},
|
||||
UnchangedConflictAdd(
|
||||
1,
|
||||
),
|
||||
]
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -527,8 +527,8 @@ fn test_simplify_conflict() {
|
||||
assert_eq!(
|
||||
conflict.adds().map(|v| v.as_ref()).collect_vec(),
|
||||
vec![
|
||||
branch_tree.value(component),
|
||||
upstream2_tree.value(component),
|
||||
branch_tree.value(component)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ fn test_merge_ref_targets() {
|
||||
//
|
||||
// Under the hood, the conflict is simplified as below:
|
||||
// ```
|
||||
// 3 4 5 3 4 5 5 4
|
||||
// 3 4 5 3 4 5 4 5
|
||||
// 2 / => 2 3 => 2
|
||||
// 3
|
||||
// ```
|
||||
@ -257,7 +257,7 @@ fn test_merge_ref_targets() {
|
||||
),
|
||||
RefTarget::from_legacy_form(
|
||||
[commit2.id().clone()],
|
||||
[commit5.id().clone(), commit4.id().clone()]
|
||||
[commit4.id().clone(), commit5.id().clone()]
|
||||
)
|
||||
);
|
||||
|
||||
@ -290,7 +290,7 @@ fn test_merge_ref_targets() {
|
||||
//
|
||||
// Under the hood, the conflict is simplified as below:
|
||||
// ```
|
||||
// 3 4 1 3 4 1 1 4
|
||||
// 3 4 1 3 4 1 4 1
|
||||
// 2 / => 2 3 => 2
|
||||
// 3
|
||||
// ```
|
||||
@ -306,7 +306,7 @@ fn test_merge_ref_targets() {
|
||||
),
|
||||
RefTarget::from_legacy_form(
|
||||
[commit2.id().clone()],
|
||||
[commit1.id().clone(), commit4.id().clone()]
|
||||
[commit4.id().clone(), commit1.id().clone()]
|
||||
)
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user