diff --git a/LibGit2Sharp.Tests/CommitFixture.cs b/LibGit2Sharp.Tests/CommitFixture.cs index 77174467d..adaaa1573 100644 --- a/LibGit2Sharp.Tests/CommitFixture.cs +++ b/LibGit2Sharp.Tests/CommitFixture.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using LibGit2Sharp.Core; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -1055,5 +1056,118 @@ public void CanPrettifyAMessage() Assert.Equal(expected, Commit.PrettifyMessage(input, '#')); Assert.Equal(expected, Commit.PrettifyMessage(input.Replace('#', ';'), ';')); } + + private readonly string signedCommit = @"tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6 +parent 34734e478d6cf50c27c9d69026d93974d052c454 +author Ben Burkert 1358451456 -0800 +committer Ben Burkert 1358451456 -0800 +gpgsig -----BEGIN PGP SIGNATURE----- + Version: GnuPG v1.4.12 (Darwin) + + iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al + o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8 + JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq + AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq + SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW + who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok + 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG + cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu + c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9 + ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J + 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc + cpxtDQQMGYFpXK/71stq + =ozeK + -----END PGP SIGNATURE----- + +a simple commit which works +"; + + private readonly string signatureData = @"-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.12 (Darwin) + +iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al +o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8 +JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq +AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq +SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW +who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok +6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG +cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu +c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9 +ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J +7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc +cpxtDQQMGYFpXK/71stq +=ozeK +-----END PGP SIGNATURE-----"; + + private readonly string signedData = @"tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6 +parent 34734e478d6cf50c27c9d69026d93974d052c454 +author Ben Burkert 1358451456 -0800 +committer Ben Burkert 1358451456 -0800 + +a simple commit which works +"; + + + [Fact] + public void CanExtractSignatureFromCommit() + { + string repoPath = InitNewRepository(); + using (var repo = new Repository(repoPath)) + { + var odb = repo.ObjectDatabase; + var signedId = odb.Write(Encoding.UTF8.GetBytes(signedCommit)); + + // Look up the commit to make sure we wrote something valid + var commit = repo.Lookup(signedId); + Assert.Equal("a simple commit which works\n", commit.Message); + + var signatureInfo = Commit.ExtractSignature(repo, signedId, "gpgsig"); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + + signatureInfo = Commit.ExtractSignature(repo, signedId); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + } + } + + [Fact] + public void CanCreateACommitString() + { + string repoPath = SandboxStandardTestRepo(); + using (var repo = new Repository(repoPath)) + { + var tipCommit = repo.Head.Tip; + var recreatedCommit = Commit.CreateBuffer( + tipCommit.Author, + tipCommit.Committer, + tipCommit.Message, + tipCommit.Tree, + tipCommit.Parents, + false, null); + + var recreatedId = repo.ObjectDatabase.Write(Encoding.UTF8.GetBytes(recreatedCommit)); + Assert.Equal(tipCommit.Id, recreatedId); + } + } + + [Fact] + public void CanCreateASignedCommit() + { + string repoPath = InitNewRepository(); + using (var repo = new Repository(repoPath)) + { + var odb = repo.ObjectDatabase; + var signedId = odb.Write(Encoding.UTF8.GetBytes(signedCommit)); + var signedId2 = odb.CreateCommitWithSignature(signedData, signatureData); + + Assert.Equal(signedId, signedId2); + + var signatureInfo = Commit.ExtractSignature(repo, signedId2); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + } + } } } diff --git a/LibGit2Sharp/Commit.cs b/LibGit2Sharp/Commit.cs index 88b29621d..357567d8a 100644 --- a/LibGit2Sharp/Commit.cs +++ b/LibGit2Sharp/Commit.cs @@ -138,6 +138,68 @@ private string DebuggerDisplay } } + /// + /// Extract the signature data from this commit + /// + /// The signature and the signed data + /// The repository in which the object lives + /// The commit to extract the signature from + /// The header field which contains the signature; use null for the default of "gpgsig" + public static SignatureInfo ExtractSignature(Repository repo, ObjectId id, string field) + { + return Proxy.git_commit_extract_signature(repo.Handle, id, field); + } + + /// + /// Extract the signature data from this commit + /// + /// The overload uses the default header field "gpgsig" + /// + /// + /// The signature and the signed data + /// The repository in which the object lives + /// The commit to extract the signature from + public static SignatureInfo ExtractSignature(Repository repo, ObjectId id) + { + return Proxy.git_commit_extract_signature(repo.Handle, id, null); + } + + /// + /// Create a commit in-memory + /// + /// Prettifing the message includes: + /// * Removing empty lines from the beginning and end. + /// * Removing trailing spaces from every line. + /// * Turning multiple consecutive empty lines between paragraphs into just one empty line. + /// * Ensuring the commit message ends with a newline. + /// * Removing every line starting with the . + /// + /// + /// The of who made the change. + /// The of who added the change to the repository. + /// The description of why a change was made to the repository. + /// The of the to be created. + /// The parents of the to be created. + /// True to prettify the message, or false to leave it as is. + /// When non null, lines starting with this character will be stripped if prettifyMessage is true. + /// The contents of the commit object. + public static string CreateBuffer(Signature author, Signature committer, string message, Tree tree, IEnumerable parents, bool prettifyMessage, char? commentChar) + { + Ensure.ArgumentNotNull(message, "message"); + Ensure.ArgumentDoesNotContainZeroByte(message, "message"); + Ensure.ArgumentNotNull(author, "author"); + Ensure.ArgumentNotNull(committer, "committer"); + Ensure.ArgumentNotNull(tree, "tree"); + Ensure.ArgumentNotNull(parents, "parents"); + + if (prettifyMessage) + { + message = Proxy.git_message_prettify(message, commentChar); + } + + return Proxy.git_commit_create_buffer(tree.repo.Handle, author, committer, message, tree, parents.ToArray()); + } + private class ParentsCollection : ICollection { private readonly Lazy> _parents; diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 4b40088e8..846617f2c 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -316,6 +316,27 @@ internal static extern unsafe int git_commit_create_from_ids( UIntPtr parentCount, [MarshalAs(UnmanagedType.LPArray)] [In] IntPtr[] parents); + [DllImport(libgit2)] + internal static extern unsafe int git_commit_create_buffer( + GitBuf res, + git_repository* repo, + git_signature* author, + git_signature* committer, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string encoding, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, + git_object* tree, + UIntPtr parent_count, + IntPtr* parents /* git_commit** originally */); + + [DllImport(libgit2)] + internal static extern unsafe int git_commit_create_with_signature( + out GitOid id, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string commit_content, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string signature, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string signature_field); + + [DllImport(libgit2)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern unsafe string git_commit_message(git_object* commit); @@ -337,6 +358,14 @@ internal static extern unsafe int git_commit_create_from_ids( [DllImport(libgit2)] internal static extern unsafe git_oid* git_commit_tree_id(git_object* commit); + [DllImport(libgit2)] + internal static extern unsafe int git_commit_extract_signature( + GitBuf signature, + GitBuf signed_data, + git_repository *repo, + ref GitOid commit_id, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string field); + [DllImport(libgit2)] internal static extern unsafe int git_config_delete_entry( git_config* cfg, @@ -919,6 +948,9 @@ internal static extern unsafe int git_odb_foreach( [DllImport(libgit2)] internal static extern unsafe void git_odb_stream_free(git_odb_stream* stream); + [DllImport(libgit2)] + internal static extern unsafe int git_odb_write(out GitOid id, git_odb *odb, byte* data, UIntPtr len, GitObjectType type); + [DllImport(libgit2)] internal static extern unsafe git_oid* git_object_id(git_object* obj); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 34a507298..fbf7d2b3e 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -388,6 +388,61 @@ public static unsafe ObjectId git_commit_create( } } + public static unsafe string git_commit_create_buffer( + RepositoryHandle repo, + Signature author, + Signature committer, + string message, + Tree tree, + Commit[] parents) + { + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) + using (var treeHandle = Proxy.git_object_lookup(tree.repo.Handle, tree.Id, GitObjectType.Tree)) + using (var buf = new GitBuf()) + { + ObjectHandle[] handles = new ObjectHandle[0]; + try + { + handles = parents.Select(c => Proxy.git_object_lookup(c.repo.Handle, c.Id, GitObjectType.Commit)).ToArray(); + var ptrs = handles.Select(p => p.AsIntPtr()).ToArray(); + int res; + fixed(IntPtr* objs = ptrs) + { + res = NativeMethods.git_commit_create_buffer(buf, + repo, + authorHandle, + committerHandle, + null, + message, + treeHandle, + new UIntPtr((ulong)parents.LongCount()), + objs); + } + Ensure.ZeroResult(res); + } + finally + { + foreach (var handle in handles) + { + handle.Dispose(); + } + } + + return LaxUtf8Marshaler.FromNative(buf.ptr); + } + } + + public static unsafe ObjectId git_commit_create_with_signature(RepositoryHandle repo, string commitContent, + string signature, string field) + { + GitOid id; + int res = NativeMethods.git_commit_create_with_signature(out id, repo, commitContent, signature, field); + Ensure.ZeroResult(res); + + return id; + } + public static unsafe string git_commit_message(ObjectHandle obj) { return NativeMethods.git_commit_message(obj); @@ -426,6 +481,22 @@ public static unsafe ObjectId git_commit_tree_id(ObjectHandle obj) return ObjectId.BuildFromPtr(NativeMethods.git_commit_tree_id(obj)); } + public static unsafe SignatureInfo git_commit_extract_signature(RepositoryHandle repo, ObjectId id, string field) + { + using (var signature = new GitBuf()) + using (var signedData = new GitBuf()) + { + var oid = id.Oid; + Ensure.ZeroResult(NativeMethods.git_commit_extract_signature(signature, signedData, repo, ref oid, field)); + + return new SignatureInfo() + { + Signature = LaxUtf8Marshaler.FromNative(signature.ptr, signature.size.ConvertToInt()), + SignedData = LaxUtf8Marshaler.FromNative(signedData.ptr, signedData.size.ConvertToInt()), + }; + } + } + #endregion #region git_config_ @@ -1510,6 +1581,19 @@ public static unsafe ObjectId git_odb_stream_finalize_write(OdbStreamHandle stre return id; } + public static unsafe ObjectId git_odb_write(ObjectDatabaseHandle odb, byte[] data, ObjectType type) + { + GitOid id; + int res; + fixed(byte* p = data) + { + res = NativeMethods.git_odb_write(out id, odb, p, new UIntPtr((ulong)data.LongLength), type.ToGitObjectType()); + } + Ensure.ZeroResult(res); + + return id; + } + #endregion #region git_patch_ diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index 6530f212c..2412003d8 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -28,7 +28,7 @@ public abstract class GitObject : IEquatable, IBelongToARepository /// /// The containing the object. /// - protected readonly Repository repo; + internal readonly Repository repo; /// /// Needed for mocking purposes. diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 4854ba280..1b737bf3e 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -355,6 +355,7 @@ + diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index aac0c7646..920811157 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -177,6 +177,16 @@ public int Provider(IntPtr content, int max_length, IntPtr data) } } + /// + /// Write an object to the object database + /// + /// The contents of the object + /// The type of object to write + public virtual ObjectId Write(byte[] data) where T : GitObject + { + return Proxy.git_odb_write(handle, data, GitObject.TypeToKindMap[typeof(T)]); + } + /// /// Inserts a into the object database, created from the content of a stream. /// Optionally, git filters will be applied to the content before storing it. @@ -412,6 +422,32 @@ public virtual Commit CreateCommit(Signature author, Signature committer, string return commit; } + /// + /// Inserts a into the object database after attaching the given signature. + /// + /// The raw unsigned commit + /// The signature data + /// The header field in the commit in which to store the signature + /// The created . + public virtual ObjectId CreateCommitWithSignature(string commitContent, string signature, string field) + { + return Proxy.git_commit_create_with_signature(repo.Handle, commitContent, signature, field); + } + + /// + /// Inserts a into the object database after attaching the given signature. + /// + /// This overload uses the default header field of "gpgsig" + /// + /// + /// The raw unsigned commit + /// The signature data + /// The created . + public virtual ObjectId CreateCommitWithSignature(string commitContent, string signature) + { + return Proxy.git_commit_create_with_signature(repo.Handle, commitContent, signature, null); + } + /// /// Inserts a into the object database, pointing to a specific . /// diff --git a/LibGit2Sharp/SignatureInfo.cs b/LibGit2Sharp/SignatureInfo.cs new file mode 100644 index 000000000..71db67a7b --- /dev/null +++ b/LibGit2Sharp/SignatureInfo.cs @@ -0,0 +1,20 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Structure for holding a signature extracted from a commit or a tag + /// + public struct SignatureInfo + { + /// + /// The signature data, PGP/GPG or otherwise. + /// + public string Signature; + /// + /// The data which was signed. The object contents without the signature part. + /// + public string SignedData; + } +} +