From 2ff661ac2ff4fe9d0b6c68e3acb5942cfa50130a Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 24 Dec 2013 10:13:04 +0100 Subject: [PATCH 01/16] Drop obsolete members after release v0.15.0 --- LibGit2Sharp/Blob.cs | 24 ------------ LibGit2Sharp/BlobExtensions.cs | 30 -------------- LibGit2Sharp/IRepository.cs | 9 ----- LibGit2Sharp/LibGit2Sharp.csproj | 1 - LibGit2Sharp/Network.cs | 40 ------------------- LibGit2Sharp/ObjectId.cs | 48 ----------------------- LibGit2Sharp/Repository.cs | 12 ------ LibGit2Sharp/RepositoryExtensions.cs | 58 ---------------------------- LibGit2Sharp/ResetOptions.cs | 29 -------------- 9 files changed, 251 deletions(-) delete mode 100644 LibGit2Sharp/ResetOptions.cs diff --git a/LibGit2Sharp/Blob.cs b/LibGit2Sharp/Blob.cs index 88c5f3b10..bf1a72ac2 100644 --- a/LibGit2Sharp/Blob.cs +++ b/LibGit2Sharp/Blob.cs @@ -35,18 +35,6 @@ internal Blob(Repository repo, ObjectId id) /// public virtual bool IsBinary { get { return lazyIsBinary.Value; } } - /// - /// Gets the blob content in a array. - /// - [Obsolete("This property will be removed in the next release. Please use one of the GetContentStream() overloads instead.")] - public virtual byte[] Content - { - get - { - return Proxy.git_blob_rawcontent(repo.Handle, Id, Size); - } - } - /// /// Gets the blob content in a . /// @@ -65,17 +53,5 @@ public virtual Stream GetContentStream(FilteringOptions filteringOptions) Ensure.ArgumentNotNull(filteringOptions, "filteringOptions"); return Proxy.git_blob_filtered_content_stream(repo.Handle, Id, filteringOptions.HintPath, false); } - - /// - /// Gets the blob content in a . - /// - [Obsolete("This property will be removed in the next release. Please use one of the GetContentStream() overloads instead.")] - public virtual Stream ContentStream - { - get - { - return GetContentStream(); - } - } } } diff --git a/LibGit2Sharp/BlobExtensions.cs b/LibGit2Sharp/BlobExtensions.cs index 138cbdf9c..c9e15bf4c 100644 --- a/LibGit2Sharp/BlobExtensions.cs +++ b/LibGit2Sharp/BlobExtensions.cs @@ -10,36 +10,6 @@ namespace LibGit2Sharp /// public static class BlobExtensions { - /// - /// Gets the blob content decoded with the specified encoding, - /// or according to byte order marks, with UTF8 as fallback, - /// if is null. - /// - /// The blob for which the content will be returned. - /// The encoding of the text. (default: detected or UTF8) - /// Blob content as text. - [Obsolete("This method will be removed in the next release. Please use one of the GetContentText() overloads instead.")] - public static string ContentAsText(this Blob blob, Encoding encoding = null) - { - return GetContentText(blob, encoding); - } - - /// - /// Gets the blob content as it would be checked out to the - /// working directory, decoded with the specified encoding, - /// or according to byte order marks, with UTF8 as fallback, - /// if is null. - /// - /// The blob for which the content will be returned. - /// Parameter controlling content filtering behavior - /// The encoding of the text. (default: detected or UTF8) - /// Blob content as text. - [Obsolete("This method will be removed in the next release. Please use one of the GetContentText() overloads instead.")] - public static string ContentAsText(this Blob blob, FilteringOptions filteringOptions, Encoding encoding = null) - { - return GetContentText(blob, filteringOptions, encoding); - } - /// /// Gets the blob content decoded with the specified encoding, /// or according to byte order marks, with UTF8 as fallback, diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index 557547e12..9c3c9f31d 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -164,15 +164,6 @@ public interface IRepository : IDisposable /// The generated . Commit Commit(string message, Signature author, Signature committer, bool amendPreviousCommit = false); - /// - /// Sets the current to the specified commit and optionally resets the and - /// the content of the working tree to match. - /// - /// Flavor of reset operation to perform. - /// The target commit object. - [Obsolete("This method will be removed in the next release. Please use Reset(ResetMode, Commit) instead.")] - void Reset(ResetOptions resetOptions, Commit commit); - /// /// Sets the current to the specified commit and optionally resets the and /// the content of the working tree to match. diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 07e07b454..bb3948cac 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -280,7 +280,6 @@ - diff --git a/LibGit2Sharp/Network.cs b/LibGit2Sharp/Network.cs index a94c01e43..9c56cdf47 100644 --- a/LibGit2Sharp/Network.cs +++ b/LibGit2Sharp/Network.cs @@ -133,46 +133,6 @@ static void DoFetch(RemoteSafeHandle remoteHandle, FetchOptions options) } } - /// - /// Fetch from the . - /// - /// The remote to fetch - /// Optional parameter indicating what tags to download. - /// Progress callback. Corresponds to libgit2 progress callback. - /// UpdateTips callback. Corresponds to libgit2 update_tips callback. - /// Callback method that transfer progress will be reported through. - /// Reports the client's state regarding the received and processed (bytes, objects) from the server. - /// Credentials to use for username/password authentication. - [Obsolete("This overload will be removed in the next release. Please use Fetch(Remote, FetchOptions) instead.")] - public virtual void Fetch( - Remote remote, - TagFetchMode? tagFetchMode = null, - ProgressHandler onProgress = null, - UpdateTipsHandler onUpdateTips = null, - TransferProgressHandler onTransferProgress = null, - Credentials credentials = null) - { - Fetch(remote, new FetchOptions - { - TagFetchMode = tagFetchMode, - OnProgress = onProgress, - OnUpdateTips = onUpdateTips, - OnTransferProgress = onTransferProgress, - Credentials = credentials - }); - } - - /// - /// Fetch from the . - /// - /// The remote to fetch - public virtual void Fetch(Remote remote) - { - // This overload is required as long as the obsolete overload exists. - // Otherwise, Fetch(Remote) is ambiguous. - Fetch(remote, (FetchOptions)null); - } - /// /// Fetch from the . /// diff --git a/LibGit2Sharp/ObjectId.cs b/LibGit2Sharp/ObjectId.cs index 00454347a..0d7b845dd 100644 --- a/LibGit2Sharp/ObjectId.cs +++ b/LibGit2Sharp/ObjectId.cs @@ -305,54 +305,6 @@ private static bool LooksValid(string objectId, bool throwIfInvalid) return objectId.All(c => hexDigits.Contains(c.ToString(CultureInfo.InvariantCulture))); } - /// - /// Determine whether the beginning of this instance matches the - /// first nibbles of . - /// - /// The byte array to compare the against. - /// The number of nibbles from - /// - [Obsolete("This method will be removed in the next release. Please use one of the StartsWith(string) overload instead.")] - public bool StartsWith(byte[] rawId, int len) - { - Ensure.ArgumentNotNull(rawId, "rawId"); - - if (len < 1 || len > HexSize) - { - throw new ArgumentOutOfRangeException("len"); - } - - if (len > rawId.Length * 2) - { - throw new ArgumentOutOfRangeException("len", "len exceeds the size of rawId"); - } - - bool match = true; - - int length = len >> 1; - for (int i = 0; i < length; i++) - { - if (RawId[i] != rawId[i]) - { - match = false; - break; - } - } - - if (match && ((len & 1) == 1)) - { - var a = RawId[length] >> 4; - var b = rawId[length] >> 4; - - if (a != b) - { - match = false; - } - } - - return match; - } - /// /// Determine whether matches the hexified /// representation of the first nibbles of this instance. diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index b3df311fc..33f9d795e 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -795,18 +795,6 @@ private void CheckoutTree( } } - /// - /// Sets the current to the specified commit and optionally resets the and - /// the content of the working tree to match. - /// - /// Flavor of reset operation to perform. - /// The target commit object. - [Obsolete("This method will be removed in the next release. Please use Reset(ResetMode, Commit) instead.")] - public void Reset(ResetOptions resetOptions, Commit commit) - { - Reset((ResetMode)resetOptions, commit); - } - /// /// Sets the current to the specified commit and optionally resets the and /// the content of the working tree to match. diff --git a/LibGit2Sharp/RepositoryExtensions.cs b/LibGit2Sharp/RepositoryExtensions.cs index 57688121f..754b13740 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -140,19 +140,6 @@ public static Branch CreateBranch(this IRepository repository, string branchName return repository.Branches.Add(branchName, committish); } - /// - /// Sets the current to the specified commit and optionally resets the and - /// the content of the working tree to match. - /// - /// The being worked with. - /// Flavor of reset operation to perform. - /// A revparse spec for the target commit object. - [Obsolete("This method will be removed in the next release. Please use Reset(this IRepository, ResetMode, string) instead.")] - public static void Reset(this IRepository repository, ResetOptions resetOptions, string committish = "HEAD") - { - repository.Reset((ResetMode) resetOptions, committish); - } - /// /// Sets the current to the specified commit and optionally resets the and /// the content of the working tree to match. @@ -235,51 +222,6 @@ public static Commit Commit(this IRepository repository, string message, Signatu return repository.Commit(message, author, committer, amendPreviousCommit); } - /// - /// Fetch from the specified remote. - /// - /// The being worked with. - /// The name of the to fetch from. - /// Optional parameter indicating what tags to download. - /// Progress callback. Corresponds to libgit2 progress callback. - /// UpdateTips callback. Corresponds to libgit2 update_tips callback. - /// Callback method that transfer progress will be reported through. - /// Reports the client's state regarding the received and processed (bytes, objects) from the server. - /// Credentials to use for username/password authentication. - [Obsolete("This overload will be removed in the next release. Please use Fetch(Remote, FetchOptions) instead.")] - public static void Fetch(this IRepository repository, string remoteName, - TagFetchMode tagFetchMode = TagFetchMode.Auto, - ProgressHandler onProgress = null, - UpdateTipsHandler onUpdateTips = null, - TransferProgressHandler onTransferProgress = null, - Credentials credentials = null) - { - Ensure.ArgumentNotNull(repository, "repository"); - Ensure.ArgumentNotNullOrEmptyString(remoteName, "remoteName"); - - Remote remote = repository.Network.Remotes.RemoteForName(remoteName, true); - repository.Network.Fetch(remote, new FetchOptions - { - TagFetchMode = tagFetchMode, - OnProgress = onProgress, - OnUpdateTips = onUpdateTips, - OnTransferProgress = onTransferProgress, - Credentials = credentials - }); - } - - /// - /// Fetch from the specified remote. - /// - /// The being worked with. - /// The name of the to fetch from. - public static void Fetch(this IRepository repository, string remoteName) - { - // This overload is required as long as the obsolete overload exists. - // Otherwise, Fetch(string) is ambiguous. - Fetch(repository, remoteName, (FetchOptions)null); - } - /// /// Fetch from the specified remote. /// diff --git a/LibGit2Sharp/ResetOptions.cs b/LibGit2Sharp/ResetOptions.cs deleted file mode 100644 index d294adb17..000000000 --- a/LibGit2Sharp/ResetOptions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace LibGit2Sharp -{ - /// - /// Specifies the kind of operation that should perform. - /// - [Obsolete("This enumeration will be removed in the next release. Please use ResetMode instead.")] - public enum ResetOptions - { - /// - /// Moves the branch pointed to by HEAD to the specified commit object. - /// - Soft = 1, - - /// - /// Moves the branch pointed to by HEAD to the specified commit object and resets the index - /// to the tree recorded by the commit. - /// - Mixed, - - /// - /// Moves the branch pointed to by HEAD to the specified commit object, resets the index - /// to the tree recorded by the commit and updates the working directory to match the content - /// of the index. - /// - Hard, - } -} From c7b1a993d6e852292a41dcf283348496b9f0d024 Mon Sep 17 00:00:00 2001 From: yorah Date: Mon, 6 Jan 2014 12:34:26 +0100 Subject: [PATCH 02/16] Skip failling tests due to recent github handling modification of include-tag Github recently (25th of december, 2013) changed its handling of the include-tag option. Skipping tests while waiting for a proper solution to be found. Same thing as libgit2/libgit2#2020 --- LibGit2Sharp.Tests/FetchFixture.cs | 4 ++-- LibGit2Sharp.Tests/RepositoryFixture.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LibGit2Sharp.Tests/FetchFixture.cs b/LibGit2Sharp.Tests/FetchFixture.cs index 3e340bcdd..dab632654 100644 --- a/LibGit2Sharp.Tests/FetchFixture.cs +++ b/LibGit2Sharp.Tests/FetchFixture.cs @@ -10,7 +10,7 @@ public class FetchFixture : BaseFixture { private const string remoteName = "testRemote"; - [Theory] + [Theory(Skip = "Skipping due to recent github handling modification of --include-tag.")] [InlineData("http://github.com/libgit2/TestGitRepository")] [InlineData("https://github.com/libgit2/TestGitRepository")] [InlineData("git://github.com/libgit2/TestGitRepository.git")] @@ -138,7 +138,7 @@ public void CanFetchCustomRefSpecsIntoAnEmptyRepository(string url, string local } } - [Theory] + [Theory(Skip = "Skipping due to recent github handling modification of --include-tag.")] [InlineData(TagFetchMode.All, 4)] [InlineData(TagFetchMode.None, 0)] [InlineData(TagFetchMode.Auto, 3)] diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index bc5656acf..f493cf7ca 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -149,7 +149,7 @@ private static void AssertIsHidden(string repoPath) Assert.Equal(FileAttributes.Hidden, (attribs & FileAttributes.Hidden)); } - [Fact] + [Fact(Skip = "Skipping due to recent github handling modification of --include-tag.")] public void CanFetchFromRemoteByName() { string remoteName = "testRemote"; From 7089f6372434aa58b04c784dce0556a55e2ea2e2 Mon Sep 17 00:00:00 2001 From: Aimeast Date: Tue, 7 Jan 2014 00:33:17 +0800 Subject: [PATCH 03/16] Delegate Commit.MessageShort to libgit2 (#593) --- LibGit2Sharp/Commit.cs | 16 +++------------- LibGit2Sharp/Core/NativeMethods.cs | 4 ++++ LibGit2Sharp/Core/Proxy.cs | 5 +++++ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/LibGit2Sharp/Commit.cs b/LibGit2Sharp/Commit.cs index 9a2b755ab..8b380ae80 100644 --- a/LibGit2Sharp/Commit.cs +++ b/LibGit2Sharp/Commit.cs @@ -21,10 +21,10 @@ public class Commit : GitObject private readonly ILazy lazyAuthor; private readonly ILazy lazyCommitter; private readonly ILazy lazyMessage; + private readonly ILazy lazyMessageShort; private readonly ILazy lazyEncoding; private readonly ParentsCollection parents; - private readonly Lazy lazyShortMessage; private readonly Lazy> lazyNotes; /// @@ -42,9 +42,9 @@ internal Commit(Repository repo, ObjectId id) lazyAuthor = group.AddLazy(Proxy.git_commit_author); lazyCommitter = group.AddLazy(Proxy.git_commit_committer); lazyMessage = group.AddLazy(Proxy.git_commit_message); + lazyMessageShort = group.AddLazy(Proxy.git_commit_summary); lazyEncoding = group.AddLazy(RetrieveEncodingOf); - lazyShortMessage = new Lazy(ExtractShortMessage); lazyNotes = new Lazy>(() => RetrieveNotesOfCommit(id).ToList()); parents = new ParentsCollection(repo, id); @@ -68,7 +68,7 @@ public virtual TreeEntry this[string relativePath] /// /// Gets the short commit message which is usually the first line of the commit. /// - public virtual string MessageShort { get { return lazyShortMessage.Value; } } + public virtual string MessageShort { get { return lazyMessageShort.Value; } } /// /// Gets the encoding of the message. @@ -100,16 +100,6 @@ public virtual TreeEntry this[string relativePath] /// public virtual IEnumerable Notes { get { return lazyNotes.Value; } } - private string ExtractShortMessage() - { - if (Message == null) - { - return string.Empty; //TODO: Add some test coverage - } - - return Message.Split('\n')[0]; - } - private IEnumerable RetrieveNotesOfCommit(ObjectId oid) { return repo.Notes[oid]; diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 40b7d3459..6f1641760 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -261,6 +261,10 @@ internal static extern int git_commit_create_from_oids( [return : MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_commit_message(GitObjectSafeHandle commit); + [DllImport(libgit2)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern string git_commit_summary(GitObjectSafeHandle commit); + [DllImport(libgit2)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_commit_message_encoding(GitObjectSafeHandle commit); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 18cc88b95..5ddb63490 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -347,6 +347,11 @@ public static string git_commit_message(GitObjectSafeHandle obj) return NativeMethods.git_commit_message(obj); } + public static string git_commit_summary(GitObjectSafeHandle obj) + { + return NativeMethods.git_commit_summary(obj); + } + public static string git_commit_message_encoding(GitObjectSafeHandle obj) { return NativeMethods.git_commit_message_encoding(obj); From 7df7b96a7ace321914fd9048deca61bf983ba1d3 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 18 Dec 2013 15:27:43 -0800 Subject: [PATCH 04/16] Prefer git_patch_print to git_diff_print This has the nice side effect of allowing binary changes to be detected. --- LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs | 65 ++++++++++++++++++++ LibGit2Sharp/Core/GitDiff.cs | 14 ++--- LibGit2Sharp/Core/GitDiffExtensions.cs | 4 +- LibGit2Sharp/Core/Handles/PatchSafeHandle.cs | 11 ++++ LibGit2Sharp/Core/NativeMethods.cs | 27 ++++++-- LibGit2Sharp/Core/Proxy.cs | 48 +++++++++++++++ LibGit2Sharp/LibGit2Sharp.csproj | 1 + LibGit2Sharp/Patch.cs | 34 +++++----- 8 files changed, 174 insertions(+), 30 deletions(-) create mode 100644 LibGit2Sharp/Core/Handles/PatchSafeHandle.cs diff --git a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs index 5ced715e0..c1b0bcce6 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs @@ -73,6 +73,71 @@ public void CanCompareACommitTreeAgainstItsParent() } } + static void CreateBinaryFile(string path) + { + var content = new byte[] { 0x1, 0x0, 0x2, 0x0 }; + + using (var binfile = File.Create(path)) + { + for (int i = 0; i < 1000; i++) + { + binfile.Write(content, 0, content.Length); + } + } + } + + [Fact] + public void CanDetectABinaryChange() + { + using (var repo = new Repository(CloneStandardTestRepo())) + { + const string filename = "binfile.foo"; + var filepath = Path.Combine(repo.Info.WorkingDirectory, filename); + + CreateBinaryFile(filepath); + + repo.Index.Stage(filename); + var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); + + File.AppendAllText(filepath, "abcdef"); + + var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename }); + Assert.True(patch[filename].IsBinaryComparison); + + repo.Index.Stage(filename); + var commit2 = repo.Commit("Update binary file", Constants.Signature, Constants.Signature); + + var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename }); + Assert.True(patch2[filename].IsBinaryComparison); + } + } + + [Fact] + public void CanDetectABinaryDeletion() + { + using (var repo = new Repository(CloneStandardTestRepo())) + { + const string filename = "binfile.foo"; + var filepath = Path.Combine(repo.Info.WorkingDirectory, filename); + + CreateBinaryFile(filepath); + + repo.Index.Stage(filename); + var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); + + File.Delete(filepath); + + var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new [] {filename}); + Assert.True(patch[filename].IsBinaryComparison); + + repo.Index.Remove(filename); + var commit2 = repo.Commit("Delete binary file", Constants.Signature, Constants.Signature); + + var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename }); + Assert.True(patch2[filename].IsBinaryComparison); + } + } + /* * $ git diff 9fd738e..HEAD -- "1" "2/" * diff --git a/1/branch_file.txt b/1/branch_file.txt diff --git a/LibGit2Sharp/Core/GitDiff.cs b/LibGit2Sharp/Core/GitDiff.cs index d0d9bdd9b..33ebcce77 100644 --- a/LibGit2Sharp/Core/GitDiff.cs +++ b/LibGit2Sharp/Core/GitDiff.cs @@ -200,7 +200,7 @@ public void Dispose() } [Flags] - internal enum GitDiffFileFlags + internal enum GitDiffFlags { GIT_DIFF_FLAG_BINARY = (1 << 0), GIT_DIFF_FLAG_NOT_BINARY = (1 << 1), @@ -213,17 +213,17 @@ internal class GitDiffFile public GitOid Oid; public IntPtr Path; public Int64 Size; - public GitDiffFileFlags Flags; - public ushort Mode; + public GitDiffFlags Flags; + public UInt16 Mode; } [StructLayout(LayoutKind.Sequential)] internal class GitDiffDelta { public ChangeKind Status; - public uint Flags; - public ushort Similarity; - public ushort NumberOfFiles; + public GitDiffFlags Flags; + public UInt16 Similarity; + public UInt16 NumberOfFiles; public GitDiffFile OldFile; public GitDiffFile NewFile; } @@ -237,7 +237,7 @@ internal class GitDiffHunk public int NewLines; public UIntPtr HeaderLen; - [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 128)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] Header; } diff --git a/LibGit2Sharp/Core/GitDiffExtensions.cs b/LibGit2Sharp/Core/GitDiffExtensions.cs index ae2afaf95..a885901a5 100644 --- a/LibGit2Sharp/Core/GitDiffExtensions.cs +++ b/LibGit2Sharp/Core/GitDiffExtensions.cs @@ -6,9 +6,7 @@ internal static class GitDiffExtensions { public static bool IsBinary(this GitDiffDelta delta) { - //TODO Fix the interop issue on amd64 and use GitDiffDelta.Binary - return delta.OldFile.Flags.HasFlag(GitDiffFileFlags.GIT_DIFF_FLAG_BINARY) - || delta.NewFile.Flags.HasFlag(GitDiffFileFlags.GIT_DIFF_FLAG_BINARY); + return delta.Flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY); } } } diff --git a/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs b/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs new file mode 100644 index 000000000..97c0dc9bb --- /dev/null +++ b/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs @@ -0,0 +1,11 @@ +namespace LibGit2Sharp.Core.Handles +{ + internal class PatchSafeHandle : SafeHandleBase + { + protected override bool ReleaseHandleImpl() + { + Proxy.git_patch_free(handle); + return true; + } + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 6f1641760..6b1accb2b 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -435,19 +435,19 @@ internal static extern int git_diff_tree_to_workdir( GitDiffOptions options); internal delegate int git_diff_file_cb( - GitDiffDelta delta, + [In] GitDiffDelta delta, float progress, IntPtr payload); internal delegate int git_diff_hunk_cb( - GitDiffDelta delta, - GitDiffHunk hunk, + [In] GitDiffDelta delta, + [In] GitDiffHunk hunk, IntPtr payload); internal delegate int git_diff_line_cb( - GitDiffDelta delta, - GitDiffHunk hunk, - GitDiffLine line, + [In] GitDiffDelta delta, + [In] GitDiffHunk hunk, + [In] GitDiffLine line, IntPtr payload); [DllImport(libgit2)] @@ -482,6 +482,12 @@ internal static extern int git_diff_find_similar( DiffSafeHandle diff, GitDiffFindOptions options); + [DllImport(libgit2)] + internal static extern UIntPtr git_diff_num_deltas(DiffSafeHandle diff); + + [DllImport(libgit2)] + internal static extern IntPtr git_diff_get_delta(DiffSafeHandle diff, UIntPtr idx); + [DllImport(libgit2)] internal static extern int git_graph_ahead_behind(out UIntPtr ahead, out UIntPtr behind, RepositorySafeHandle repo, ref GitOid one, ref GitOid two); @@ -670,6 +676,15 @@ internal static extern int git_object_peel( [DllImport(libgit2)] internal static extern GitObjectType git_object_type(GitObjectSafeHandle obj); + [DllImport(libgit2)] + internal static extern int git_patch_from_diff(out PatchSafeHandle patch, DiffSafeHandle diff, UIntPtr idx); + + [DllImport(libgit2)] + internal static extern int git_patch_print(PatchSafeHandle patch, git_diff_line_cb print_cb, IntPtr payload); + + [DllImport(libgit2)] + internal static extern void git_patch_free(IntPtr patch); + [DllImport(libgit2)] internal static extern int git_push_new(out PushSafeHandle push, RemoteSafeHandle remote); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 5ddb63490..f16982fdd 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -13,6 +13,15 @@ namespace LibGit2Sharp.Core { internal class Proxy { + private static T MarshalAs(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + { + return default(T); + } + return (T)Marshal.PtrToStructure(ptr, typeof(T)); + } + #region giterr_ 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 } } + public static int git_diff_num_deltas(DiffSafeHandle diff) + { + return (int)NativeMethods.git_diff_num_deltas(diff); + } + + public static GitDiffDelta git_diff_get_delta(DiffSafeHandle diff, int idx) + { + return MarshalAs(NativeMethods.git_diff_get_delta(diff, (UIntPtr) idx)); + } + #endregion #region git_graph_ @@ -1184,6 +1203,35 @@ public static void git_odb_free(IntPtr odb) #endregion + #region git_patch_ + + public static void git_patch_free(IntPtr patch) + { + NativeMethods.git_patch_free(patch); + } + + public static PatchSafeHandle git_patch_from_diff(DiffSafeHandle diff, int idx) + { + using (ThreadAffinity()) + { + PatchSafeHandle handle; + int res = NativeMethods.git_patch_from_diff(out handle, diff, (UIntPtr) idx); + Ensure.ZeroResult(res); + return handle; + } + } + + public static void git_patch_print(PatchSafeHandle patch, NativeMethods.git_diff_line_cb printCallback) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_patch_print(patch, printCallback, IntPtr.Zero); + Ensure.ZeroResult(res); + } + } + + #endregion + #region git_push_ public static void git_push_add_refspec(PushSafeHandle push, string pushRefSpec) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index bb3948cac..92d4513b1 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -75,6 +75,7 @@ + diff --git a/LibGit2Sharp/Patch.cs b/LibGit2Sharp/Patch.cs index 9f227275c..0a22f6b9f 100644 --- a/LibGit2Sharp/Patch.cs +++ b/LibGit2Sharp/Patch.cs @@ -32,28 +32,34 @@ protected Patch() internal Patch(DiffSafeHandle diff) { - Proxy.git_diff_foreach(diff, FileCallback, null, null); - - Proxy.git_diff_print(diff, PrintCallBack); - } + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) + { + using (var patch = Proxy.git_patch_from_diff(diff, i)) + { + var delta = Proxy.git_diff_get_delta(diff, i); + AddFileChange(delta); + Proxy.git_patch_print(patch, PrintCallBack); + } - private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) - { - AddFileChange(delta); - return 0; + } } private void AddFileChange(GitDiffDelta delta) { - var newFilePath = LaxFilePathMarshaler.FromNative(delta.NewFile.Path); - + var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path; + var newFilePath = LaxFilePathMarshaler.FromNative(pathPtr); changes.Add(newFilePath, new ContentChanges(delta.IsBinary())); } private int PrintCallBack(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) { string patchPart = LaxUtf8Marshaler.FromNative(line.content, (int)line.contentLen); - var filePath = LaxFilePathMarshaler.FromNative(delta.NewFile.Path); + + // Deleted files mean no "new file" path + + var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path; + var filePath = LaxFilePathMarshaler.FromNative(pathPtr); ContentChanges currentChange = this[filePath]; string prefix = string.Empty; @@ -77,10 +83,10 @@ private int PrintCallBack(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line break; } - string formatedOutput = string.Concat(prefix, patchPart); + string formattedOutput = string.Concat(prefix, patchPart); - fullPatchBuilder.Append(formatedOutput); - this[filePath].AppendToPatch(formatedOutput); + fullPatchBuilder.Append(formattedOutput); + currentChange.AppendToPatch(formattedOutput); return 0; } From 8077fd2f94aaec2e12a51d6844c0095314b13e1b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 9 Jan 2014 10:08:08 -0800 Subject: [PATCH 05/16] Update error categories to match libgit2 --- LibGit2Sharp/Core/GitErrorCategory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LibGit2Sharp/Core/GitErrorCategory.cs b/LibGit2Sharp/Core/GitErrorCategory.cs index 6782871cb..66e10cbaf 100644 --- a/LibGit2Sharp/Core/GitErrorCategory.cs +++ b/LibGit2Sharp/Core/GitErrorCategory.cs @@ -3,6 +3,7 @@ namespace LibGit2Sharp.Core internal enum GitErrorCategory { Unknown = -1, + None, NoMemory, Os, Invalid, @@ -27,5 +28,7 @@ internal enum GitErrorCategory Merge, Ssh, Filter, + Revert, + Callback, } } From dce1367264ac8c5ba6df22080cac1e22100a8c75 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 8 Jan 2014 10:22:51 -0800 Subject: [PATCH 06/16] Deploy Proxy.MarshalAs more extensively --- LibGit2Sharp/Core/Proxy.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index f16982fdd..5273c4b68 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -61,9 +61,7 @@ public static BlameSafeHandle git_blame_file( public static GitBlameHunk git_blame_get_hunk_byindex(BlameSafeHandle blame, uint idx) { - GitBlameHunk hunk = new GitBlameHunk(); - Marshal.PtrToStructure(NativeMethods.git_blame_get_hunk_byindex(blame, idx), hunk); - return hunk; + return MarshalAs(NativeMethods.git_blame_get_hunk_byindex(blame, idx)); } public static void git_blame_free(IntPtr blame) @@ -2798,7 +2796,7 @@ public static IList RemoteLsHelper(IntPtr heads, UIntPtr size) var list = new List(count); for (int i = 0; i < count; i++) { - list.Add((GitRemoteHead)Marshal.PtrToStructure(rawHeads[i], typeof (GitRemoteHead))); + list.Add(MarshalAs(rawHeads[i])); } return list; } From 9165ae30d4cff10681b5b65f094d0d4320426b6e Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 8 Jan 2014 10:31:29 -0800 Subject: [PATCH 07/16] Introduce marshaling extension to IntPtr --- LibGit2Sharp/Configuration.cs | 2 +- .../Core/Handles/GitConfigEntryHandle.cs | 2 +- LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs | 2 +- .../Core/Handles/IndexEntrySafeHandle.cs | 2 +- LibGit2Sharp/Core/Handles/OidSafeHandle.cs | 2 +- .../Core/Handles/StatusEntrySafeHandle.cs | 2 +- LibGit2Sharp/Core/IntPtrExtensions.cs | 17 +++++++++++++++++ LibGit2Sharp/Core/Proxy.cs | 15 +++------------ LibGit2Sharp/LibGit2Sharp.csproj | 1 + LibGit2Sharp/ObjectDatabase.cs | 2 +- LibGit2Sharp/RepositoryStatus.cs | 4 ++-- LibGit2Sharp/Signature.cs | 3 +-- 12 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 LibGit2Sharp/Core/IntPtrExtensions.cs diff --git a/LibGit2Sharp/Configuration.cs b/LibGit2Sharp/Configuration.cs index 1e70437b8..e7de3d24f 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -305,7 +305,7 @@ private IEnumerable> BuildConfigEntries() private static ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) { - var entry = (GitConfigEntry)Marshal.PtrToStructure(entryPtr, typeof(GitConfigEntry)); + var entry = entryPtr.MarshalAs(); return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry.namePtr), LaxUtf8Marshaler.FromNative(entry.valuePtr), diff --git a/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs b/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs index c5ac71ded..5d9d4a2b6 100644 --- a/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs +++ b/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs @@ -6,7 +6,7 @@ internal class GitConfigEntryHandle : NotOwnedSafeHandleBase { public GitConfigEntry MarshalAsGitConfigEntry() { - return (GitConfigEntry)Marshal.PtrToStructure(handle, typeof(GitConfigEntry)); + return handle.MarshalAs(); } } } diff --git a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs b/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs index 673b2187b..8ae8c3c8c 100644 --- a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs +++ b/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs @@ -15,7 +15,7 @@ public GitError MarshalAsGitError() return null; } - return (GitError)Marshal.PtrToStructure(handle, typeof(GitError)); + return handle.MarshalAs(); } } } diff --git a/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs index 5f5301941..87e0f1dc4 100644 --- a/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs +++ b/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs @@ -6,7 +6,7 @@ internal class IndexEntrySafeHandle : NotOwnedSafeHandleBase { public GitIndexEntry MarshalAsGitIndexEntry() { - return (GitIndexEntry)Marshal.PtrToStructure(handle, typeof(GitIndexEntry)); + return handle.MarshalAs(); } } } diff --git a/LibGit2Sharp/Core/Handles/OidSafeHandle.cs b/LibGit2Sharp/Core/Handles/OidSafeHandle.cs index bc3ce6a76..9b806aab1 100644 --- a/LibGit2Sharp/Core/Handles/OidSafeHandle.cs +++ b/LibGit2Sharp/Core/Handles/OidSafeHandle.cs @@ -6,7 +6,7 @@ internal class OidSafeHandle : NotOwnedSafeHandleBase { private GitOid? MarshalAsGitOid() { - return IsInvalid ? null : (GitOid?)Marshal.PtrToStructure(handle, typeof(GitOid)); + return IsInvalid ? null : (GitOid?)handle.MarshalAs(); } public ObjectId MarshalAsObjectId() diff --git a/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs index ef8433529..0a85125f3 100644 --- a/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs +++ b/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs @@ -21,7 +21,7 @@ public StatusEntrySafeHandle(IntPtr handle) public GitStatusEntry MarshalAsGitStatusEntry() { - return (GitStatusEntry)Marshal.PtrToStructure(handle, typeof(GitStatusEntry)); + return handle.MarshalAs(); } } } diff --git a/LibGit2Sharp/Core/IntPtrExtensions.cs b/LibGit2Sharp/Core/IntPtrExtensions.cs new file mode 100644 index 000000000..314a16834 --- /dev/null +++ b/LibGit2Sharp/Core/IntPtrExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + internal static class IntPtrExtensions + { + public static T MarshalAs(this IntPtr ptr, bool throwWhenNull = true) + { + if (!throwWhenNull && ptr == IntPtr.Zero) + { + return default(T); + } + return (T)Marshal.PtrToStructure(ptr, typeof(T)); + } + } +} diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 5273c4b68..78c5a2beb 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -13,15 +13,6 @@ namespace LibGit2Sharp.Core { internal class Proxy { - private static T MarshalAs(IntPtr ptr) - { - if (ptr == IntPtr.Zero) - { - return default(T); - } - return (T)Marshal.PtrToStructure(ptr, typeof(T)); - } - #region giterr_ public static void giterr_set_str(GitErrorCategory error_class, Exception exception) @@ -61,7 +52,7 @@ public static BlameSafeHandle git_blame_file( public static GitBlameHunk git_blame_get_hunk_byindex(BlameSafeHandle blame, uint idx) { - return MarshalAs(NativeMethods.git_blame_get_hunk_byindex(blame, idx)); + return NativeMethods.git_blame_get_hunk_byindex(blame, idx).MarshalAs(false); } public static void git_blame_free(IntPtr blame) @@ -741,7 +732,7 @@ public static int git_diff_num_deltas(DiffSafeHandle diff) public static GitDiffDelta git_diff_get_delta(DiffSafeHandle diff, int idx) { - return MarshalAs(NativeMethods.git_diff_get_delta(diff, (UIntPtr) idx)); + return NativeMethods.git_diff_get_delta(diff, (UIntPtr) idx).MarshalAs(false); } #endregion @@ -2796,7 +2787,7 @@ public static IList RemoteLsHelper(IntPtr heads, UIntPtr size) var list = new List(count); for (int i = 0; i < count; i++) { - list.Add(MarshalAs(rawHeads[i])); + list.Add(rawHeads[i].MarshalAs()); } return list; } diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 92d4513b1..54fc4b9c1 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -76,6 +76,7 @@ + diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 1fe0a890b..bb43e6401 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -43,7 +43,7 @@ internal ObjectDatabase(Repository repo) public virtual IEnumerator GetEnumerator() { ICollection oids = Proxy.git_odb_foreach(handle, - ptr => (GitOid) Marshal.PtrToStructure(ptr, typeof (GitOid))); + ptr => ptr.MarshalAs()); return oids .Select(gitOid => repo.Lookup(new ObjectId(gitOid))) diff --git a/LibGit2Sharp/RepositoryStatus.cs b/LibGit2Sharp/RepositoryStatus.cs index 74f3c3781..92af33244 100644 --- a/LibGit2Sharp/RepositoryStatus.cs +++ b/LibGit2Sharp/RepositoryStatus.cs @@ -73,11 +73,11 @@ internal RepositoryStatus(Repository repo, StatusOptions options) if (entry.HeadToIndexPtr != IntPtr.Zero) { - deltaHeadToIndex = (GitDiffDelta)Marshal.PtrToStructure(entry.HeadToIndexPtr, typeof(GitDiffDelta)); + deltaHeadToIndex = entry.HeadToIndexPtr.MarshalAs(); } if (entry.IndexToWorkDirPtr != IntPtr.Zero) { - deltaIndexToWorkDir = (GitDiffDelta)Marshal.PtrToStructure(entry.IndexToWorkDirPtr, typeof(GitDiffDelta)); + deltaIndexToWorkDir = entry.IndexToWorkDirPtr.MarshalAs(); } AddStatusEntryForDelta(entry.Status, deltaHeadToIndex, deltaIndexToWorkDir); diff --git a/LibGit2Sharp/Signature.cs b/LibGit2Sharp/Signature.cs index d4a1f3745..bc9dc7786 100644 --- a/LibGit2Sharp/Signature.cs +++ b/LibGit2Sharp/Signature.cs @@ -19,8 +19,7 @@ public sealed class Signature : IEquatable internal Signature(IntPtr signaturePtr) { - var handle = new GitSignature(); - Marshal.PtrToStructure(signaturePtr, handle); + var handle = signaturePtr.MarshalAs(); name = LaxUtf8Marshaler.FromNative(handle.Name); email = LaxUtf8Marshaler.FromNative(handle.Email); From cc4bb2da1de35122860993cb1f5c9fae7a724f59 Mon Sep 17 00:00:00 2001 From: crumblycake Date: Thu, 5 Dec 2013 22:31:18 +1100 Subject: [PATCH 08/16] Initial merge functionality. Bring initial merge functionality to LibGit2Sharp. --- LibGit2Sharp.Tests/MergeFixture.cs | 190 +++++++++++++++++- LibGit2Sharp/Core/GitMergeOpts.cs | 33 +++ LibGit2Sharp/Core/GitMergeResult.cs | 47 +++++ LibGit2Sharp/Core/GitMergeTreeOpts.cs | 57 ++++++ .../Core/Handles/GitMergeHeadHandle.cs | 13 ++ .../Core/Handles/GitMergeResultHandle.cs | 13 ++ LibGit2Sharp/Core/NativeMethods.cs | 49 +++++ LibGit2Sharp/Core/Proxy.cs | 83 ++++++++ LibGit2Sharp/IRepository.cs | 7 + LibGit2Sharp/LibGit2Sharp.csproj | 6 + LibGit2Sharp/MergeResult.cs | 71 +++++++ LibGit2Sharp/Repository.cs | 74 +++++++ 12 files changed, 642 insertions(+), 1 deletion(-) create mode 100644 LibGit2Sharp/Core/GitMergeOpts.cs create mode 100644 LibGit2Sharp/Core/GitMergeResult.cs create mode 100644 LibGit2Sharp/Core/GitMergeTreeOpts.cs create mode 100644 LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs create mode 100644 LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs create mode 100644 LibGit2Sharp/MergeResult.cs diff --git a/LibGit2Sharp.Tests/MergeFixture.cs b/LibGit2Sharp.Tests/MergeFixture.cs index 7bb5d54dd..32bef84e9 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -80,5 +81,192 @@ public void CanRetrieveTheBranchBeingMerged() Assert.Null(mergedHeads[1].Tip); } } + + [Fact] + public void CanMergeRepoNonFastForward() + { + const string firstBranchFileName = "first branch file.txt"; + const string secondBranchFileName = "second branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + var originalTreeCount = firstBranch.Tip.Tree.Count; + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + + secondBranch.Checkout(); + + // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). + AddFileCommitToRepo(repo, secondBranchFileName); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status); + + Assert.Equal(repo.Head.Tip, mergeResult.Commit); + Assert.Equal(originalTreeCount + 3, mergeResult.Commit.Tree.Count); // Expecting original tree count plussed by the 3 added files. + Assert.Equal(2, mergeResult.Commit.Parents.Count()); // Merge commit should have 2 parents + } + } + + [Fact] + public void IsUpToDateMerge() + { + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + + secondBranch.Checkout(); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.UpToDate, mergeResult.Status); + } + } + + [Fact] + public void CanFastForwardRepos() + { + const string firstBranchFileName = "first branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + // Reset the index and the working tree. + repo.Reset(ResetMode.Hard); + + // Clean the working directory. + repo.RemoveUntrackedFiles(); + + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + + secondBranch.Checkout(); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.FastForward, mergeResult.Status); + Assert.Equal(repo.Branches["FirstBranch"].Tip, mergeResult.Commit); + Assert.Equal(repo.Branches["FirstBranch"].Tip, repo.Head.Tip); + Assert.Equal(0, repo.Index.RetrieveStatus().Count()); + } + } + + [Fact] + public void ConflictingMergeRepos() + { + const string firstBranchFileName = "first branch file.txt"; + const string secondBranchFileName = "second branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch + + secondBranch.Checkout(); + // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). + AddFileCommitToRepo(repo, secondBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "The second branches comment"); // Change file in second branch + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + Assert.Null(mergeResult.Commit); + Assert.Equal(1, repo.Index.Conflicts.Count()); + + var conflict = repo.Index.Conflicts.First(); + var changes = repo.Diff.Compare(repo.Lookup(conflict.Theirs.Id), repo.Lookup(conflict.Ours.Id)); + + Assert.False(changes.IsBinaryComparison); + } + } + + [Fact] + public void ConflictingMergeReposBinary() + { + const string firstBranchFileName = "first branch file.bin"; + const string secondBranchFileName = "second branch file.bin"; + const string sharedBranchFileName = "first+second branch file.bin"; + + string path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + firstBranch.Checkout(); + + // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "\0The first branches comment\0"); // Change file in first branch + + secondBranch.Checkout(); + // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). + AddFileCommitToRepo(repo, secondBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "\0The second branches comment\0"); // Change file in second branch + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + Assert.Equal(1, repo.Index.Conflicts.Count()); + + Conflict conflict = repo.Index.Conflicts.First(); + + var changes = repo.Diff.Compare(repo.Lookup(conflict.Theirs.Id), repo.Lookup(conflict.Ours.Id)); + + Assert.True(changes.IsBinaryComparison); + } + } + + private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null) + { + Touch(repository.Info.WorkingDirectory, filename, content); + + repository.Index.Stage(filename); + + return repository.Commit("New commit", Constants.Signature, Constants.Signature); + } } } diff --git a/LibGit2Sharp/Core/GitMergeOpts.cs b/LibGit2Sharp/Core/GitMergeOpts.cs new file mode 100644 index 000000000..1b371a371 --- /dev/null +++ b/LibGit2Sharp/Core/GitMergeOpts.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + internal enum GitMergeFlags + { + /// + /// Default + /// + GIT_MERGE_DEFAULT = 0, + + /// + /// Do not fast-forward. + /// + GIT_MERGE_NO_FASTFORWARD = 1, + + /// + /// Only perform fast-forward. + /// + GIT_MERGE_FASTFORWARD_ONLY = 2, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GitMergeOpts + { + public uint Version; + + public GitMergeFlags MergeFlags; + public GitMergeTreeOpts MergeTreeOpts; + public GitCheckoutOpts CheckoutOpts; + } +} diff --git a/LibGit2Sharp/Core/GitMergeResult.cs b/LibGit2Sharp/Core/GitMergeResult.cs new file mode 100644 index 000000000..e335cabfa --- /dev/null +++ b/LibGit2Sharp/Core/GitMergeResult.cs @@ -0,0 +1,47 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + internal class GitMergeResult + { + internal GitMergeResult(GitMergeResultHandle handle) + { + IsUpToDate = Proxy.git_merge_result_is_uptodate(handle); + IsFastForward = Proxy.git_merge_result_is_fastforward(handle); + + if (IsFastForward) + { + FastForwardId = Proxy.git_merge_result_fastforward_oid(handle); + } + } + + public virtual bool IsUpToDate { get; private set; } + + public virtual bool IsFastForward { get; private set; } + + /// + /// The ID that a fast-forward merge should advance to. + /// + public virtual ObjectId FastForwardId { get; private set; } + + public virtual MergeStatus Status + { + get + { + if (IsUpToDate) + { + return MergeStatus.UpToDate; + } + else if (IsFastForward) + { + return MergeStatus.FastForward; + } + else + { + return MergeStatus.NonFastForward; + } + } + } + } +} diff --git a/LibGit2Sharp/Core/GitMergeTreeOpts.cs b/LibGit2Sharp/Core/GitMergeTreeOpts.cs new file mode 100644 index 000000000..dfcbe5970 --- /dev/null +++ b/LibGit2Sharp/Core/GitMergeTreeOpts.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [Flags] + internal enum GitMergeTreeFlags + { + /// + /// No options. + /// + GIT_MERGE_TREE_NORMAL = 0, + + /// + /// GIT_MERGE_TREE_FIND_RENAMES in libgit2 + /// + GIT_MERGE_TREE_FIND_RENAMES = (1 << 0), + } + + internal enum GitMergeAutomergeFlags + { + GIT_MERGE_AUTOMERGE_NORMAL = 0, + GIT_MERGE_AUTOMERGE_NONE = 1, + GIT_MERGE_AUTOMERGE_FAVOR_OURS = 2, + GIT_MERGE_AUTOMERGE_FAVOR_THEIRS = 3, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GitMergeTreeOpts + { + public uint Version; + + public GitMergeTreeFlags MergeTreeFlags; + + /// + /// Similarity to consider a file renamed. + /// + public uint RenameThreshold; + + /// + /// Maximum similarity sources to examine (overrides + /// 'merge.renameLimit' config (default 200) + /// + public uint TargetLimit; + + /// + /// Pluggable similarityMetric; pass IntPtr.Zero + /// to use internal metric. + /// + public IntPtr SimilarityMetric; + + /// + /// Flags for automerging content. + /// + public GitMergeAutomergeFlags MergeAutomergeFlags; + } +} diff --git a/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs b/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs new file mode 100644 index 000000000..f49e30e54 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core.Handles +{ + internal class GitMergeHeadHandle : SafeHandleBase + { + protected override bool ReleaseHandleImpl() + { + Proxy.git_merge_head_free(handle); + return true; + } + } +} diff --git a/LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs b/LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs new file mode 100644 index 000000000..f13b03e67 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/GitMergeResultHandle.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core.Handles +{ + internal class GitMergeResultHandle : SafeHandleBase + { + protected override bool ReleaseHandleImpl() + { + Proxy.git_merge_result_free(handle); + return true; + } + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 6b1accb2b..9b2f9215e 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -576,6 +576,55 @@ internal static extern int git_merge_base( GitObjectSafeHandle one, GitObjectSafeHandle two); + [DllImport(libgit2)] + internal static extern int git_merge_head_from_ref( + out GitMergeHeadHandle mergehead, + RepositorySafeHandle repo, + ReferenceSafeHandle reference); + + [DllImport(libgit2)] + internal static extern int git_merge_head_from_fetchhead( + out GitMergeHeadHandle mergehead, + RepositorySafeHandle repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string branch_name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote_url, + ref GitOid oid); + + [DllImport(libgit2)] + internal static extern int git_merge_head_from_oid( + out GitMergeHeadHandle mergehead, + RepositorySafeHandle repo, + ref GitOid oid); + + [DllImport(libgit2)] + internal static extern int git_merge( + out GitMergeResultHandle mergeResult, + RepositorySafeHandle repo, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] [In] IntPtr[] their_heads, + UIntPtr their_heads_len, + ref GitMergeOpts given_opts); + + [DllImport(libgit2)] + internal static extern int git_merge_result_is_uptodate( + GitMergeResultHandle merge_result); + + [DllImport(libgit2)] + internal static extern int git_merge_result_is_fastforward( + GitMergeResultHandle merge_result); + + [DllImport(libgit2)] + internal static extern int git_merge_result_fastforward_oid( + out GitOid oid, + GitMergeResultHandle merge_result); + + [DllImport(libgit2)] + internal static extern void git_merge_result_free( + IntPtr merge_result); + + [DllImport(libgit2)] + internal static extern void git_merge_head_free( + IntPtr merge_head); + [DllImport(libgit2)] internal static extern int git_message_prettify( byte[] message_out, // NB: This is more properly a StringBuilder, but it's UTF8 diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 78c5a2beb..01377e043 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -956,6 +956,89 @@ public static ObjectId git_merge_base(RepositorySafeHandle repo, Commit first, C } } + public static GitMergeHeadHandle git_merge_head_from_oid(RepositorySafeHandle repo, GitOid oid) + { + using (ThreadAffinity()) + { + GitMergeHeadHandle their_head; + + int res = NativeMethods.git_merge_head_from_oid(out their_head, repo, ref oid); + + Ensure.ZeroResult(res); + + return their_head; + } + } + + public static GitMergeResultHandle git_merge(RepositorySafeHandle repo, GitMergeHeadHandle[] heads, GitMergeOpts options) + { + using (ThreadAffinity()) + { + GitMergeResultHandle ret; + + IntPtr[] their_heads = new IntPtr[heads.Length]; + for (int i = 0; i < heads.Length; i++) + { + their_heads[i] = heads[i].DangerousGetHandle(); + } + + int res = NativeMethods.git_merge( + out ret, + repo, + their_heads, + (UIntPtr)their_heads.Length, + ref options); + + Ensure.ZeroResult(res); + + return ret; + } + } + + public static bool git_merge_result_is_uptodate(GitMergeResultHandle handle) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_merge_result_is_uptodate(handle); + Ensure.BooleanResult(res); + + return (res == 1); + } + } + + public static bool git_merge_result_is_fastforward(GitMergeResultHandle handle) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_merge_result_is_fastforward(handle); + Ensure.BooleanResult(res); + + return (res == 1); + } + } + + public static GitOid git_merge_result_fastforward_oid(GitMergeResultHandle handle) + { + using (ThreadAffinity()) + { + GitOid oid; + int res = NativeMethods.git_merge_result_fastforward_oid(out oid, handle); + Ensure.ZeroResult(res); + + return oid; + } + } + + public static void git_merge_result_free(IntPtr handle) + { + NativeMethods.git_merge_result_free(handle); + } + + public static void git_merge_head_free(IntPtr handle) + { + NativeMethods.git_merge_head_free(handle); + } + #endregion #region git_message_ diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index 9c3c9f31d..952e1f648 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -193,6 +193,13 @@ public interface IRepository : IDisposable /// IEnumerable MergeHeads { get; } + /// + /// Merges the given commit into HEAD. + /// + /// The commit to use as a reference for the changes that should be merged into HEAD. + /// If the merge generates a merge commit (i.e. a non-fast forward merge), the of who made the merge. + MergeResult Merge(Commit commit, Signature merger); + /// /// Manipulate the currently ignored files. /// diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 54fc4b9c1..26e5aec30 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -78,19 +78,25 @@ + + + + + + diff --git a/LibGit2Sharp/MergeResult.cs b/LibGit2Sharp/MergeResult.cs new file mode 100644 index 000000000..fa2093362 --- /dev/null +++ b/LibGit2Sharp/MergeResult.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Class to report the result of a merge. + /// + public class MergeResult + { + /// + /// Needed for mocking purposes. + /// + protected MergeResult() + { } + + internal MergeResult(MergeStatus status, Commit commit = null) + { + this.Status = status; + this.Commit = commit; + } + + /// + /// The status of the merge. + /// + public virtual MergeStatus Status + { + get; + private set; + } + + /// + /// The resulting commit of the merge. For fast-forward merges, this is the + /// commit that merge was fast forwarded to. + /// This will return null if the merge has been unsuccessful due to conflicts. + /// + public virtual Commit Commit + { + get; + private set; + } + } + + /// + /// The status of what happened as a result of a merge. + /// + public enum MergeStatus + { + /// + /// Merge was up-to-date. + /// + UpToDate, + + /// + /// Fast-forward merge. + /// + FastForward, + + /// + /// A non fast-forward merge. + /// + NonFastForward, + + /// + /// Merge resulted in conflicts. + /// + Conflicts, + } +} diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 33f9d795e..092aa38d8 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -1032,6 +1032,80 @@ public IEnumerable MergeHeads } } + /// + /// Merges the given commit into HEAD as well as performing a Fast Forward if possible. + /// + /// The commit to use as a reference for the changes that should be merged into HEAD. + /// If the merge generates a merge commit (i.e. a non-fast forward merge), the of who made the merge. + /// The result of the performed merge . + public MergeResult Merge(Commit commit, Signature merger) + { + using (GitMergeHeadHandle mergeHeadHandle = Proxy.git_merge_head_from_oid(Handle, commit.Id.Oid)) + { + GitMergeOpts opts = new GitMergeOpts() + { + Version = 1, + MergeTreeOpts = { Version = 1 }, + CheckoutOpts = { version = 1 }, + }; + + + // Perform the merge in libgit2 and get the result back. + GitMergeResult gitMergeResult; + using (GitMergeResultHandle mergeResultHandle = Proxy.git_merge(Handle, new GitMergeHeadHandle[] { mergeHeadHandle }, opts)) + { + gitMergeResult = new GitMergeResult(mergeResultHandle); + } + + // Handle the result of the merge performed in libgit2 + // and commit the result / update the working directory as necessary. + MergeResult mergeResult; + switch(gitMergeResult.Status) + { + case MergeStatus.UpToDate: + mergeResult = new MergeResult(MergeStatus.UpToDate); + break; + case MergeStatus.FastForward: + Commit fastForwardCommit = this.Lookup(gitMergeResult.FastForwardId); + FastForward(fastForwardCommit); + mergeResult = new MergeResult(MergeStatus.FastForward, fastForwardCommit); + break; + case MergeStatus.NonFastForward: + { + if (Index.IsFullyMerged) + { + // Commit the merge + Commit mergeCommit = this.Commit(Info.Message, author: merger, committer: merger); + mergeResult = new MergeResult(MergeStatus.NonFastForward, mergeCommit); + } + else + { + mergeResult = new MergeResult(MergeStatus.Conflicts); + } + } + break; + default: + throw new NotImplementedException(string.Format("Unknown MergeStatus: {0}", gitMergeResult.Status)); + } + + return mergeResult; + } + } + + private void FastForward(Commit fastForwardCommit) + { + var checkoutOpts = new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.None, + }; + + CheckoutTree(fastForwardCommit.Tree, null, checkoutOpts); + + Refs.UpdateTarget("HEAD", fastForwardCommit.Id.Sha); + + // TODO: Update Reflog... + } + internal StringComparer PathComparer { get { return pathCase.Value.Comparer; } From 0bbed219976caab5e697fb3c158058511157b297 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Fri, 17 Jan 2014 13:26:59 -0800 Subject: [PATCH 09/16] Bind git_patch_line_stats --- LibGit2Sharp/Core/NativeMethods.cs | 7 +++++++ LibGit2Sharp/Core/Proxy.cs | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 9b2f9215e..67f0ad9d1 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -731,6 +731,13 @@ internal static extern int git_object_peel( [DllImport(libgit2)] internal static extern int git_patch_print(PatchSafeHandle patch, git_diff_line_cb print_cb, IntPtr payload); + [DllImport(libgit2)] + internal static extern int git_patch_line_stats( + out UIntPtr total_context, + out UIntPtr total_additions, + out UIntPtr total_deletions, + PatchSafeHandle patch); + [DllImport(libgit2)] internal static extern void git_patch_free(IntPtr patch); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 01377e043..5f60e7930 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1302,6 +1302,17 @@ public static void git_patch_print(PatchSafeHandle patch, NativeMethods.git_diff } } + public static Tuple git_patch_line_stats(PatchSafeHandle patch) + { + using (ThreadAffinity()) + { + UIntPtr ctx, add, del; + int res = NativeMethods.git_patch_line_stats(out ctx, out add, out del, patch); + Ensure.ZeroResult(res); + return new Tuple((int)add, (int)del); + } + } + #endregion #region git_push_ From 4e43e4fc28dc8f11f81218252b3bcda88b562e40 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Fri, 17 Jan 2014 13:51:51 -0800 Subject: [PATCH 10/16] Introduce Diff.Compare() --- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 1 + LibGit2Sharp.Tests/PatchStatsFixture.cs | 26 ++++ LibGit2Sharp/ContentChangeStats.cs | 30 +++++ LibGit2Sharp/Diff.cs | 1 + LibGit2Sharp/LibGit2Sharp.csproj | 2 + LibGit2Sharp/PatchStats.cs | 119 +++++++++++++++++++ 6 files changed, 179 insertions(+) create mode 100644 LibGit2Sharp.Tests/PatchStatsFixture.cs create mode 100644 LibGit2Sharp/ContentChangeStats.cs create mode 100644 LibGit2Sharp/PatchStats.cs diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 966b94e9f..7ae394444 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -61,6 +61,7 @@ + diff --git a/LibGit2Sharp.Tests/PatchStatsFixture.cs b/LibGit2Sharp.Tests/PatchStatsFixture.cs new file mode 100644 index 000000000..8b5ce40ee --- /dev/null +++ b/LibGit2Sharp.Tests/PatchStatsFixture.cs @@ -0,0 +1,26 @@ +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class PatchStatsFixture : BaseFixture + { + [Fact] + public void CanExtractStatisticsFromDiff() + { + using (var repo = new Repository(StandardTestRepoPath)) + { + var oldTree = repo.Lookup("origin/packed-test").Tree; + var newTree = repo.Lookup("HEAD").Tree; + var stats = repo.Diff.Compare(oldTree, newTree); + + Assert.Equal(8, stats.TotalLinesAdded); + Assert.Equal(1, stats.TotalLinesDeleted); + + var contentStats = stats["new.txt"]; + Assert.Equal(1, contentStats.LinesAdded); + Assert.Equal(1, contentStats.LinesDeleted); + } + } + } +} diff --git a/LibGit2Sharp/ContentChangeStats.cs b/LibGit2Sharp/ContentChangeStats.cs new file mode 100644 index 000000000..8314888a0 --- /dev/null +++ b/LibGit2Sharp/ContentChangeStats.cs @@ -0,0 +1,30 @@ +namespace LibGit2Sharp +{ + /// + /// Holds a summary of a change to a single file. + /// + public class ContentChangeStats + { + /// + /// The number of lines added in the diff. + /// + public virtual int LinesAdded { get; private set; } + + /// + /// The number of lines deleted in the diff. + /// + public virtual int LinesDeleted { get; private set; } + + /// + /// For mocking. + /// + protected ContentChangeStats() + { } + + internal ContentChangeStats(int added, int deleted) + { + LinesAdded = added; + LinesDeleted = deleted; + } + } +} diff --git a/LibGit2Sharp/Diff.cs b/LibGit2Sharp/Diff.cs index 3dafa58aa..bde61d7ef 100644 --- a/LibGit2Sharp/Diff.cs +++ b/LibGit2Sharp/Diff.cs @@ -97,6 +97,7 @@ private static IDictionary new Patch(diff) }, { typeof(TreeChanges), diff => new TreeChanges(diff) }, + { typeof(PatchStats), diff => new PatchStats(diff) }, }; /// diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 26e5aec30..e10e62ddb 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -75,10 +75,12 @@ + + diff --git a/LibGit2Sharp/PatchStats.cs b/LibGit2Sharp/PatchStats.cs new file mode 100644 index 000000000..f22eecb1a --- /dev/null +++ b/LibGit2Sharp/PatchStats.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Holds summary information for a diff. + /// The individual patches for each file can be accessed through the indexer of this class. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class PatchStats : IEnumerable + { + private readonly IDictionary changes = new Dictionary(); + private readonly int totalLinesAdded; + private readonly int totalLinesDeleted; + + /// + /// For mocking. + /// + protected PatchStats() + { } + + internal PatchStats(DiffSafeHandle diff) + { + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) + { + using (var patch = Proxy.git_patch_from_diff(diff, i)) + { + var delta = Proxy.git_diff_get_delta(diff, i); + var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path; + var newFilePath = LaxFilePathMarshaler.FromNative(pathPtr); + + var stats = Proxy.git_patch_line_stats(patch); + int added = stats.Item1; + int deleted = stats.Item2; + changes.Add(newFilePath, new ContentChangeStats(added, deleted)); + totalLinesAdded += added; + totalLinesDeleted += deleted; + } + + } + } + + #region IEnumerable Members + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return changes.Values.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + /// + /// Gets the corresponding to the specified . + /// + /// + public virtual ContentChangeStats this[string path] + { + get { return this[(FilePath) path]; } + } + + private ContentChangeStats this[FilePath path] + { + get + { + ContentChangeStats stats; + if (changes.TryGetValue(path, out stats)) + { + return stats; + } + return null; + } + } + + /// + /// The total number of lines added in this diff. + /// + public virtual int TotalLinesAdded + { + get { return totalLinesAdded; } + } + + /// + /// The total number of lines deleted in this diff. + /// + public virtual int TotalLinesDeleted + { + get { return totalLinesDeleted; } + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "+{0} -{1}", + TotalLinesAdded, TotalLinesDeleted); + } + } + } +} From a3de5bafc3e6735f3d588c507c04c49223191a9a Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Fri, 17 Jan 2014 13:52:04 -0800 Subject: [PATCH 11/16] Fix xmldoc typo --- LibGit2Sharp/Patch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibGit2Sharp/Patch.cs b/LibGit2Sharp/Patch.cs index 0a22f6b9f..c08807a6c 100644 --- a/LibGit2Sharp/Patch.cs +++ b/LibGit2Sharp/Patch.cs @@ -144,7 +144,7 @@ public virtual int LinesAdded } /// - /// The total number of lines added in this diff. + /// The total number of lines deleted in this diff. /// public virtual int LinesDeleted { From 08fb1226409589b16594fd58804bc4bc868e8213 Mon Sep 17 00:00:00 2001 From: Duncan Smart Date: Wed, 22 Jan 2014 12:16:24 +0000 Subject: [PATCH 12/16] Speed up postbuild xcopy Added `/d` to xcopy so that files are only copied if newer, saving a few seconds of unnecessary I/O on build. --- nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 b/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 index eb32fe37d..225ad5338 100644 --- a/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 +++ b/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 @@ -8,6 +8,6 @@ $x64 = $(Join-Path $NativeAssembliesDir "amd64\*.*") $LibGit2SharpPostBuildCmd = " if not exist `"`$(TargetDir)NativeBinaries`" md `"`$(TargetDir)NativeBinaries`" if not exist `"`$(TargetDir)NativeBinaries\x86`" md `"`$(TargetDir)NativeBinaries\x86`" -xcopy /s /y `"$x86`" `"`$(TargetDir)NativeBinaries\x86`" +xcopy /s /y /d `"$x86`" `"`$(TargetDir)NativeBinaries\x86`" if not exist `"`$(TargetDir)NativeBinaries\amd64`" md `"`$(TargetDir)NativeBinaries\amd64`" -xcopy /s /y `"$x64`" `"`$(TargetDir)NativeBinaries\amd64`"" \ No newline at end of file +xcopy /s /y /d `"$x64`" `"`$(TargetDir)NativeBinaries\amd64`"" From 61e8add29c12bd4a56b84af6ddac318e1778ea50 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 23 Jan 2014 16:09:41 +0100 Subject: [PATCH 13/16] Fix Remotes.Add(name, url, refspec) implementation Prevents the creation of a default fetch refspec beside the passed in one. --- LibGit2Sharp.Tests/RemoteFixture.cs | 22 ++++++++++++++++++++++ LibGit2Sharp/Core/NativeMethods.cs | 14 +++++++++----- LibGit2Sharp/Core/Proxy.cs | 21 ++++++++++++--------- LibGit2Sharp/RemoteCollection.cs | 4 +--- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/LibGit2Sharp.Tests/RemoteFixture.cs b/LibGit2Sharp.Tests/RemoteFixture.cs index f325d16b0..ebe67591d 100644 --- a/LibGit2Sharp.Tests/RemoteFixture.cs +++ b/LibGit2Sharp.Tests/RemoteFixture.cs @@ -170,5 +170,27 @@ public void DoesNotThrowWhenARemoteHasNoUrlSet() Assert.Equal(1, remotes.Count(r => r.Name == "no_url")); } } + + [Fact] + public void CreatingARemoteAddsADefaultFetchRefSpec() + { + var path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var remote = repo.Network.Remotes.Add("one", "http://github.com/up/stream"); + Assert.Equal("+refs/heads/*:refs/remotes/one/*", remote.RefSpecs.Single().Specification); + } + } + + [Fact] + public void CanCreateARemoteWithASpecifiedFetchRefSpec() + { + var path = CloneStandardTestRepo(); + using (var repo = new Repository(path)) + { + var remote = repo.Network.Remotes.Add("two", "http://github.com/up/stream", "+refs/heads/*:refs/remotes/grmpf/*"); + Assert.Equal("+refs/heads/*:refs/remotes/grmpf/*", remote.RefSpecs.Single().Specification); + } + } } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 67f0ad9d1..63bfef0d2 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -953,6 +953,15 @@ internal static extern int git_remote_create_inmemory( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refspec, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); + + [DllImport(libgit2)] + internal static extern int git_remote_create_with_fetchspec( + out RemoteSafeHandle remote, + RepositorySafeHandle repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refspec); + [DllImport(libgit2)] internal static extern void git_remote_disconnect(RemoteSafeHandle remote); @@ -1009,11 +1018,6 @@ internal static extern int git_remote_set_callbacks( RemoteSafeHandle remote, ref GitRemoteCallbacks callbacks); - [DllImport(libgit2)] - internal static extern int git_remote_add_fetch( - RemoteSafeHandle remote, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string refspec); - internal delegate int remote_progress_callback(IntPtr str, int len, IntPtr data); internal delegate int remote_completion_callback(RemoteCompletionType type, IntPtr data); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 5f60e7930..09afbcc78 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1679,6 +1679,18 @@ public static RemoteSafeHandle git_remote_create(RepositorySafeHandle repo, stri } } + public static RemoteSafeHandle git_remote_create_with_fetchspec(RepositorySafeHandle repo, string name, string url, string refspec) + { + using (ThreadAffinity()) + { + RemoteSafeHandle handle; + int res = NativeMethods.git_remote_create_with_fetchspec(out handle, repo, name, url, refspec); + Ensure.ZeroResult(res); + + return handle; + } + } + public static RemoteSafeHandle git_remote_create_inmemory(RepositorySafeHandle repo, string url, string refspec) { using (ThreadAffinity()) @@ -1862,15 +1874,6 @@ public static void git_remote_set_autotag(RemoteSafeHandle remote, TagFetchMode NativeMethods.git_remote_set_autotag(remote, value); } - public static void git_remote_add_fetch(RemoteSafeHandle remote, string refspec) - { - using (ThreadAffinity()) - { - int res = NativeMethods.git_remote_add_fetch(remote, refspec); - Ensure.ZeroResult(res); - } - } - public static void git_remote_set_callbacks(RemoteSafeHandle remote, ref GitRemoteCallbacks callbacks) { using (ThreadAffinity()) diff --git a/LibGit2Sharp/RemoteCollection.cs b/LibGit2Sharp/RemoteCollection.cs index cef32f094..ca58c51d7 100644 --- a/LibGit2Sharp/RemoteCollection.cs +++ b/LibGit2Sharp/RemoteCollection.cs @@ -120,10 +120,8 @@ public virtual Remote Add(string name, string url, string fetchRefSpec) Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(fetchRefSpec, "fetchRefSpec"); - using (RemoteSafeHandle handle = Proxy.git_remote_create(repository.Handle, name, url)) + using (RemoteSafeHandle handle = Proxy.git_remote_create_with_fetchspec(repository.Handle, name, url, fetchRefSpec)) { - Proxy.git_remote_add_fetch(handle, fetchRefSpec); - Proxy.git_remote_save(handle); return Remote.BuildFromPtr(handle, this.repository); } } From bc101b2ba468b828bebc8f300218e98c20040eb4 Mon Sep 17 00:00:00 2001 From: crumblycake Date: Thu, 6 Feb 2014 22:37:31 +1100 Subject: [PATCH 14/16] Fix fast forward merge behavior. --- LibGit2Sharp/Repository.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 092aa38d8..7854e8c4d 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -1101,7 +1101,9 @@ private void FastForward(Commit fastForwardCommit) CheckoutTree(fastForwardCommit.Tree, null, checkoutOpts); - Refs.UpdateTarget("HEAD", fastForwardCommit.Id.Sha); + var reference = Refs.Head.ResolveToDirectReference(); + + Refs.UpdateTarget(reference, fastForwardCommit.Id.Sha); // TODO: Update Reflog... } From 97adc9da1f82b7d1e8e61a801099491e549d386f Mon Sep 17 00:00:00 2001 From: crumblycake Date: Thu, 6 Feb 2014 22:38:03 +1100 Subject: [PATCH 15/16] Enhance merge test coverage to better test possible outcomes. --- LibGit2Sharp.Tests/MergeFixture.cs | 56 +++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/LibGit2Sharp.Tests/MergeFixture.cs b/LibGit2Sharp.Tests/MergeFixture.cs index 32bef84e9..2323b3115 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -1,7 +1,7 @@ -using System; -using System.Linq; +using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -81,9 +81,11 @@ public void CanRetrieveTheBranchBeingMerged() Assert.Null(mergedHeads[1].Tip); } } - - [Fact] - public void CanMergeRepoNonFastForward() + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanMergeRepoNonFastForward(bool shouldMergeOccurInDetachedHeadState) { const string firstBranchFileName = "first branch file.txt"; const string secondBranchFileName = "second branch file.txt"; @@ -104,7 +106,15 @@ public void CanMergeRepoNonFastForward() // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). AddFileCommitToRepo(repo, firstBranchFileName); - secondBranch.Checkout(); + if (shouldMergeOccurInDetachedHeadState) + { + // Detaches HEAD + repo.Checkout(secondBranch.Tip); + } + else + { + secondBranch.Checkout(); + } // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). AddFileCommitToRepo(repo, secondBranchFileName); @@ -116,6 +126,13 @@ public void CanMergeRepoNonFastForward() Assert.Equal(repo.Head.Tip, mergeResult.Commit); Assert.Equal(originalTreeCount + 3, mergeResult.Commit.Tree.Count); // Expecting original tree count plussed by the 3 added files. Assert.Equal(2, mergeResult.Commit.Parents.Count()); // Merge commit should have 2 parents + Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); + + if (!shouldMergeOccurInDetachedHeadState) + { + // Ensure HEAD is still attached and points to SecondBranch + Assert.Equal(repo.Refs.Head.TargetIdentifier, secondBranch.CanonicalName); + } } } @@ -143,8 +160,10 @@ public void IsUpToDateMerge() } } - [Fact] - public void CanFastForwardRepos() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) { const string firstBranchFileName = "first branch file.txt"; const string sharedBranchFileName = "first+second branch file.txt"; @@ -169,14 +188,33 @@ public void CanFastForwardRepos() // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). AddFileCommitToRepo(repo, firstBranchFileName); - secondBranch.Checkout(); + if (shouldMergeOccurInDetachedHeadState) + { + // Detaches HEAD + repo.Checkout(secondBranch.Tip); + } + else + { + secondBranch.Checkout(); + } + + Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); Assert.Equal(MergeStatus.FastForward, mergeResult.Status); Assert.Equal(repo.Branches["FirstBranch"].Tip, mergeResult.Commit); Assert.Equal(repo.Branches["FirstBranch"].Tip, repo.Head.Tip); + Assert.Equal(repo.Head.Tip, mergeResult.Commit); + Assert.Equal(0, repo.Index.RetrieveStatus().Count()); + Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); + + if (!shouldMergeOccurInDetachedHeadState) + { + // Ensure HEAD is still attached and points to SecondBranch + Assert.Equal(repo.Refs.Head.TargetIdentifier, secondBranch.CanonicalName); + } } } From edb140a1a7ffceacf6ddd9b83149281fe1daf5ff Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 6 Feb 2014 20:18:00 +0100 Subject: [PATCH 16/16] Release LibGit2Sharp v0.16.0 --- CHANGES.md | 16 ++++++++++++++++ LICENSE.md | 2 +- LibGit2Sharp/Properties/AssemblyInfo.cs | 6 +++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1c35dddd5..507c48710 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,22 @@ - CI server: - @libgit2sharp: +## v0.16.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.15.0...v0.16.0)) + +### Additions + + - Introduce Repository.Merge() (#608 and #620) + - Teach Diff.Compare<>() to return a PatchStats (#610) + +### Changes + + - Speed up NuGet post build copy of the native binaries (#613) + +### Fixes + + - Fix Remotes.Add(name, url, refspec) to prevent the creation of a default fetch refspec beside the passed in one (#614) + - Make LibGit2SharpException.Data expose the correct libgit2 error categories (#601) + ## v0.15.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.14.1...v0.15.0)) ### Additions diff --git a/LICENSE.md b/LICENSE.md index f2da33d8b..2f4d091cd 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2011-2013 LibGit2Sharp contributors +Copyright (c) 2011-2014 LibGit2Sharp contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LibGit2Sharp/Properties/AssemblyInfo.cs b/LibGit2Sharp/Properties/AssemblyInfo.cs index 4fac73f6b..61d00bc33 100644 --- a/LibGit2Sharp/Properties/AssemblyInfo.cs +++ b/LibGit2Sharp/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ #endif [assembly: AssemblyProduct("LibGit2Sharp")] -[assembly: AssemblyCopyright("Copyright © 2011-2013 LibGit2Sharp contributors")] +[assembly: AssemblyCopyright("Copyright © 2011-2014 LibGit2Sharp contributors")] [assembly: CLSCompliant(true)] @@ -42,5 +42,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.15.0")] -[assembly: AssemblyFileVersion("0.15.0")] +[assembly: AssemblyVersion("0.16.0")] +[assembly: AssemblyFileVersion("0.16.0")]