From 665dd77d572cb475d66567e2de959d679d6dd58c Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Thu, 6 Mar 2025 16:04:18 +1100 Subject: [PATCH 1/2] Add ability to diff two arbitrary buffers --- .../DiffBufferToBufferFixture.cs | 319 ++++++++++++++++++ LibGit2Sharp/ContentChanges.cs | 10 + LibGit2Sharp/Core/NativeMethods.cs | 17 + LibGit2Sharp/Core/Proxy.cs | 32 ++ LibGit2Sharp/Diff.cs | 34 ++ 5 files changed, 412 insertions(+) create mode 100644 LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs diff --git a/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs b/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs new file mode 100644 index 000000000..215425db6 --- /dev/null +++ b/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs @@ -0,0 +1,319 @@ +using System.IO; +using System.Linq; +using System.Text; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class DiffBufferToBufferFixture + { + + [Fact] + public void ComparingTheSameBufferReturnsNoDifference() + { + const string text = @" +1 +2 +3 +4 +5 +6"; + + var buffer = Encoding.UTF8.GetBytes(text); + + var changes = Diff.CompareBuffers(buffer, buffer); + + Assert.Equal(0, changes.LinesAdded); + Assert.Equal(0, changes.LinesDeleted); + Assert.Equal(string.Empty, changes.Patch); + } + + [Fact] + public void CanCompareTwoBuffersWithADiffOfTwoHunks() + { + const string oldText = @"1 +2 +4 +5 +6 +7 +8"; + + const string newText = @"1 +2 +3 +4 +5 +7 +6 +7 +8 +9"; + + var oldBuffer = Encoding.UTF8.GetBytes(oldText); + var newBuffer = Encoding.UTF8.GetBytes(newText); + + var changes = Diff.CompareBuffers(oldBuffer, newBuffer); + + Assert.False(changes.IsBinaryComparison); + + Assert.Equal(3, changes.LinesAdded); + Assert.Equal(1, changes.LinesDeleted); + + var expected = new StringBuilder() + .Append("@@ -1,4 +1,5 @@\n") + .Append(" 1\n") + .Append("+2\n") + .Append(" 3\n") + .Append(" 4\n") + .Append(" 5\n") + .Append("@@ -8,8 +9,9 @@\n") + .Append(" 8\n") + .Append(" 9\n") + .Append(" 10\n") + .Append("-12\n") + .Append("+11\n") + .Append(" 12\n") + .Append(" 13\n") + .Append(" 14\n") + .Append(" 15\n") + .Append("+16\n"); + + Assert.Equal(expected.ToString(), changes.Patch); + } +// [Fact] +// public void ComparingABlobAgainstItselfReturnsNoDifference() +// { +// var path = SandboxStandardTestRepoGitDir(); +// using (var repo = new Repository(path)) +// { +// var blob = repo.Lookup("7909961"); +// +// ContentChanges changes = repo.Diff.Compare(blob, blob); +// +// Assert.Equal(0, changes.LinesAdded); +// Assert.Equal(0, changes.LinesDeleted); +// Assert.Equal(string.Empty, changes.Patch); +// } +// } +// +// [Fact] +// public void CanCompareTwoVersionsOfABlobWithADiffOfTwoHunks() +// { +// var path = SandboxStandardTestRepoGitDir(); +// using (var repo = new Repository(path)) +// { +// var oldblob = repo.Lookup("7909961"); +// var newblob = repo.Lookup("4e935b7"); +// +// ContentChanges changes = repo.Diff.Compare(oldblob, newblob); +// +// Assert.False(changes.IsBinaryComparison); +// +// Assert.Equal(3, changes.LinesAdded); +// Assert.Equal(1, changes.LinesDeleted); +// +// var expected = new StringBuilder() +// .Append("@@ -1,4 +1,5 @@\n") +// .Append(" 1\n") +// .Append("+2\n") +// .Append(" 3\n") +// .Append(" 4\n") +// .Append(" 5\n") +// .Append("@@ -8,8 +9,9 @@\n") +// .Append(" 8\n") +// .Append(" 9\n") +// .Append(" 10\n") +// .Append("-12\n") +// .Append("+11\n") +// .Append(" 12\n") +// .Append(" 13\n") +// .Append(" 14\n") +// .Append(" 15\n") +// .Append("+16\n"); +// +// Assert.Equal(expected.ToString(), changes.Patch); +// } +// } +// +// Blob CreateBinaryBlob(IRepository repo) +// { +// string fullpath = Path.Combine(repo.Info.WorkingDirectory, "binary.bin"); +// +// File.WriteAllBytes(fullpath, new byte[] { 17, 16, 0, 4, 65 }); +// +// return repo.ObjectDatabase.CreateBlob(fullpath); +// } +// +// [Fact] +// public void CanCompareATextualBlobAgainstABinaryBlob() +// { +// string path = SandboxStandardTestRepo(); +// using (var repo = new Repository(path)) +// { +// Blob binBlob = CreateBinaryBlob(repo); +// +// var blob = repo.Lookup("7909961"); +// +// ContentChanges changes = repo.Diff.Compare(blob, binBlob); +// +// Assert.True(changes.IsBinaryComparison); +// +// Assert.Equal(0, changes.LinesAdded); +// Assert.Equal(0, changes.LinesDeleted); +// } +// } +// +// [Fact] +// public void CanCompareABlobAgainstANullBlob() +// { +// var path = SandboxStandardTestRepoGitDir(); +// using (var repo = new Repository(path)) +// { +// var blob = repo.Lookup("7909961"); +// +// ContentChanges changes = repo.Diff.Compare(null, blob); +// +// Assert.NotEqual(0, changes.LinesAdded); +// Assert.Equal(0, changes.LinesDeleted); +// Assert.NotEqual(string.Empty, changes.Patch); +// +// changes = repo.Diff.Compare(blob, null); +// +// Assert.Equal(0, changes.LinesAdded); +// Assert.NotEqual(0, changes.LinesDeleted); +// Assert.NotEqual(string.Empty, changes.Patch); +// } +// } +// +// [Fact] +// public void ComparingTwoNullBlobsReturnsAnEmptyContentChanges() +// { +// var path = SandboxStandardTestRepoGitDir(); +// using (var repo = new Repository(path)) +// { +// ContentChanges changes = repo.Diff.Compare((Blob)null, (Blob)null); +// +// Assert.False(changes.IsBinaryComparison); +// +// Assert.Equal(0, changes.LinesAdded); +// Assert.Equal(0, changes.LinesDeleted); +// } +// } +// +// [Fact] +// public void ComparingBlobsWithNoSpacesAndIndentHeuristicOptionMakesADifference() +// { +// var path = SandboxStandardTestRepoGitDir(); +// using (var repo = new Repository(path)) +// { +// // Based on test diff indent heuristic from: +// // https://github.com/git/git/blob/433860f3d0beb0c6f205290bd16cda413148f098/t/t4061-diff-indent.sh#L17 +// var oldContent = +// @" 1 +// 2 +// a +// +// b +// 3 +// 4"; +// var newContent = +// @" 1 +// 2 +// a +// +// b +// a +// +// b +// 3 +// 4"; +// var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); +// var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); +// var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; +// var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; +// +// ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption); +// ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption); +// +// Assert.NotEqual(changes0.Patch, changes1.Patch); +// Assert.Equal(CanonicalChangedLines(changes0), CanonicalChangedLines(changes1)); +// } +// } +// +// [Fact] +// public void ComparingBlobsWithNoSpacesIndentHeuristicOptionMakesNoDifference() +// { +// var path = SandboxStandardTestRepoGitDir(); +// using (var repo = new Repository(path)) +// { +// var oldContent = +// @" 1 +// 2 +// a +// b +// 3 +// 4"; +// var newContent = +// @" 1 +// 2 +// a +// b +// a +// b +// 3 +// 4"; +// var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); +// var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); +// var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; +// var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; +// +// ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption); +// ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption); +// +// Assert.Equal(changes0.Patch, changes1.Patch); +// } +// } +// +// [Fact] +// public void DiffSetsTheAddedAndDeletedLinesCorrectly() +// { +// var path = SandboxStandardTestRepoGitDir(); +// +// using (var repo = new Repository(path)) +// { +// var oldContent = +// @"1 +// 2 +// 3 +// 4"; +// +// var newContent = +// @"1 +// 2 +// 3 +// 5"; +// var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); +// var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); +// +// ContentChanges changes = repo.Diff.Compare(oldBlob, newBlob); +// +// Assert.Single(changes.AddedLines); +// Assert.Single(changes.DeletedLines); +// +// Assert.Equal("4", changes.DeletedLines.First().Content); +// Assert.Equal("5", changes.AddedLines.First().Content); +// +// Assert.Equal(4, changes.DeletedLines.First().LineNumber); +// Assert.Equal(4, changes.AddedLines.First().LineNumber); +// } +// } +// +// static string CanonicalChangedLines(ContentChanges changes) +// { +// // Create an ordered representation of lines that have been added or removed +// return string.Join("\n", changes.Patch.Split('\n').Where(l => l.StartsWith("+") || l.StartsWith("-")).OrderBy(l => l)); +// } + } +} diff --git a/LibGit2Sharp/ContentChanges.cs b/LibGit2Sharp/ContentChanges.cs index c4628f919..0a2d80909 100644 --- a/LibGit2Sharp/ContentChanges.cs +++ b/LibGit2Sharp/ContentChanges.cs @@ -32,6 +32,16 @@ internal unsafe ContentChanges(Repository repo, Blob oldBlob, Blob newBlob, GitD LineCallback); } + internal unsafe ContentChanges(byte[] oldBuffer, byte[] newBuffer, GitDiffOptions options) + { + Proxy.git_diff_buffers(oldBuffer, + newBuffer, + options, + FileCallback, + HunkCallback, + LineCallback); + } + internal ContentChanges(bool isBinaryComparison) { this.IsBinaryComparison = isBinaryComparison; diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index cbb850b16..073d96e5c 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -712,6 +712,23 @@ internal static extern unsafe int git_diff_find_similar( [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe git_diff_delta* git_diff_get_delta(git_diff* diff, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_buffers( + IntPtr oldBuffer, + UIntPtr oldBufferLength, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] + string oldAsPath, + IntPtr newBuffer, + UIntPtr newBufferLength, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] + string newAsPath, + GitDiffOptions options, + git_diff_file_cb fileCallback, + git_diff_binary_cb binaryCallback, + git_diff_hunk_cb hunkCallback, + git_diff_line_cb lineCallback, + IntPtr payload); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_filter_register( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 83d35e22c..1fe148dac 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -854,6 +854,38 @@ public static unsafe int git_diff_num_deltas(DiffHandle diff) return NativeMethods.git_diff_get_delta(diff, (UIntPtr)idx); } + public static unsafe void git_diff_buffers( + byte[] oldBuffer, + byte[] newBuffer, + GitDiffOptions options, + NativeMethods.git_diff_file_cb fileCallback, + NativeMethods.git_diff_hunk_cb hunkCallback, + NativeMethods.git_diff_line_cb lineCallback) + { + int res; + fixed(byte* oldP = oldBuffer) + { + fixed(byte* newP = newBuffer) + { + res = NativeMethods.git_diff_buffers( + (IntPtr)oldP, + new UIntPtr((ulong)oldBuffer.LongLength), + null, + (IntPtr)newP, + new UIntPtr((ulong)newBuffer.LongLength), + null, + options, + fileCallback, + null, + hunkCallback, + lineCallback, + IntPtr.Zero); + } + } + + Ensure.ZeroResult(res); + } + #endregion #region git_error_ diff --git a/LibGit2Sharp/Diff.cs b/LibGit2Sharp/Diff.cs index 857eb8ed1..3a3796d09 100644 --- a/LibGit2Sharp/Diff.cs +++ b/LibGit2Sharp/Diff.cs @@ -124,6 +124,32 @@ private static T BuildDiffResult(DiffHandle diff) where T : class, IDiffResul return (T)builder(diff); } + /// + /// Show changes between two arbitrary buffers. + /// + /// The buffer you want to compare from. + /// The buffer you want to compare to. + /// A containing the changes between the and the . + public static ContentChanges Compare(byte[] oldBuffer, byte[] newBuffer) + { + return Compare(oldBuffer, newBuffer, null); + } + + /// + /// Show changes between two arbitrary buffers. + /// + /// The buffer you want to compare from. + /// The buffer you want to compare to. + /// Additional options to define comparison behavior. + /// A containing the changes between the and the . + public static ContentChanges Compare(byte[] oldBuffer, byte[] newBuffer, CompareOptions compareOptions) + { + using(var options = BuildOptions(DiffModifiers.None, compareOptions: compareOptions)) + { + return new ContentChanges(oldBuffer, newBuffer, options); + } + } + /// /// Show changes between two s. /// @@ -462,6 +488,14 @@ public virtual T Compare( return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths, explicitPathsOptions, compareOptions); } + public static ContentChanges CompareBuffers(byte[] oldBuffer, byte[] newBuffer) + { + using(var options = BuildOptions(DiffModifiers.None)) + { + return new ContentChanges(oldBuffer, newBuffer, options); + } + } + internal virtual T Compare( DiffModifiers diffOptions, IEnumerable paths = null, From 037ca580dc2fe0e076a0031410fa44d85990e5bf Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Fri, 7 Mar 2025 14:54:27 +1100 Subject: [PATCH 2/2] Add unit tests for buffer to buffer diffing --- .../DiffBufferToBufferFixture.cs | 525 +++++++++--------- LibGit2Sharp/Core/Proxy.cs | 4 +- LibGit2Sharp/Diff.cs | 8 - 3 files changed, 271 insertions(+), 266 deletions(-) diff --git a/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs b/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs index 215425db6..e172b6631 100644 --- a/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs +++ b/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Text; using LibGit2Sharp.Tests.TestHelpers; @@ -8,7 +9,6 @@ namespace LibGit2Sharp.Tests { public class DiffBufferToBufferFixture { - [Fact] public void ComparingTheSameBufferReturnsNoDifference() { @@ -22,39 +22,38 @@ public void ComparingTheSameBufferReturnsNoDifference() var buffer = Encoding.UTF8.GetBytes(text); - var changes = Diff.CompareBuffers(buffer, buffer); + var changes = Diff.Compare(buffer, buffer); Assert.Equal(0, changes.LinesAdded); Assert.Equal(0, changes.LinesDeleted); Assert.Equal(string.Empty, changes.Patch); - } + } [Fact] - public void CanCompareTwoBuffersWithADiffOfTwoHunks() + public void CanCompareTwoBuffersWithADiffOfOneHunk() { - const string oldText = @"1 + var oldText = @"1 2 4 5 6 -7 -8"; +8 +7".EnsureUnixLineEndings(); - const string newText = @"1 + var newText = @"1 2 3 4 5 -7 6 7 8 -9"; +9".EnsureUnixLineEndings(); var oldBuffer = Encoding.UTF8.GetBytes(oldText); var newBuffer = Encoding.UTF8.GetBytes(newText); - var changes = Diff.CompareBuffers(oldBuffer, newBuffer); + var changes = Diff.Compare(oldBuffer, newBuffer); Assert.False(changes.IsBinaryComparison); @@ -62,258 +61,272 @@ public void CanCompareTwoBuffersWithADiffOfTwoHunks() Assert.Equal(1, changes.LinesDeleted); var expected = new StringBuilder() - .Append("@@ -1,4 +1,5 @@\n") + .Append("@@ -1,7 +1,9 @@\n") .Append(" 1\n") - .Append("+2\n") - .Append(" 3\n") + .Append(" 2\n") + .Append("+3\n") .Append(" 4\n") .Append(" 5\n") - .Append("@@ -8,8 +9,9 @@\n") + .Append(" 6\n") + .Append("+7\n") .Append(" 8\n") + .Append("-7\n") + .Append("\\ No newline at end of file\n") + .Append("+9\n") + .Append("\\ No newline at end of file\n"); + + Assert.Equal(expected.ToString(), changes.Patch); + } + + [Fact] + public void CanCompareTwoBuffersWithADiffOfTwoHunks() + { + var oldText = @"1 +2 +3 +4 +5 +6 +8 +7 +9 +10 +11".EnsureUnixLineEndings(); + + var newText = @"2 +3 +4 +5 +6 +8 +7 +9 +10 +12".EnsureUnixLineEndings(); + + var oldBuffer = Encoding.UTF8.GetBytes(oldText); + var newBuffer = Encoding.UTF8.GetBytes(newText); + + var changes = Diff.Compare(oldBuffer, newBuffer); + + Assert.False(changes.IsBinaryComparison); + + Assert.Equal(1, changes.LinesAdded); + Assert.Equal(2, changes.LinesDeleted); + + var expected = new StringBuilder() + .Append("@@ -1,4 +1,3 @@\n") + .Append("-1\n") + .Append(" 2\n") + .Append(" 3\n") + .Append(" 4\n") + .Append("@@ -8,4 +7,4 @@\n") + .Append(" 7\n") .Append(" 9\n") .Append(" 10\n") - .Append("-12\n") - .Append("+11\n") - .Append(" 12\n") - .Append(" 13\n") - .Append(" 14\n") - .Append(" 15\n") - .Append("+16\n"); + .Append("-11\n") + .Append("\\ No newline at end of file\n") + .Append("+12\n") + .Append("\\ No newline at end of file\n"); Assert.Equal(expected.ToString(), changes.Patch); } -// [Fact] -// public void ComparingABlobAgainstItselfReturnsNoDifference() -// { -// var path = SandboxStandardTestRepoGitDir(); -// using (var repo = new Repository(path)) -// { -// var blob = repo.Lookup("7909961"); -// -// ContentChanges changes = repo.Diff.Compare(blob, blob); -// -// Assert.Equal(0, changes.LinesAdded); -// Assert.Equal(0, changes.LinesDeleted); -// Assert.Equal(string.Empty, changes.Patch); -// } -// } -// -// [Fact] -// public void CanCompareTwoVersionsOfABlobWithADiffOfTwoHunks() -// { -// var path = SandboxStandardTestRepoGitDir(); -// using (var repo = new Repository(path)) -// { -// var oldblob = repo.Lookup("7909961"); -// var newblob = repo.Lookup("4e935b7"); -// -// ContentChanges changes = repo.Diff.Compare(oldblob, newblob); -// -// Assert.False(changes.IsBinaryComparison); -// -// Assert.Equal(3, changes.LinesAdded); -// Assert.Equal(1, changes.LinesDeleted); -// -// var expected = new StringBuilder() -// .Append("@@ -1,4 +1,5 @@\n") -// .Append(" 1\n") -// .Append("+2\n") -// .Append(" 3\n") -// .Append(" 4\n") -// .Append(" 5\n") -// .Append("@@ -8,8 +9,9 @@\n") -// .Append(" 8\n") -// .Append(" 9\n") -// .Append(" 10\n") -// .Append("-12\n") -// .Append("+11\n") -// .Append(" 12\n") -// .Append(" 13\n") -// .Append(" 14\n") -// .Append(" 15\n") -// .Append("+16\n"); -// -// Assert.Equal(expected.ToString(), changes.Patch); -// } -// } -// -// Blob CreateBinaryBlob(IRepository repo) -// { -// string fullpath = Path.Combine(repo.Info.WorkingDirectory, "binary.bin"); -// -// File.WriteAllBytes(fullpath, new byte[] { 17, 16, 0, 4, 65 }); -// -// return repo.ObjectDatabase.CreateBlob(fullpath); -// } -// -// [Fact] -// public void CanCompareATextualBlobAgainstABinaryBlob() -// { -// string path = SandboxStandardTestRepo(); -// using (var repo = new Repository(path)) -// { -// Blob binBlob = CreateBinaryBlob(repo); -// -// var blob = repo.Lookup("7909961"); -// -// ContentChanges changes = repo.Diff.Compare(blob, binBlob); -// -// Assert.True(changes.IsBinaryComparison); -// -// Assert.Equal(0, changes.LinesAdded); -// Assert.Equal(0, changes.LinesDeleted); -// } -// } -// -// [Fact] -// public void CanCompareABlobAgainstANullBlob() -// { -// var path = SandboxStandardTestRepoGitDir(); -// using (var repo = new Repository(path)) -// { -// var blob = repo.Lookup("7909961"); -// -// ContentChanges changes = repo.Diff.Compare(null, blob); -// -// Assert.NotEqual(0, changes.LinesAdded); -// Assert.Equal(0, changes.LinesDeleted); -// Assert.NotEqual(string.Empty, changes.Patch); -// -// changes = repo.Diff.Compare(blob, null); -// -// Assert.Equal(0, changes.LinesAdded); -// Assert.NotEqual(0, changes.LinesDeleted); -// Assert.NotEqual(string.Empty, changes.Patch); -// } -// } -// -// [Fact] -// public void ComparingTwoNullBlobsReturnsAnEmptyContentChanges() -// { -// var path = SandboxStandardTestRepoGitDir(); -// using (var repo = new Repository(path)) -// { -// ContentChanges changes = repo.Diff.Compare((Blob)null, (Blob)null); -// -// Assert.False(changes.IsBinaryComparison); -// -// Assert.Equal(0, changes.LinesAdded); -// Assert.Equal(0, changes.LinesDeleted); -// } -// } -// -// [Fact] -// public void ComparingBlobsWithNoSpacesAndIndentHeuristicOptionMakesADifference() -// { -// var path = SandboxStandardTestRepoGitDir(); -// using (var repo = new Repository(path)) -// { -// // Based on test diff indent heuristic from: -// // https://github.com/git/git/blob/433860f3d0beb0c6f205290bd16cda413148f098/t/t4061-diff-indent.sh#L17 -// var oldContent = -// @" 1 -// 2 -// a -// -// b -// 3 -// 4"; -// var newContent = -// @" 1 -// 2 -// a -// -// b -// a -// -// b -// 3 -// 4"; -// var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); -// var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); -// var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; -// var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; -// -// ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption); -// ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption); -// -// Assert.NotEqual(changes0.Patch, changes1.Patch); -// Assert.Equal(CanonicalChangedLines(changes0), CanonicalChangedLines(changes1)); -// } -// } -// -// [Fact] -// public void ComparingBlobsWithNoSpacesIndentHeuristicOptionMakesNoDifference() -// { -// var path = SandboxStandardTestRepoGitDir(); -// using (var repo = new Repository(path)) -// { -// var oldContent = -// @" 1 -// 2 -// a -// b -// 3 -// 4"; -// var newContent = -// @" 1 -// 2 -// a -// b -// a -// b -// 3 -// 4"; -// var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); -// var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); -// var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; -// var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; -// -// ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption); -// ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption); -// -// Assert.Equal(changes0.Patch, changes1.Patch); -// } -// } -// -// [Fact] -// public void DiffSetsTheAddedAndDeletedLinesCorrectly() -// { -// var path = SandboxStandardTestRepoGitDir(); -// -// using (var repo = new Repository(path)) -// { -// var oldContent = -// @"1 -// 2 -// 3 -// 4"; -// -// var newContent = -// @"1 -// 2 -// 3 -// 5"; -// var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); -// var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); -// -// ContentChanges changes = repo.Diff.Compare(oldBlob, newBlob); -// -// Assert.Single(changes.AddedLines); -// Assert.Single(changes.DeletedLines); -// -// Assert.Equal("4", changes.DeletedLines.First().Content); -// Assert.Equal("5", changes.AddedLines.First().Content); -// -// Assert.Equal(4, changes.DeletedLines.First().LineNumber); -// Assert.Equal(4, changes.AddedLines.First().LineNumber); -// } -// } -// -// static string CanonicalChangedLines(ContentChanges changes) -// { -// // Create an ordered representation of lines that have been added or removed -// return string.Join("\n", changes.Patch.Split('\n').Where(l => l.StartsWith("+") || l.StartsWith("-")).OrderBy(l => l)); -// } + + [Fact] + public void CanCompareATextualBufferAgainstABinaryBuffer() + { + var oldText = @"1 +2 +3 +4 +5".EnsureUnixLineEndings(); + + var binaryBuffer = new byte[] { 17, 16, 0, 4, 65 }; + + + var oldBuffer = Encoding.UTF8.GetBytes(oldText); + + var changes = Diff.Compare(oldBuffer, binaryBuffer); + + Assert.True(changes.IsBinaryComparison); + + Assert.Equal(0, changes.LinesAdded); + Assert.Equal(0, changes.LinesDeleted); + } + + [Fact] + public void CanCompareABufferAgainstANullBuffer() + { + var oldText = @"1 +2 +3 +4 +5".EnsureUnixLineEndings(); + + + var oldBuffer = Encoding.UTF8.GetBytes(oldText); + + var changes = Diff.Compare(oldBuffer, null); + + Assert.Equal(0, changes.LinesAdded); + Assert.NotEqual(0, changes.LinesDeleted); + Assert.NotEqual(string.Empty, changes.Patch); + + changes = Diff.Compare(null, oldBuffer); + + Assert.NotEqual(0, changes.LinesAdded); + Assert.Equal(0, changes.LinesDeleted); + Assert.NotEqual(string.Empty, changes.Patch); + } + + [Fact] + public void CanCompareABufferAgainstAnEmptyBuffer() + { + var oldText = @"1 +2 +3 +4 +5".EnsureUnixLineEndings(); + + + var oldBuffer = Encoding.UTF8.GetBytes(oldText); + var emptyBuffer = Array.Empty(); + + var changes = Diff.Compare(oldBuffer, emptyBuffer); + + Assert.Equal(0, changes.LinesAdded); + Assert.NotEqual(0, changes.LinesDeleted); + Assert.NotEqual(string.Empty, changes.Patch); + + changes = Diff.Compare(emptyBuffer, oldBuffer); + + Assert.NotEqual(0, changes.LinesAdded); + Assert.Equal(0, changes.LinesDeleted); + Assert.NotEqual(string.Empty, changes.Patch); + } + + [Fact] + public void ComparingTwoNullBlobsReturnsAnEmptyContentChanges() + { + var changes = Diff.Compare(null, null); + Assert.False(changes.IsBinaryComparison); + + Assert.Equal(0, changes.LinesAdded); + Assert.Equal(0, changes.LinesDeleted); + } + + [Fact] + public void ComparingBuffersWithNoSpacesAndIndentHeuristicOptionMakesADifference() + { + // Based on test diff indent heuristic from: + // https://github.com/git/git/blob/433860f3d0beb0c6f205290bd16cda413148f098/t/t4061-diff-indent.sh#L17 + var oldContent = +@" 1 + 2 + a + + b + 3 + 4"; + var newContent = +@" 1 + 2 + a + + b + a + + b + 3 + 4"; + + var oldBuffer = Encoding.UTF8.GetBytes(oldContent); + var newBuffer = Encoding.UTF8.GetBytes(newContent); + + var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; + var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; + + var changes0 = Diff.Compare(oldBuffer, newBuffer, noIndentHeuristicOption); + var changes1 = Diff.Compare(oldBuffer, newBuffer, indentHeuristicOption); + + Assert.NotEqual(changes0.Patch, changes1.Patch); + Assert.Equal(CanonicalChangedLines(changes0), CanonicalChangedLines(changes1)); + } + + [Fact] + public void ComparingBlobsWithNoSpacesIndentHeuristicOptionMakesNoDifference() + { + var oldContent = +@" 1 + 2 + a + b + 3 + 4"; + var newContent = +@" 1 + 2 + a + b + a + b + 3 + 4"; + + var oldBuffer = Encoding.UTF8.GetBytes(oldContent); + var newBuffer = Encoding.UTF8.GetBytes(newContent); + + var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; + var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; + + var changes0 = Diff.Compare(oldBuffer, newBuffer, noIndentHeuristicOption); + var changes1 = Diff.Compare(oldBuffer, newBuffer, indentHeuristicOption); + + Assert.Equal(changes0.Patch, changes1.Patch); + + } + + [Fact] + public void DiffSetsTheAddedAndDeletedLinesCorrectly() + { + + var oldContent = + @"1 +2 +3 +4"; + + var newContent = + @"1 +2 +3 +5"; + + var oldBuffer = Encoding.UTF8.GetBytes(oldContent); + var newBuffer = Encoding.UTF8.GetBytes(newContent); + + ContentChanges changes = Diff.Compare(oldBuffer, newBuffer); + + Assert.Single(changes.AddedLines); + Assert.Single(changes.DeletedLines); + + Assert.Equal("4", changes.DeletedLines.First().Content); + Assert.Equal("5", changes.AddedLines.First().Content); + + Assert.Equal(4, changes.DeletedLines.First().LineNumber); + Assert.Equal(4, changes.AddedLines.First().LineNumber); + + } + + static string CanonicalChangedLines(ContentChanges changes) + { + // Create an ordered representation of lines that have been added or removed + return string.Join("\n", changes.Patch.Split('\n').Where(l => l.StartsWith("+") || l.StartsWith("-")).OrderBy(l => l)); + } + } + + public static class StringExtensionMethods + { + public static string EnsureUnixLineEndings(this string str) => str.Replace("\r", ""); } } diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 1fe148dac..b39e5a6cc 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -869,10 +869,10 @@ public static unsafe void git_diff_buffers( { res = NativeMethods.git_diff_buffers( (IntPtr)oldP, - new UIntPtr((ulong)oldBuffer.LongLength), + new UIntPtr(oldBuffer != null ? (ulong)oldBuffer.LongLength: 0), null, (IntPtr)newP, - new UIntPtr((ulong)newBuffer.LongLength), + new UIntPtr(newBuffer != null ?(ulong)newBuffer.LongLength : 0), null, options, fileCallback, diff --git a/LibGit2Sharp/Diff.cs b/LibGit2Sharp/Diff.cs index 3a3796d09..d912bd047 100644 --- a/LibGit2Sharp/Diff.cs +++ b/LibGit2Sharp/Diff.cs @@ -488,14 +488,6 @@ public virtual T Compare( return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths, explicitPathsOptions, compareOptions); } - public static ContentChanges CompareBuffers(byte[] oldBuffer, byte[] newBuffer) - { - using(var options = BuildOptions(DiffModifiers.None)) - { - return new ContentChanges(oldBuffer, newBuffer, options); - } - } - internal virtual T Compare( DiffModifiers diffOptions, IEnumerable paths = null,