The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl

use Test::More;

use Git::Raw;
use File::Copy;
use File::Slurp;
use Cwd qw(abs_path);
use Capture::Tiny 'capture_stdout';

my $path = abs_path('t/test_repo');
my $repo = Git::Raw::Repository -> open($path);

$repo -> config -> bool('diff.mnemonicprefix', 0);
$repo -> config -> str('core.autocrlf', "true");

my $file  = $repo -> workdir . 'diff';
write_file($file, "diff me, biatch\n");

my $file2  = $repo -> workdir . 'diff2';
write_file($file2, "diff me too, biatch\n");

my $file3  = $repo -> workdir . 'diff3';
write_file($file3, "diff me also, biatch, i have some whitespace    \r\n");

my $index = $repo -> index;
my $tree  = $repo -> head -> target -> tree;

$index -> add('diff');
$index -> add('diff2');

my $printer = sub {
	my ($usage, $line) = @_;

	print ("$usage => $line");
};

my $diff = $repo -> diff({
	'tree'   => $tree,
	'prefix' => { 'a' => 'aaa', 'b' => 'bbb' },
	'context_lines'   => 3,
	'interhunk_lines' => 0,
	'paths'  => [
		undef,
		'diff'
	],
});

ok (!eval { $diff -> print('invalid_format', sub {}) });

my $expected = <<'EOS';
file => diff --git aaa/diff bbb/diff
new file mode 100644
index 0000000..6afc8a6
--- /dev/null
+++ bbb/diff
hunk => @@ -0,0 +1 @@
add => diff me, biatch
EOS

my $output = capture_stdout { $diff -> print("patch", $printer) };
is $output, $expected;

$diff = $repo -> diff({
	'tree'   => $tree,
	'prefix' => { 'a' => 'aaa', 'b' => 'bbb' },
	'paths'  => [ 'diff' ],
	'flags'  => {
		'reverse' => 1
	}
});

$expected = <<'EOS';
file => diff --git bbb/diff aaa/diff
deleted file mode 100644
index 6afc8a6..0000000
--- bbb/diff
+++ /dev/null
hunk => @@ -1 +0,0 @@
del => diff me, biatch
EOS

$output = capture_stdout { $diff -> print("patch", $printer) };
is $output, $expected;

$diff = $repo -> diff({
	'tree'   => $tree,
	'paths'  => [ 'diff2' ]
});

$expected = <<'EOS';
file => diff --git a/diff2 b/diff2
new file mode 100644
index 0000000..e6ada20
--- /dev/null
+++ b/diff2
hunk => @@ -0,0 +1 @@
add => diff me too, biatch
EOS

$output = capture_stdout { $diff -> print("patch", $printer) };
is $output, $expected;

$diff = $repo -> diff({
	'tree'   => $tree,
	'paths'  => [ 'diff3' ],
	'flags'  => {
		'ignore_whitespace' => 1,
		'ignore_whitespace_eol' => 1
	}
});

$expected = '';

$output = capture_stdout { $diff -> print("patch", $printer) };
is $output, $expected;

$diff = $repo -> diff({
	'tree'   => $tree
});

$expected = <<'EOS';
file => A	diff
file => A	diff2
EOS

$output = capture_stdout { $diff -> print("name_status", $printer) };
is $output, $expected;

$expected = <<'EOS';
file => diff
file => diff2
EOS

$output = capture_stdout { $diff -> print("name_only", $printer) };
is $output, $expected;

$expected = <<'EOS';
file => :000000 100644 0000000... 6afc8a6... A	diff
file => :000000 100644 0000000... e6ada20... A	diff2
EOS

$output = capture_stdout { $diff -> print("raw", $printer) };
is $output, $expected;

$expected = <<'EOS';
file => diff --git a/diff b/diff
new file mode 100644
index 0000000..6afc8a6
--- /dev/null
+++ b/diff
file => diff --git a/diff2 b/diff2
new file mode 100644
index 0000000..e6ada20
--- /dev/null
+++ b/diff2
EOS

$output = capture_stdout { $diff -> print("patch_header", $printer) };
is $output, $expected;

is $diff -> delta_count, 2;
my @patches = $diff -> patches;
is scalar(@patches), 2;

foreach my $patch (@patches) {
	my @hunks = $patch -> hunks;
	ok (eval { $patch -> hunks(0) });
	ok (!eval { $patch -> hunks(1) });
	ok (!eval { $patch -> hunks($diff) });
	is $patch -> hunk_count, 1;
	is scalar(@hunks), 1;

	my $hunk = $hunks[0];
	isa_ok $hunk, 'Git::Raw::Diff::Hunk';
	is $hunk -> new_start, 1;
	is $hunk -> new_lines, 1;
	is $hunk -> old_start, 0;
	is $hunk -> old_lines, 0;
	is $hunk -> header, '@@ -0,0 +1 @@';
}

$expected = <<'EOS';
diff --git a/diff b/diff
new file mode 100644
index 0000000..6afc8a6
--- /dev/null
+++ b/diff
@@ -0,0 +1 @@
+diff me, biatch
EOS

is $patches[0] -> buffer, $expected;
is_deeply $patches[0] -> line_stats, {
	'context' => 0, 'additions' => 1, 'deletions' => 0
};

my $delta = $patches[0] -> delta;
isa_ok $delta, 'Git::Raw::Diff::Delta';
is $delta -> file_count, 1;
is $delta -> status, "added";
is_deeply $delta -> flags, [];

my $old_file = $delta -> old_file;
isa_ok $old_file, 'Git::Raw::Diff::File';
is $old_file -> id, '0' x 40;
is $old_file -> path, 'diff';
is_deeply $old_file -> flags, ['valid_id'];
is_deeply $old_file -> mode, 'new';

my $new_file = $delta -> new_file;
isa_ok $new_file, 'Git::Raw::Diff::File';
is substr($new_file -> id, 0, 7), '6afc8a6';
is $new_file -> path, 'diff';
is_deeply $new_file -> flags, ['valid_id'];
is_deeply $new_file -> mode, 'blob';

$expected = <<'EOS';
diff --git a/diff2 b/diff2
new file mode 100644
index 0000000..e6ada20
--- /dev/null
+++ b/diff2
@@ -0,0 +1 @@
+diff me too, biatch
EOS

is $patches[1] -> buffer, $expected;
is_deeply $patches[1] -> line_stats, {
	'context' => 0, 'additions' => 1, 'deletions' => 0
};

my $tree2 = $repo -> head -> target -> tree;
my $tree1 = $repo -> head -> target -> parents -> [0] -> tree;

$diff = $tree1 -> diff({
	'tree' => $tree2,
	'prefix' => { 'a' => 'aaa', 'b' => 'bbb' },
});

$expected = <<'EOS';
file => diff --git aaa/test3/under/the/tree/test3 bbb/test3/under/the/tree/test3
new file mode 100644
index 0000000..c7eaef2
--- /dev/null
+++ bbb/test3/under/the/tree/test3
hunk => @@ -0,0 +1 @@
add => this is a third testdel => 
\ No newline at end of file
EOS

$output = capture_stdout { $diff -> print("patch", $printer) };

is $output, $expected;

$expected = <<'EOS';
file => A	test3/under/the/tree/test3
EOS

$output = capture_stdout { $diff -> print("name_status", $printer) };

is $output, $expected;

is $diff -> delta_count, 1;
@patches = $diff -> patches;
is scalar(@patches), 1;

$delta = $patches[0] -> delta;
is $delta -> old_file -> id, '0' x 40;
is substr($delta -> new_file -> id, 0, 7), 'c7eaef2';


$index -> add('diff');
$index -> add('diff2');
my $index_tree1 = $repo -> lookup($index -> write_tree);

move($file, $file.'.moved');
$index -> remove('diff');
$index -> add('diff.moved');
my $index_tree2 = $repo -> lookup($index -> write_tree);

my $tree_diff = $index_tree1 -> diff({
	'tree'   => $index_tree2
});

is $tree_diff -> delta_count, 2;
@patches = $tree_diff -> patches;

$expected = <<'EOS';
diff --git a/diff b/diff
deleted file mode 100644
index 6afc8a6..0000000
--- a/diff
+++ /dev/null
@@ -1 +0,0 @@
-diff me, biatch
EOS

is $patches[0] -> buffer, $expected;

$expected = <<'EOS';
diff --git a/diff.moved b/diff.moved
new file mode 100644
index 0000000..6afc8a6
--- /dev/null
+++ b/diff.moved
@@ -0,0 +1 @@
+diff me, biatch
EOS

is $patches[1] -> buffer, $expected;

my $stats = $tree_diff -> stats;
isa_ok $stats, 'Git::Raw::Diff::Stats';
is $stats -> insertions, 1;
is $stats -> deletions, 1;
is $stats -> files_changed, 2;

$expected = <<'EOS';
 diff       | 1 -
 diff.moved | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)
 delete mode 100644 diff
 create mode 100644 diff.moved
EOS

is $stats -> buffer({
	'flags' => {
		'full'    => 1,
		'summary' => 1,
	}
}), $expected;

$expected = <<'EOS';
 2 files changed, 1 insertion(+), 1 deletion(-)
 delete mode 100644 diff
 create mode 100644 diff.moved
EOS

is $stats -> buffer({
	'flags' => {
		'short'    => 1,
		'summary'  => 1,
	}
}), $expected;

$tree_diff -> find_similar;
is $tree_diff -> delta_count, 1;
@patches = $tree_diff -> patches;

$expected = <<'EOS';
diff --git a/diff b/diff.moved
index 6afc8a6..6afc8a6 100644
--- a/diff
+++ b/diff.moved
EOS

is $patches[0] -> buffer, $expected;
$delta = $patches[0] -> delta;
isa_ok $delta, 'Git::Raw::Diff::Delta';
is $delta -> status, 'renamed';
is $delta -> similarity, 100;
is $delta -> new_file -> size, 16;

$stats = $tree_diff -> stats;
isa_ok $stats, 'Git::Raw::Diff::Stats';
is $stats -> insertions, 0;
is $stats -> deletions, 0;
is $stats -> files_changed, 1;

$expected = <<'EOS';
 diff => diff.moved | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
EOS

is $stats -> buffer({
	'flags' => {
		'full'    => 1,
		'summary' => 1,
	}
}), $expected;

$expected = <<'EOS';
 1 file changed, 0 insertions(+), 0 deletions(-)
EOS

is $stats -> buffer({
	'flags' => {
		'short'    => 1,
		'summary' => 1,
	}
}), $expected;

my $content = <<'EOS';
AAAAAAAAAA
AAAAAAAAAA
AAAAAAAAAA
AAAAAAAAAA
AAAAAAAAAA
EOS

write_file("$file.moved", $content);
$index -> add('diff.moved');
$index_tree1 = $repo -> lookup($index -> write_tree);

move($file.'.moved', $file);
$index -> remove('diff.moved');

$content = <<'EOS';
AAAAAAAAAA
AAAAAAAAAA
AAAAZZAAAA
AAAAAAAAAA
AAAAAAAAAA
EOS

write_file($file, $content);
$index -> add('diff');
$index_tree2 = $repo -> lookup($index -> write_tree);

$tree_diff = $index_tree1 -> diff({
	'tree'   => $index_tree2,
	'flags'  => {
		'all' => 1
	},
	'context_lines'   => 3,
	'interhunk_lines' => 0,
	'paths' => [
		undef,
		'diff',
		'diff.moved'
	]
});

is $tree_diff -> delta_count, 2;
ok (!eval { $tree_diff -> find_similar([]) });
$tree_diff -> find_similar({
	'flags' => {
		'renames'                   => 1,
		'ignore_whitespace'         => 1,
		'ignore_leading_whitespace' => 1,
		'break_rewrites'            => 1,
	},
	'rename_threshold'              => 50,
	'rename_from_rewrite_threshold' => 50,
	'copy_threshold'                => 50,
	'break_rewrite_threshold'       => 60,
	'rename_limit'                  => 200,
});

is $tree_diff -> delta_count, 1;
@patches = $tree_diff -> patches;

$expected = <<'EOS';
diff --git a/diff.moved b/diff
index 5b96873..f97fd8f 100644
--- a/diff.moved
+++ b/diff
@@ -1,5 +1,5 @@
 AAAAAAAAAA
 AAAAAAAAAA
-AAAAAAAAAA
+AAAAZZAAAA
 AAAAAAAAAA
 AAAAAAAAAA
EOS

is $patches[0] -> buffer, $expected;

$content = <<'EOS';
 AAAAAAAAAA
AAAAAAAAAA
AAAAZZAAAA
AAAAAAAAAA
 AAAAAAAAAA
EOS

write_file($file, $content);
$index -> add('diff');
$index_tree2 = $repo -> lookup($index -> write_tree);

$tree_diff = $index_tree1 -> diff({
	'tree'   => $index_tree2,
	'flags'  => {
		'all' => 1
	}
});

is $tree_diff -> delta_count, 2;
$tree_diff -> find_similar;
is $tree_diff -> delta_count, 1;
@patches = $tree_diff -> patches;

done_testing;