-
-
Notifications
You must be signed in to change notification settings - Fork 312
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement
text
and binary
merge algorithms, also with baseline te…
…sts for correctness.
- Loading branch information
Showing
9 changed files
with
841 additions
and
22 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
#!/usr/bin/env bash | ||
set -eu -o pipefail | ||
|
||
git init | ||
rm -Rf .git/hooks | ||
|
||
function baseline() { | ||
local ours=$DIR/${1:?1: our file}.blob; | ||
local base=$DIR/${2:?2: base file}.blob; | ||
local theirs=$DIR/${3:?3: their file}.blob; | ||
local output=$DIR/${4:?4: the name of the output file}.merged; | ||
|
||
shift 4 | ||
git merge-file --stdout "$@" "$ours" "$base" "$theirs" > "$output" || true | ||
|
||
echo "$ours" "$base" "$theirs" "$output" "$@" >> baseline.cases | ||
} | ||
|
||
mkdir simple | ||
(cd simple | ||
echo -e "line1-changed-by-both\nline2-to-be-changed-in-incoming" > ours.blob | ||
echo -e "line1-to-be-changed-by-both\nline2-to-be-changed-in-incoming" > base.blob | ||
echo -e "line1-changed-by-both\nline2-changed" > theirs.blob | ||
) | ||
|
||
# one big change includes multiple smaller ones | ||
mkdir multi-change | ||
(cd multi-change | ||
cat <<EOF > base.blob | ||
0 | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
6 | ||
7 | ||
8 | ||
9 | ||
EOF | ||
|
||
cat <<EOF > ours.blob | ||
0 | ||
1 | ||
X | ||
X | ||
4 | ||
5 | ||
Y | ||
Y | ||
8 | ||
Z | ||
EOF | ||
|
||
cat <<EOF > theirs.blob | ||
T | ||
T | ||
T | ||
T | ||
T | ||
T | ||
T | ||
T | ||
T | ||
T | ||
EOF | ||
) | ||
|
||
# a change with deletion/clearing our file | ||
mkdir clear-ours | ||
(cd clear-ours | ||
cat <<EOF > base.blob | ||
0 | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
EOF | ||
|
||
touch ours.blob | ||
|
||
cat <<EOF > theirs.blob | ||
T | ||
T | ||
T | ||
T | ||
T | ||
EOF | ||
) | ||
|
||
# a change with deletion/clearing their file | ||
mkdir clear-theirs | ||
(cd clear-theirs | ||
cat <<EOF > base.blob | ||
0 | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
EOF | ||
|
||
cat <<EOF > ours.blob | ||
O | ||
O | ||
O | ||
O | ||
O | ||
EOF | ||
|
||
touch theirs.blob | ||
) | ||
|
||
# differently sized changes | ||
mkdir ours-2-lines-theirs-1-line | ||
(cd ours-2-lines-theirs-1-line | ||
cat <<EOF > base.blob | ||
0 | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
EOF | ||
|
||
cat <<EOF > ours.blob | ||
0 | ||
1 | ||
X | ||
X | ||
4 | ||
5 | ||
EOF | ||
|
||
cat <<EOF > theirs.blob | ||
0 | ||
1 | ||
Y | ||
3 | ||
4 | ||
5 | ||
EOF | ||
) | ||
|
||
# partial match | ||
mkdir partial-match | ||
(cd partial-match | ||
cat <<EOF > base.blob | ||
0 | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
EOF | ||
|
||
cat <<EOF > ours.blob | ||
0 | ||
X | ||
X | ||
X | ||
X | ||
5 | ||
EOF | ||
|
||
cat <<EOF > theirs.blob | ||
0 | ||
X | ||
2 | ||
X | ||
X | ||
5 | ||
EOF | ||
) | ||
|
||
for dir in simple multi-change clear-ours clear-theirs ours-2-lines-theirs-1-line partial-match; do | ||
DIR=$dir | ||
baseline ours base theirs merge | ||
baseline ours base theirs diff3 --diff3 | ||
baseline ours base theirs zdiff3 --zdiff3 | ||
baseline ours base theirs merge-ours --ours | ||
baseline ours base theirs merge-theirs --theirs | ||
baseline ours base theirs merge-union --union | ||
done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
use gix_merge::blob::builtin_driver::binary::{Pick, ResolveWith}; | ||
use gix_merge::blob::{builtin_driver, Resolution}; | ||
|
||
#[test] | ||
fn binary() { | ||
assert_eq!( | ||
builtin_driver::binary(None), | ||
(Pick::Ours, Resolution::Conflict), | ||
"by default it picks ours and marks it as conflict" | ||
); | ||
assert_eq!( | ||
builtin_driver::binary(Some(ResolveWith::Ancestor)), | ||
(Pick::Ancestor, Resolution::Complete), | ||
"Otherwise we can pick anything and it will mark it as complete" | ||
); | ||
assert_eq!( | ||
builtin_driver::binary(Some(ResolveWith::Ours)), | ||
(Pick::Ours, Resolution::Complete) | ||
); | ||
assert_eq!( | ||
builtin_driver::binary(Some(ResolveWith::Theirs)), | ||
(Pick::Theirs, Resolution::Complete) | ||
); | ||
} | ||
|
||
mod text { | ||
use bstr::ByteSlice; | ||
use gix_merge::blob::builtin_driver::text::ResolveWith; | ||
use gix_merge::blob::Resolution; | ||
use pretty_assertions::assert_str_eq; | ||
|
||
#[test] | ||
fn run_baseline() -> crate::Result { | ||
let root = gix_testtools::scripted_fixture_read_only("text-baseline.sh")?; | ||
let cases = std::fs::read_to_string(root.join("baseline.cases"))?; | ||
let mut out = Vec::new(); | ||
let mut tokens = Vec::new(); | ||
for case in baseline::Expectations::new(&root, &cases) | ||
// TODO: remove filter | ||
.filter(|case| { | ||
matches!( | ||
case.options.on_conflict, | ||
Some(ResolveWith::Union | ResolveWith::Ours | ResolveWith::Theirs) | ||
) | ||
}) | ||
{ | ||
let mut input = imara_diff::intern::InternedInput::default(); | ||
dbg!(&case.name, case.options); | ||
let actual = gix_merge::blob::builtin_driver::text( | ||
&mut out, | ||
&mut input, | ||
&mut tokens, | ||
&case.ours, | ||
&case.base, | ||
&case.theirs, | ||
case.options, | ||
); | ||
let expected_resolution = if case.expected.contains_str("<<<<<<<") { | ||
Resolution::Conflict | ||
} else { | ||
Resolution::Complete | ||
}; | ||
assert_eq!(actual, expected_resolution, "{}: resolution mismatch", case.name); | ||
assert_str_eq!( | ||
out.as_bstr().to_str_lossy(), | ||
case.expected.to_str_lossy(), | ||
"{}: output mismatch", | ||
case.name | ||
); | ||
} | ||
Ok(()) | ||
} | ||
|
||
mod baseline { | ||
use bstr::BString; | ||
use gix_merge::blob::builtin_driver::text::{ConflictStyle, ResolveWith}; | ||
use std::path::Path; | ||
|
||
#[derive(Debug)] | ||
pub struct Expectation { | ||
pub ours: BString, | ||
pub theirs: BString, | ||
pub base: BString, | ||
pub name: BString, | ||
pub expected: BString, | ||
pub options: gix_merge::blob::builtin_driver::text::Options, | ||
} | ||
|
||
pub struct Expectations<'a> { | ||
root: &'a Path, | ||
lines: std::str::Lines<'a>, | ||
} | ||
|
||
impl<'a> Expectations<'a> { | ||
pub fn new(root: &'a Path, cases: &'a str) -> Self { | ||
Expectations { | ||
root, | ||
lines: cases.lines(), | ||
} | ||
} | ||
} | ||
|
||
impl Iterator for Expectations<'_> { | ||
type Item = Expectation; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
let line = self.lines.next()?; | ||
let mut words = line.split(' '); | ||
let (Some(ours), Some(base), Some(theirs), Some(output)) = | ||
(words.next(), words.next(), words.next(), words.next()) | ||
else { | ||
panic!("need at least the input and output") | ||
}; | ||
|
||
let read = |rela_path: &str| read_blob(&self.root, rela_path); | ||
|
||
let mut options = gix_merge::blob::builtin_driver::text::Options::default(); | ||
for arg in words { | ||
match arg { | ||
"--diff3" => options.conflict_style = ConflictStyle::Diff3, | ||
"--zdiff3" => options.conflict_style = ConflictStyle::ZealousDiff3, | ||
"--ours" => options.on_conflict = Some(ResolveWith::Ours), | ||
"--theirs" => options.on_conflict = Some(ResolveWith::Theirs), | ||
"--union" => options.on_conflict = Some(ResolveWith::Union), | ||
_ => panic!("Unknown argument to parse into options: '{arg}'"), | ||
} | ||
} | ||
|
||
Some(Expectation { | ||
ours: read(ours), | ||
theirs: read(theirs), | ||
base: read(base), | ||
expected: read(output), | ||
name: output.into(), | ||
options, | ||
}) | ||
} | ||
} | ||
|
||
fn read_blob(root: &Path, rela_path: &str) -> BString { | ||
std::fs::read(root.join(rela_path)) | ||
.unwrap_or_else(|_| panic!("Failed to read '{rela_path}' in '{}'", root.display())) | ||
.into() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mod builtin_driver; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#[cfg(feature = "blob")] | ||
mod blob; | ||
|
||
pub use gix_testtools::Result; |