Skip to content

Adding Pre-Push Callback Support #1061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions LibGit2Sharp.Tests/PushFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LibGit2Sharp.Handlers;
Expand Down Expand Up @@ -78,6 +79,63 @@ public void CanPushABranchTrackingAnUpstreamBranch()
Assert.True(packBuilderCalled);
}

[Fact]
public void CanInvokePrePushCallbackAndSucceed()
{
bool packBuilderCalled = false;
bool prePushHandlerCalled = false;
PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; };
PrePushHandler prePushHook = (IEnumerable<PushUpdate> updates) =>
{
Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count());
prePushHandlerCalled = true;
return true;
};

AssertPush(repo => repo.Network.Push(repo.Head));
AssertPush(repo => repo.Network.Push(repo.Branches["master"]));

PushOptions options = new PushOptions()
{
OnPushStatusError = OnPushStatusError,
OnPackBuilderProgress = packBuilderCb,
OnNegotiationCompletedBeforePush = prePushHook,
};

AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options));
Assert.True(packBuilderCalled);
Assert.True(prePushHandlerCalled);
}

[Fact]
public void CanInvokePrePushCallbackAndFail()
{
bool packBuilderCalled = false;
bool prePushHandlerCalled = false;
PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; };
PrePushHandler prePushHook = (IEnumerable<PushUpdate> updates) =>
{
Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count());
prePushHandlerCalled = true;
return false;
};

AssertPush(repo => repo.Network.Push(repo.Head));
AssertPush(repo => repo.Network.Push(repo.Branches["master"]));

PushOptions options = new PushOptions()
{
OnPushStatusError = OnPushStatusError,
OnPackBuilderProgress = packBuilderCb,
OnNegotiationCompletedBeforePush = prePushHook
};

Assert.Throws<UserCancelledException>(() => { AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); });

Assert.False(packBuilderCalled);
Assert.True(prePushHandlerCalled);
}

[Fact]
public void PushingABranchThatDoesNotTrackAnUpstreamBranchThrows()
{
Expand Down
10 changes: 5 additions & 5 deletions LibGit2Sharp/Core/GitPushUpdate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
namespace LibGit2Sharp.Core
{
[StructLayout(LayoutKind.Sequential)]
internal class GitPushUpdate
internal struct GitPushUpdate
{
IntPtr src_refname;
IntPtr dst_refname;
GitOid src;
GitOid dst;
public IntPtr src_refname;
public IntPtr dst_refname;
public GitOid src;
public GitOid dst;
}
}
5 changes: 2 additions & 3 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1141,10 +1141,9 @@ internal delegate int remote_update_tips_callback(
IntPtr data);

internal delegate int push_negotiation_callback(
IntPtr updates, // GitPushUpdate?
IntPtr updates,
UIntPtr len,
IntPtr payload
);
IntPtr payload);

internal delegate int push_update_reference_callback(
IntPtr refName,
Expand Down
9 changes: 9 additions & 0 deletions LibGit2Sharp/Handlers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;

namespace LibGit2Sharp.Handlers
{
/// <summary>
Expand Down Expand Up @@ -71,6 +73,13 @@ namespace LibGit2Sharp.Handlers
/// <returns>True to continue, false to cancel.</returns>
public delegate bool PackBuilderProgressHandler(PackBuilderStage stage, int current, int total);

/// <summary>
/// Provides information about what updates will be performed before a push occurs
/// </summary>
/// <param name="updates">List of updates about to be performed via push</param>
/// <returns>True to continue, false to cancel.</returns>
public delegate bool PrePushHandler(IEnumerable<PushUpdate> updates);

/// <summary>
/// Delegate definition to handle reporting errors when updating references on the remote.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
<Compile Include="PatchStats.cs" />
<Compile Include="PeelException.cs" />
<Compile Include="PullOptions.cs" />
<Compile Include="PushUpdate.cs" />
<Compile Include="RecurseSubmodulesException.cs" />
<Compile Include="RefSpec.cs" />
<Compile Include="RefSpecCollection.cs" />
Expand Down
6 changes: 6 additions & 0 deletions LibGit2Sharp/PushOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@ public sealed class PushOptions
/// be more than once every 0.5 seconds (in general).
/// </summary>
public PackBuilderProgressHandler OnPackBuilderProgress { get; set; }

/// <summary>
/// Called once between the negotiation step and the upload. It provides
/// information about what updates will be performed.
/// </summary>
public PrePushHandler OnNegotiationCompletedBeforePush { get; set; }
}
}
54 changes: 54 additions & 0 deletions LibGit2Sharp/PushUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Runtime.InteropServices;
using LibGit2Sharp.Core;

namespace LibGit2Sharp
{
/// <summary>
/// Represents an update which will be performed on the remote during push
/// </summary>
public class PushUpdate
{
internal PushUpdate(string srcRefName, ObjectId srcOid, string dstRefName, ObjectId dstOid)
{
DestinationObjectId = dstOid;
DestinationRefName = dstRefName;
SourceObjectId = srcOid;
SourceRefName = srcRefName;
}
internal PushUpdate(GitPushUpdate update)
{
DestinationObjectId = update.dst;
DestinationRefName = LaxUtf8Marshaler.FromNative(update.dst_refname);
SourceObjectId = update.src;
SourceRefName = LaxUtf8Marshaler.FromNative(update.src_refname);
}
/// <summary>
/// Empty constructor to support test suites
/// </summary>
protected PushUpdate()
{
DestinationObjectId = ObjectId.Zero;
DestinationRefName = String.Empty;
SourceObjectId = ObjectId.Zero;
SourceRefName = String.Empty;
}

/// <summary>
/// The source name of the reference
/// </summary>
public readonly string SourceRefName;
/// <summary>
/// The name of the reference to update on the server
/// </summary>
public readonly string DestinationRefName;
/// <summary>
/// The current target of the reference
/// </summary>
public readonly ObjectId SourceObjectId;
/// <summary>
/// The new target for the reference
/// </summary>
public readonly ObjectId DestinationObjectId;
}
}
58 changes: 56 additions & 2 deletions LibGit2Sharp/RemoteCallbacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal RemoteCallbacks(PushOptions pushOptions)
PackBuilderProgress = pushOptions.OnPackBuilderProgress;
CredentialsProvider = pushOptions.CredentialsProvider;
PushStatusError = pushOptions.OnPushStatusError;
PrePushCallback = pushOptions.OnNegotiationCompletedBeforePush;
}

internal RemoteCallbacks(FetchOptionsBase fetchOptions)
Expand Down Expand Up @@ -77,6 +78,11 @@ internal RemoteCallbacks(FetchOptionsBase fetchOptions)
/// </summary>
private readonly PackBuilderProgressHandler PackBuilderProgress;

/// <summary>
/// Called during remote push operation after negotiation, before upload
/// </summary>
private readonly PrePushHandler PrePushCallback;

#endregion

/// <summary>
Expand All @@ -86,7 +92,7 @@ internal RemoteCallbacks(FetchOptionsBase fetchOptions)

internal GitRemoteCallbacks GenerateCallbacks()
{
var callbacks = new GitRemoteCallbacks {version = 1};
var callbacks = new GitRemoteCallbacks { version = 1 };

if (Progress != null)
{
Expand Down Expand Up @@ -123,6 +129,11 @@ internal GitRemoteCallbacks GenerateCallbacks()
callbacks.pack_progress = GitPackbuilderProgressHandler;
}

if (PrePushCallback != null)
{
callbacks.push_negotiation = GitPushNegotiationHandler;
}

return callbacks;
}

Expand Down Expand Up @@ -185,7 +196,7 @@ private int GitUpdateTipsHandler(IntPtr str, ref GitOid oldId, ref GitOid newId,
/// <returns>0 on success; a negative value to abort the process.</returns>
private int GitPushUpdateReference(IntPtr str, IntPtr status, IntPtr data)
{
PushStatusErrorHandler onPushError = PushStatusError;
PushStatusErrorHandler onPushError = PushStatusError;

if (onPushError != null)
{
Expand Down Expand Up @@ -262,6 +273,49 @@ private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFro
return cred.GitCredentialHandler(out ptr);
}

private int GitPushNegotiationHandler(IntPtr updates, UIntPtr len, IntPtr payload)
{
if (updates == IntPtr.Zero)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the method that is called by native code? I don't think we want to throw back into native code.

{
return (int)GitErrorCode.Error;
}

bool result = false;
try
{

int length = len.ConvertToInt();
PushUpdate[] pushUpdates = new PushUpdate[length];

unsafe
{
IntPtr* ptr = (IntPtr*)updates.ToPointer();

for (int i = 0; i < length; i++)
{
if (ptr[i] == IntPtr.Zero)
{
throw new NullReferenceException("Unexpected null git_push_update pointer was encountered");
}

GitPushUpdate gitPushUpdate = ptr[i].MarshalAs<GitPushUpdate>();
PushUpdate pushUpdate = new PushUpdate(gitPushUpdate);
pushUpdates[i] = pushUpdate;
}

result = PrePushCallback(pushUpdates);
}
}
catch (Exception exception)
{
Log.Write(LogLevel.Error, exception.ToString());
Proxy.giterr_set_str(GitErrorCategory.Callback, exception);
result = false;
}

return Proxy.ConvertResultToCancelFlag(result);
}

#endregion
}
}