Skip to content

Commit 7df7b96

Browse files
committed
Prefer git_patch_print to git_diff_print
This has the nice side effect of allowing binary changes to be detected.
1 parent 7089f63 commit 7df7b96

File tree

8 files changed

+174
-30
lines changed

8 files changed

+174
-30
lines changed

LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,71 @@ public void CanCompareACommitTreeAgainstItsParent()
7373
}
7474
}
7575

76+
static void CreateBinaryFile(string path)
77+
{
78+
var content = new byte[] { 0x1, 0x0, 0x2, 0x0 };
79+
80+
using (var binfile = File.Create(path))
81+
{
82+
for (int i = 0; i < 1000; i++)
83+
{
84+
binfile.Write(content, 0, content.Length);
85+
}
86+
}
87+
}
88+
89+
[Fact]
90+
public void CanDetectABinaryChange()
91+
{
92+
using (var repo = new Repository(CloneStandardTestRepo()))
93+
{
94+
const string filename = "binfile.foo";
95+
var filepath = Path.Combine(repo.Info.WorkingDirectory, filename);
96+
97+
CreateBinaryFile(filepath);
98+
99+
repo.Index.Stage(filename);
100+
var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature);
101+
102+
File.AppendAllText(filepath, "abcdef");
103+
104+
var patch = repo.Diff.Compare<Patch>(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename });
105+
Assert.True(patch[filename].IsBinaryComparison);
106+
107+
repo.Index.Stage(filename);
108+
var commit2 = repo.Commit("Update binary file", Constants.Signature, Constants.Signature);
109+
110+
var patch2 = repo.Diff.Compare<Patch>(commit.Tree, commit2.Tree, new[] { filename });
111+
Assert.True(patch2[filename].IsBinaryComparison);
112+
}
113+
}
114+
115+
[Fact]
116+
public void CanDetectABinaryDeletion()
117+
{
118+
using (var repo = new Repository(CloneStandardTestRepo()))
119+
{
120+
const string filename = "binfile.foo";
121+
var filepath = Path.Combine(repo.Info.WorkingDirectory, filename);
122+
123+
CreateBinaryFile(filepath);
124+
125+
repo.Index.Stage(filename);
126+
var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature);
127+
128+
File.Delete(filepath);
129+
130+
var patch = repo.Diff.Compare<Patch>(commit.Tree, DiffTargets.WorkingDirectory, new [] {filename});
131+
Assert.True(patch[filename].IsBinaryComparison);
132+
133+
repo.Index.Remove(filename);
134+
var commit2 = repo.Commit("Delete binary file", Constants.Signature, Constants.Signature);
135+
136+
var patch2 = repo.Diff.Compare<Patch>(commit.Tree, commit2.Tree, new[] { filename });
137+
Assert.True(patch2[filename].IsBinaryComparison);
138+
}
139+
}
140+
76141
/*
77142
* $ git diff 9fd738e..HEAD -- "1" "2/"
78143
* diff --git a/1/branch_file.txt b/1/branch_file.txt

LibGit2Sharp/Core/GitDiff.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public void Dispose()
200200
}
201201

202202
[Flags]
203-
internal enum GitDiffFileFlags
203+
internal enum GitDiffFlags
204204
{
205205
GIT_DIFF_FLAG_BINARY = (1 << 0),
206206
GIT_DIFF_FLAG_NOT_BINARY = (1 << 1),
@@ -213,17 +213,17 @@ internal class GitDiffFile
213213
public GitOid Oid;
214214
public IntPtr Path;
215215
public Int64 Size;
216-
public GitDiffFileFlags Flags;
217-
public ushort Mode;
216+
public GitDiffFlags Flags;
217+
public UInt16 Mode;
218218
}
219219

220220
[StructLayout(LayoutKind.Sequential)]
221221
internal class GitDiffDelta
222222
{
223223
public ChangeKind Status;
224-
public uint Flags;
225-
public ushort Similarity;
226-
public ushort NumberOfFiles;
224+
public GitDiffFlags Flags;
225+
public UInt16 Similarity;
226+
public UInt16 NumberOfFiles;
227227
public GitDiffFile OldFile;
228228
public GitDiffFile NewFile;
229229
}
@@ -237,7 +237,7 @@ internal class GitDiffHunk
237237
public int NewLines;
238238
public UIntPtr HeaderLen;
239239

240-
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 128)]
240+
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
241241
public byte[] Header;
242242
}
243243

LibGit2Sharp/Core/GitDiffExtensions.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ internal static class GitDiffExtensions
66
{
77
public static bool IsBinary(this GitDiffDelta delta)
88
{
9-
//TODO Fix the interop issue on amd64 and use GitDiffDelta.Binary
10-
return delta.OldFile.Flags.HasFlag(GitDiffFileFlags.GIT_DIFF_FLAG_BINARY)
11-
|| delta.NewFile.Flags.HasFlag(GitDiffFileFlags.GIT_DIFF_FLAG_BINARY);
9+
return delta.Flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY);
1210
}
1311
}
1412
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace LibGit2Sharp.Core.Handles
2+
{
3+
internal class PatchSafeHandle : SafeHandleBase
4+
{
5+
protected override bool ReleaseHandleImpl()
6+
{
7+
Proxy.git_patch_free(handle);
8+
return true;
9+
}
10+
}
11+
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -435,19 +435,19 @@ internal static extern int git_diff_tree_to_workdir(
435435
GitDiffOptions options);
436436

437437
internal delegate int git_diff_file_cb(
438-
GitDiffDelta delta,
438+
[In] GitDiffDelta delta,
439439
float progress,
440440
IntPtr payload);
441441

442442
internal delegate int git_diff_hunk_cb(
443-
GitDiffDelta delta,
444-
GitDiffHunk hunk,
443+
[In] GitDiffDelta delta,
444+
[In] GitDiffHunk hunk,
445445
IntPtr payload);
446446

447447
internal delegate int git_diff_line_cb(
448-
GitDiffDelta delta,
449-
GitDiffHunk hunk,
450-
GitDiffLine line,
448+
[In] GitDiffDelta delta,
449+
[In] GitDiffHunk hunk,
450+
[In] GitDiffLine line,
451451
IntPtr payload);
452452

453453
[DllImport(libgit2)]
@@ -482,6 +482,12 @@ internal static extern int git_diff_find_similar(
482482
DiffSafeHandle diff,
483483
GitDiffFindOptions options);
484484

485+
[DllImport(libgit2)]
486+
internal static extern UIntPtr git_diff_num_deltas(DiffSafeHandle diff);
487+
488+
[DllImport(libgit2)]
489+
internal static extern IntPtr git_diff_get_delta(DiffSafeHandle diff, UIntPtr idx);
490+
485491
[DllImport(libgit2)]
486492
internal static extern int git_graph_ahead_behind(out UIntPtr ahead, out UIntPtr behind, RepositorySafeHandle repo, ref GitOid one, ref GitOid two);
487493

@@ -670,6 +676,15 @@ internal static extern int git_object_peel(
670676
[DllImport(libgit2)]
671677
internal static extern GitObjectType git_object_type(GitObjectSafeHandle obj);
672678

679+
[DllImport(libgit2)]
680+
internal static extern int git_patch_from_diff(out PatchSafeHandle patch, DiffSafeHandle diff, UIntPtr idx);
681+
682+
[DllImport(libgit2)]
683+
internal static extern int git_patch_print(PatchSafeHandle patch, git_diff_line_cb print_cb, IntPtr payload);
684+
685+
[DllImport(libgit2)]
686+
internal static extern void git_patch_free(IntPtr patch);
687+
673688
[DllImport(libgit2)]
674689
internal static extern int git_push_new(out PushSafeHandle push, RemoteSafeHandle remote);
675690

LibGit2Sharp/Core/Proxy.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ namespace LibGit2Sharp.Core
1313
{
1414
internal class Proxy
1515
{
16+
private static T MarshalAs<T>(IntPtr ptr)
17+
{
18+
if (ptr == IntPtr.Zero)
19+
{
20+
return default(T);
21+
}
22+
return (T)Marshal.PtrToStructure(ptr, typeof(T));
23+
}
24+
1625
#region giterr_
1726

1827
public static void giterr_set_str(GitErrorCategory error_class, Exception exception)
@@ -727,6 +736,16 @@ public static void git_diff_find_similar(DiffSafeHandle diff, GitDiffFindOptions
727736
}
728737
}
729738

739+
public static int git_diff_num_deltas(DiffSafeHandle diff)
740+
{
741+
return (int)NativeMethods.git_diff_num_deltas(diff);
742+
}
743+
744+
public static GitDiffDelta git_diff_get_delta(DiffSafeHandle diff, int idx)
745+
{
746+
return MarshalAs<GitDiffDelta>(NativeMethods.git_diff_get_delta(diff, (UIntPtr) idx));
747+
}
748+
730749
#endregion
731750

732751
#region git_graph_
@@ -1184,6 +1203,35 @@ public static void git_odb_free(IntPtr odb)
11841203

11851204
#endregion
11861205

1206+
#region git_patch_
1207+
1208+
public static void git_patch_free(IntPtr patch)
1209+
{
1210+
NativeMethods.git_patch_free(patch);
1211+
}
1212+
1213+
public static PatchSafeHandle git_patch_from_diff(DiffSafeHandle diff, int idx)
1214+
{
1215+
using (ThreadAffinity())
1216+
{
1217+
PatchSafeHandle handle;
1218+
int res = NativeMethods.git_patch_from_diff(out handle, diff, (UIntPtr) idx);
1219+
Ensure.ZeroResult(res);
1220+
return handle;
1221+
}
1222+
}
1223+
1224+
public static void git_patch_print(PatchSafeHandle patch, NativeMethods.git_diff_line_cb printCallback)
1225+
{
1226+
using (ThreadAffinity())
1227+
{
1228+
int res = NativeMethods.git_patch_print(patch, printCallback, IntPtr.Zero);
1229+
Ensure.ZeroResult(res);
1230+
}
1231+
}
1232+
1233+
#endregion
1234+
11871235
#region git_push_
11881236

11891237
public static void git_push_add_refspec(PushSafeHandle push, string pushRefSpec)

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<Compile Include="CommitFilter.cs" />
7676
<Compile Include="CommitSortStrategies.cs" />
7777
<Compile Include="CompareOptions.cs" />
78+
<Compile Include="Core\Handles\PatchSafeHandle.cs" />
7879
<Compile Include="FetchOptions.cs" />
7980
<Compile Include="RefSpec.cs" />
8081
<Compile Include="RefSpecCollection.cs" />

LibGit2Sharp/Patch.cs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,28 +32,34 @@ protected Patch()
3232

3333
internal Patch(DiffSafeHandle diff)
3434
{
35-
Proxy.git_diff_foreach(diff, FileCallback, null, null);
36-
37-
Proxy.git_diff_print(diff, PrintCallBack);
38-
}
35+
int count = Proxy.git_diff_num_deltas(diff);
36+
for (int i = 0; i < count; i++)
37+
{
38+
using (var patch = Proxy.git_patch_from_diff(diff, i))
39+
{
40+
var delta = Proxy.git_diff_get_delta(diff, i);
41+
AddFileChange(delta);
42+
Proxy.git_patch_print(patch, PrintCallBack);
43+
}
3944

40-
private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload)
41-
{
42-
AddFileChange(delta);
43-
return 0;
45+
}
4446
}
4547

4648
private void AddFileChange(GitDiffDelta delta)
4749
{
48-
var newFilePath = LaxFilePathMarshaler.FromNative(delta.NewFile.Path);
49-
50+
var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path;
51+
var newFilePath = LaxFilePathMarshaler.FromNative(pathPtr);
5052
changes.Add(newFilePath, new ContentChanges(delta.IsBinary()));
5153
}
5254

5355
private int PrintCallBack(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload)
5456
{
5557
string patchPart = LaxUtf8Marshaler.FromNative(line.content, (int)line.contentLen);
56-
var filePath = LaxFilePathMarshaler.FromNative(delta.NewFile.Path);
58+
59+
// Deleted files mean no "new file" path
60+
61+
var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path;
62+
var filePath = LaxFilePathMarshaler.FromNative(pathPtr);
5763

5864
ContentChanges currentChange = this[filePath];
5965
string prefix = string.Empty;
@@ -77,10 +83,10 @@ private int PrintCallBack(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line
7783
break;
7884
}
7985

80-
string formatedOutput = string.Concat(prefix, patchPart);
86+
string formattedOutput = string.Concat(prefix, patchPart);
8187

82-
fullPatchBuilder.Append(formatedOutput);
83-
this[filePath].AppendToPatch(formatedOutput);
88+
fullPatchBuilder.Append(formattedOutput);
89+
currentChange.AppendToPatch(formattedOutput);
8490

8591
return 0;
8692
}

0 commit comments

Comments
 (0)