diff --git a/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs b/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs
new file mode 100644
index 000000000..e172b6631
--- /dev/null
+++ b/LibGit2Sharp.Tests/DiffBufferToBufferFixture.cs
@@ -0,0 +1,332 @@
+using System;
+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.Compare(buffer, buffer);
+
+            Assert.Equal(0, changes.LinesAdded);
+            Assert.Equal(0, changes.LinesDeleted);
+            Assert.Equal(string.Empty, changes.Patch);
+        }
+
+        [Fact]
+        public void CanCompareTwoBuffersWithADiffOfOneHunk()
+        {
+            var oldText = @"1
+2
+4
+5
+6
+8
+7".EnsureUnixLineEndings();
+
+            var newText = @"1
+2
+3
+4
+5
+6
+7
+8
+9".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(3, changes.LinesAdded);
+            Assert.Equal(1, changes.LinesDeleted);
+
+            var expected = new StringBuilder()
+                .Append("@@ -1,7 +1,9 @@\n")
+                .Append(" 1\n")
+                .Append(" 2\n")
+                .Append("+3\n")
+                .Append(" 4\n")
+                .Append(" 5\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("-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 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<byte>();
+
+            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/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..b39e5a6cc 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(oldBuffer != null ? (ulong)oldBuffer.LongLength: 0),
+                        null,
+                        (IntPtr)newP,
+                        new UIntPtr(newBuffer != null ?(ulong)newBuffer.LongLength : 0),
+                        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..d912bd047 100644
--- a/LibGit2Sharp/Diff.cs
+++ b/LibGit2Sharp/Diff.cs
@@ -124,6 +124,32 @@ private static T BuildDiffResult<T>(DiffHandle diff) where T : class, IDiffResul
             return (T)builder(diff);
         }
 
+        /// <summary>
+        /// Show changes between two arbitrary <see cref="T:byte[]"/> buffers.
+        /// </summary>
+        /// <param name="oldBuffer">The <see cref="T:byte[]"/> buffer you want to compare from.</param>
+        /// <param name="newBuffer">The <see cref="T:byte[]"/> buffer you want to compare to.</param>
+        /// <returns>A <see cref="ContentChanges"/> containing the changes between the <paramref name="oldBuffer"/> and the <paramref name="newBuffer"/>.</returns>
+        public static ContentChanges Compare(byte[] oldBuffer, byte[] newBuffer)
+        {
+            return Compare(oldBuffer, newBuffer, null);
+        }
+
+        /// <summary>
+        /// Show changes between two arbitrary <see cref="T:byte[]"/> buffers.
+        /// </summary>
+        /// <param name="oldBuffer">The <see cref="T:byte[]"/> buffer you want to compare from.</param>
+        /// <param name="newBuffer">The <see cref="T:byte[]"/> buffer you want to compare to.</param>
+        /// <param name="compareOptions">Additional options to define comparison behavior.</param>
+        /// <returns>A <see cref="ContentChanges"/> containing the changes between the <paramref name="oldBuffer"/> and the <paramref name="newBuffer"/>.</returns>
+        public static ContentChanges Compare(byte[] oldBuffer, byte[] newBuffer, CompareOptions compareOptions)
+        {
+            using(var options = BuildOptions(DiffModifiers.None, compareOptions: compareOptions))
+            {
+                return new ContentChanges(oldBuffer, newBuffer, options);
+            }
+        }
+
         /// <summary>
         /// Show changes between two <see cref="Blob"/>s.
         /// </summary>