Skip to content

Commit

Permalink
Merge pull request #1304 from libgit2/cmn/signatures
Browse files Browse the repository at this point in the history
Add signature-handling methods
  • Loading branch information
Edward Thomson committed Apr 27, 2016
2 parents ccee01a + 508807f commit 1fb7fc5
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 1 deletion.
114 changes: 114 additions & 0 deletions LibGit2Sharp.Tests/CommitFixture.cs
Expand Up @@ -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;
Expand Down Expand Up @@ -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 <ben@benburkert.com> 1358451456 -0800
committer Ben Burkert <ben@benburkert.com> 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 <ben@benburkert.com> 1358451456 -0800
committer Ben Burkert <ben@benburkert.com> 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<Commit>(Encoding.UTF8.GetBytes(signedCommit));

// Look up the commit to make sure we wrote something valid
var commit = repo.Lookup<Commit>(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<Commit>(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<Commit>(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);
}
}
}
}
62 changes: 62 additions & 0 deletions LibGit2Sharp/Commit.cs
Expand Up @@ -138,6 +138,68 @@ private string DebuggerDisplay
}
}

/// <summary>
/// Extract the signature data from this commit
/// </summary>
/// <returns>The signature and the signed data</returns>
/// <param name="repo">The repository in which the object lives</param>
/// <param name="id">The commit to extract the signature from</param>
/// <param name="field">The header field which contains the signature; use null for the default of "gpgsig"</param>
public static SignatureInfo ExtractSignature(Repository repo, ObjectId id, string field)
{
return Proxy.git_commit_extract_signature(repo.Handle, id, field);
}

/// <summary>
/// Extract the signature data from this commit
/// <para>
/// The overload uses the default header field "gpgsig"
/// </para>
/// </summary>
/// <returns>The signature and the signed data</returns>
/// <param name="repo">The repository in which the object lives</param>
/// <param name="id">The commit to extract the signature from</param>
public static SignatureInfo ExtractSignature(Repository repo, ObjectId id)
{
return Proxy.git_commit_extract_signature(repo.Handle, id, null);
}

/// <summary>
/// Create a commit in-memory
/// <para>
/// 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 <paramref name="commentChar"/>.
/// </para>
/// </summary>
/// <param name="author">The <see cref="Signature"/> of who made the change.</param>
/// <param name="committer">The <see cref="Signature"/> of who added the change to the repository.</param>
/// <param name="message">The description of why a change was made to the repository.</param>
/// <param name="tree">The <see cref="Tree"/> of the <see cref="Commit"/> to be created.</param>
/// <param name="parents">The parents of the <see cref="Commit"/> to be created.</param>
/// <param name="prettifyMessage">True to prettify the message, or false to leave it as is.</param>
/// <param name="commentChar">When non null, lines starting with this character will be stripped if prettifyMessage is true.</param>
/// <returns>The contents of the commit object.</returns>
public static string CreateBuffer(Signature author, Signature committer, string message, Tree tree, IEnumerable<Commit> 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<Commit>
{
private readonly Lazy<ICollection<Commit>> _parents;
Expand Down
32 changes: 32 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Expand Up @@ -316,6 +316,27 @@ static NativeMethods()
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);
Expand All @@ -337,6 +358,14 @@ static NativeMethods()
[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,
Expand Down Expand Up @@ -919,6 +948,9 @@ static NativeMethods()
[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);

Expand Down
84 changes: 84 additions & 0 deletions LibGit2Sharp/Core/Proxy.cs
Expand Up @@ -388,6 +388,61 @@ public static unsafe Signature git_commit_committer(ObjectHandle obj)
}
}

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);
Expand Down Expand Up @@ -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_
Expand Down Expand Up @@ -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_
Expand Down
2 changes: 1 addition & 1 deletion LibGit2Sharp/GitObject.cs
Expand Up @@ -28,7 +28,7 @@ public abstract class GitObject : IEquatable<GitObject>, IBelongToARepository
/// <summary>
/// The <see cref="Repository"/> containing the object.
/// </summary>
protected readonly Repository repo;
internal readonly Repository repo;

/// <summary>
/// Needed for mocking purposes.
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Expand Up @@ -355,6 +355,7 @@
<Compile Include="Commands\Stage.cs" />
<Compile Include="Commands\Remove.cs" />
<Compile Include="Commands\Checkout.cs" />
<Compile Include="SignatureInfo.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="CustomDictionary.xml" />
Expand Down

0 comments on commit 1fb7fc5

Please sign in to comment.