Skip to content

Commit ac599f4

Browse files
authored
Merge pull request libgit2#1652 from jcansdale/fixes/git-diff-indent-heuristic
Add support for git diff --indent-heuristic option
2 parents e5fdf1b + 2f583cb commit ac599f4

File tree

4 files changed

+104
-5
lines changed

4 files changed

+104
-5
lines changed

LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs

+81
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.IO;
2+
using System.Linq;
23
using System.Text;
34
using LibGit2Sharp.Tests.TestHelpers;
45
using Xunit;
@@ -126,5 +127,85 @@ public void ComparingTwoNullBlobsReturnsAnEmptyContentChanges()
126127
Assert.Equal(0, changes.LinesDeleted);
127128
}
128129
}
130+
131+
[Fact]
132+
public void ComparingBlobsWithNoSpacesAndIndentHeuristicOptionMakesADifference()
133+
{
134+
var path = SandboxStandardTestRepoGitDir();
135+
using (var repo = new Repository(path))
136+
{
137+
// Based on test diff indent heuristic from:
138+
// https://github.com/git/git/blob/433860f3d0beb0c6f205290bd16cda413148f098/t/t4061-diff-indent.sh#L17
139+
var oldContent =
140+
@" 1
141+
2
142+
a
143+
144+
b
145+
3
146+
4";
147+
var newContent =
148+
@" 1
149+
2
150+
a
151+
152+
b
153+
a
154+
155+
b
156+
3
157+
4";
158+
var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent)));
159+
var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent)));
160+
var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false };
161+
var indentHeuristicOption = new CompareOptions { IndentHeuristic = true };
162+
163+
ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption);
164+
ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption);
165+
166+
Assert.NotEqual(changes0.Patch, changes1.Patch);
167+
Assert.Equal(CanonicalChangedLines(changes0), CanonicalChangedLines(changes1));
168+
}
169+
}
170+
171+
[Fact]
172+
public void ComparingBlobsWithNoSpacesIndentHeuristicOptionMakesNoDifference()
173+
{
174+
var path = SandboxStandardTestRepoGitDir();
175+
using (var repo = new Repository(path))
176+
{
177+
var oldContent =
178+
@" 1
179+
2
180+
a
181+
b
182+
3
183+
4";
184+
var newContent =
185+
@" 1
186+
2
187+
a
188+
b
189+
a
190+
b
191+
3
192+
4";
193+
var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent)));
194+
var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent)));
195+
var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false };
196+
var indentHeuristicOption = new CompareOptions { IndentHeuristic = true };
197+
198+
ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption);
199+
ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption);
200+
201+
Assert.Equal(changes0.Patch, changes1.Patch);
202+
}
203+
}
204+
205+
static string CanonicalChangedLines(ContentChanges changes)
206+
{
207+
// Create an ordered representation of lines that have been added or removed
208+
return string.Join("\n", changes.Patch.Split('\n').Where(l => l.StartsWith("+") || l.StartsWith("-")).OrderBy(l => l));
209+
}
129210
}
130211
}

LibGit2Sharp/CompareOptions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,11 @@ public CompareOptions()
4444
/// By default, <see cref="DiffAlgorithm.Myers"/> will be used.
4545
/// </summary>
4646
public DiffAlgorithm Algorithm { get; set; }
47+
48+
/// <summary>
49+
/// Enable --indent-heuristic Diff option, that attempts to produce more aesthetically pleasing diffs.
50+
/// By default, this option will be false.
51+
/// </summary>
52+
public bool IndentHeuristic { get; set; }
4753
}
4854
}

LibGit2Sharp/Core/GitDiff.cs

+11-4
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ internal enum GitDiffOptionFlags
133133
* Options controlling how output will be generated
134134
*/
135135

136+
/// <summary>
137+
/// Use a heuristic that takes indentation and whitespace into account
138+
/// which generally can produce better diffs when dealing with ambiguous
139+
/// diff hunks.
140+
/// </summary>
141+
GIT_DIFF_INDENT_HEURISTIC = (1 << 18),
142+
136143
/// <summary>
137144
/// Treat all files as text, disabling binary attributes and detection
138145
/// </summary>
@@ -304,11 +311,11 @@ enum GitDiffLineOrigin : byte
304311

305312
enum GitDiffFormat
306313
{
307-
GIT_DIFF_FORMAT_PATCH = 1, // < full git diff
314+
GIT_DIFF_FORMAT_PATCH = 1, // < full git diff
308315
GIT_DIFF_FORMAT_PATCH_HEADER = 2, // < just the file headers of patch
309-
GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw
310-
GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only
311-
GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status
316+
GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw
317+
GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only
318+
GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status
312319
}
313320

314321
[Flags]

LibGit2Sharp/Diff.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ private static GitDiffOptions BuildOptions(DiffModifiers diffOptions, FilePath[]
6363
options.Flags |= GitDiffOptionFlags.GIT_DIFF_DISABLE_PATHSPEC_MATCH;
6464
}
6565

66+
if (compareOptions.IndentHeuristic)
67+
{
68+
options.Flags |= GitDiffOptionFlags.GIT_DIFF_INDENT_HEURISTIC;
69+
}
70+
6671
if (matchedPathsAggregator != null)
6772
{
6873
options.NotifyCallback = matchedPathsAggregator.OnGitDiffNotify;
@@ -351,7 +356,7 @@ public virtual T Compare<T>(Tree oldTree, DiffTargets diffTargets, IEnumerable<s
351356
}
352357

353358
DiffHandle diff = BuildDiffList(oldTreeId, null, comparer, diffOptions, paths, explicitPathsOptions, compareOptions);
354-
359+
355360
try
356361
{
357362
return BuildDiffResult<T>(diff);

0 commit comments

Comments
 (0)