diff --git a/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs b/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs index dc2552a10..4aeeebf07 100644 --- a/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs +++ b/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs @@ -1,10 +1,5 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using LibGit2Sharp.Tests.TestHelpers; +using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -40,5 +35,41 @@ public void PatchEntryBasics() } } } + + [Fact] + public void ReadPatch() + { + var path = SandboxStandardTestRepoGitDir(); + string file = "numbers.txt"; + + // The repo + using (var repo = new Repository(path)) + { + Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; + Tree commitTreeWithUpdatedFile = repo.Lookup("ec9e401").Tree; + + // Create patch by diffing + using (var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile)) + { + string patchContent = patch.Content; + + var parsedPatch = Patch.FromPatchContent(patchContent); + + Assert.Equal(patch.Content, parsedPatch.Content); + + PatchEntryChanges entryChanges = parsedPatch[file]; + Assert.Equal(2, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(187, entryChanges.Patch.Length); + // Smoke test + Assert.Equal(Mode.NonExecutableFile, entryChanges.Mode); + Assert.Equal(new ObjectId("4625a36000000000000000000000000000000000"), entryChanges.Oid); + Assert.Equal(ChangeKind.Modified, entryChanges.Status); + Assert.Equal(file, entryChanges.OldPath); + Assert.Equal(Mode.NonExecutableFile, entryChanges.OldMode); + Assert.Equal(new ObjectId("7909961000000000000000000000000000000000"), entryChanges.OldOid); + } + } + } } } diff --git a/LibGit2Sharp/Core/GitDiffApplyOpts.cs b/LibGit2Sharp/Core/GitDiffApplyOpts.cs new file mode 100644 index 000000000..1fb21edb6 --- /dev/null +++ b/LibGit2Sharp/Core/GitDiffApplyOpts.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int git_apply_delta_cb(IntPtr delta, IntPtr payload); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int git_apply_hunk_cb(IntPtr hunk, IntPtr payload); + + [StructLayout(LayoutKind.Sequential)] + internal class GitDiffApplyOpts + { + public uint Version = 1; + public git_apply_delta_cb DeltaCallback; + public git_apply_hunk_cb HunkCallback; + public IntPtr Payload; + public uint Flags = 0; + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index f5d45f3cf..e3711261e 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -176,6 +176,13 @@ internal static extern int git_error_set_str( [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_error_set_oom(); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_apply( + git_repository* repo, + git_diff* diff, + PatchApplyLocation location, + GitDiffApplyOpts options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe UInt32 git_blame_get_hunk_count(git_blame* blame); @@ -710,6 +717,12 @@ 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_from_buffer(out git_diff* diff, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] + string content, + UIntPtr len); + [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 50cefc0df..8e7702409 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -17,6 +17,21 @@ internal class Proxy internal static readonly bool isOSXArm64 = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 && RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + #region git_apply_ + + public static unsafe void git_apply( + RepositoryHandle repo, + DiffHandle diff, + PatchApplyLocation location, + GitDiffApplyOpts options) + { + int res = NativeMethods.git_apply(repo, diff, location, options ?? new GitDiffApplyOpts()); + + Ensure.ZeroResult(res); + } + + #endregion + #region git_blame_ public static unsafe BlameHandle git_blame_file( @@ -854,6 +869,13 @@ public static unsafe int git_diff_num_deltas(DiffHandle diff) return NativeMethods.git_diff_get_delta(diff, (UIntPtr)idx); } + public static unsafe DiffHandle git_diff_from_buffer(string content, UIntPtr len) + { + int res = NativeMethods.git_diff_from_buffer(out var diff, content, len); + Ensure.ZeroResult(res); + return new DiffHandle(diff, true); + } + #endregion #region git_error_ diff --git a/LibGit2Sharp/DeltaApplyStatus.cs b/LibGit2Sharp/DeltaApplyStatus.cs new file mode 100644 index 000000000..665431cfe --- /dev/null +++ b/LibGit2Sharp/DeltaApplyStatus.cs @@ -0,0 +1,23 @@ +namespace LibGit2Sharp +{ + /// + /// What to do with a delta. + /// + public enum DeltaApplyStatus + { + /// + /// Apply the delta. + /// + Apply, + + /// + /// Do not apply the delta and abort. + /// + Abort, + + /// + /// Do not apply the delta and continue. + /// + Skip + } +} diff --git a/LibGit2Sharp/Patch.cs b/LibGit2Sharp/Patch.cs index 50157eb32..e9876eec4 100644 --- a/LibGit2Sharp/Patch.cs +++ b/LibGit2Sharp/Patch.cs @@ -47,6 +47,16 @@ internal unsafe Patch(DiffHandle diff) } } + /// + /// Instantiate a patch from its content. + /// + /// The patch content + /// The Patch instance + public static Patch FromPatchContent(string content) + { + return new Patch(Proxy.git_diff_from_buffer(content, (UIntPtr)content.Length)); + } + private unsafe void AddFileChange(git_diff_delta* delta) { var treeEntryChanges = new TreeEntryChanges(delta); diff --git a/LibGit2Sharp/PatchApplyOptions.cs b/LibGit2Sharp/PatchApplyOptions.cs new file mode 100644 index 000000000..27b98a25f --- /dev/null +++ b/LibGit2Sharp/PatchApplyOptions.cs @@ -0,0 +1,37 @@ +namespace LibGit2Sharp +{ + /// + /// The options to be used for patch application. + /// + public sealed class PatchApplyOptions + { + /// + /// The location to apply (workdir, index or both) + /// + public PatchApplyLocation Location { get; set; } + } + + /// + /// Possible application locations for applying a patch. + /// + public enum PatchApplyLocation + { + /// + /// Apply the patch to the workdir, leaving the index untouched. + /// This is the equivalent of `git apply` with no location argument. + /// + Workdir = 0, + + /// + /// Apply the patch to the index, leaving the working directory + /// untouched. This is the equivalent of `git apply --cached`. + /// + Index = 1, + + /// + /// Apply the patch to both the working directory and the index. + /// This is the equivalent of `git apply --index`. + /// + Both = 2 + } +} diff --git a/LibGit2Sharp/RepositoryExtensions.cs b/LibGit2Sharp/RepositoryExtensions.cs index edeb6b93f..92540573d 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -470,5 +471,37 @@ public static string Describe(this IRepository repository, Commit commit) { return repository.Describe(commit, new DescribeOptions()); } + + /// + /// Applies a patch to a repository. + /// + /// The being worked with. + /// The diff to be applied. + /// The options to use. + public static void ApplyPatch(this Repository repository, string patchContent, PatchApplyOptions applyOptions = null) + { + if (applyOptions == null) + { + applyOptions = new PatchApplyOptions(); + } + + using (var diff = Proxy.git_diff_from_buffer(patchContent, (UIntPtr) patchContent.Length)) + { + Proxy.git_apply(repository.Handle, diff, applyOptions.Location, null); + } + } + + /// + /// Applies a patch file to a repository. + /// + /// The being worked with. + /// The path to the patch file + /// The options to use. + public static void ApplyPatchFile(this Repository repository, string patchFilePath, PatchApplyOptions options = null) + { + string content = File.ReadAllText(patchFilePath); + + ApplyPatch(repository, content, options); + } } }