diff --git a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs
index 00ef0ab2b..fea0bbb74 100644
--- a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs
+++ b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Linq;
using System.Text;
using LibGit2Sharp.Tests.TestHelpers;
using Xunit;
@@ -126,5 +127,85 @@ public void ComparingTwoNullBlobsReturnsAnEmptyContentChanges()
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);
+ }
+ }
+
+ 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/CompareOptions.cs b/LibGit2Sharp/CompareOptions.cs
index fbd147c79..fb4234439 100644
--- a/LibGit2Sharp/CompareOptions.cs
+++ b/LibGit2Sharp/CompareOptions.cs
@@ -44,5 +44,11 @@ public CompareOptions()
/// By default, will be used.
///
public DiffAlgorithm Algorithm { get; set; }
+
+ ///
+ /// Enable --indent-heuristic Diff option, that attempts to produce more aesthetically pleasing diffs.
+ /// By default, this option will be false.
+ ///
+ public bool IndentHeuristic { get; set; }
}
}
diff --git a/LibGit2Sharp/Core/GitDiff.cs b/LibGit2Sharp/Core/GitDiff.cs
index eb21d6881..956bc89ab 100644
--- a/LibGit2Sharp/Core/GitDiff.cs
+++ b/LibGit2Sharp/Core/GitDiff.cs
@@ -133,6 +133,13 @@ internal enum GitDiffOptionFlags
* Options controlling how output will be generated
*/
+ ///
+ /// Use a heuristic that takes indentation and whitespace into account
+ /// which generally can produce better diffs when dealing with ambiguous
+ /// diff hunks.
+ ///
+ GIT_DIFF_INDENT_HEURISTIC = (1 << 18),
+
///
/// Treat all files as text, disabling binary attributes and detection
///
@@ -304,11 +311,11 @@ enum GitDiffLineOrigin : byte
enum GitDiffFormat
{
- GIT_DIFF_FORMAT_PATCH = 1, // < full git diff
+ GIT_DIFF_FORMAT_PATCH = 1, // < full git diff
GIT_DIFF_FORMAT_PATCH_HEADER = 2, // < just the file headers of patch
- GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw
- GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only
- GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status
+ GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw
+ GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only
+ GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status
}
[Flags]
diff --git a/LibGit2Sharp/Diff.cs b/LibGit2Sharp/Diff.cs
index 9bf14660c..087ee8d6d 100644
--- a/LibGit2Sharp/Diff.cs
+++ b/LibGit2Sharp/Diff.cs
@@ -63,6 +63,11 @@ private static GitDiffOptions BuildOptions(DiffModifiers diffOptions, FilePath[]
options.Flags |= GitDiffOptionFlags.GIT_DIFF_DISABLE_PATHSPEC_MATCH;
}
+ if (compareOptions.IndentHeuristic)
+ {
+ options.Flags |= GitDiffOptionFlags.GIT_DIFF_INDENT_HEURISTIC;
+ }
+
if (matchedPathsAggregator != null)
{
options.NotifyCallback = matchedPathsAggregator.OnGitDiffNotify;
@@ -351,7 +356,7 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable(diff);