Skip to content

Commit a4f7831

Browse files
committed
Merge pull request #1061 from whoisj/pre-push-hook-support
Adding Pre-Push Callback Support
2 parents 62c0763 + ab895be commit a4f7831

File tree

8 files changed

+191
-10
lines changed

8 files changed

+191
-10
lines changed

LibGit2Sharp.Tests/PushFixture.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using LibGit2Sharp.Handlers;
@@ -78,6 +79,63 @@ public void CanPushABranchTrackingAnUpstreamBranch()
7879
Assert.True(packBuilderCalled);
7980
}
8081

82+
[Fact]
83+
public void CanInvokePrePushCallbackAndSucceed()
84+
{
85+
bool packBuilderCalled = false;
86+
bool prePushHandlerCalled = false;
87+
PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; };
88+
PrePushHandler prePushHook = (IEnumerable<PushUpdate> updates) =>
89+
{
90+
Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count());
91+
prePushHandlerCalled = true;
92+
return true;
93+
};
94+
95+
AssertPush(repo => repo.Network.Push(repo.Head));
96+
AssertPush(repo => repo.Network.Push(repo.Branches["master"]));
97+
98+
PushOptions options = new PushOptions()
99+
{
100+
OnPushStatusError = OnPushStatusError,
101+
OnPackBuilderProgress = packBuilderCb,
102+
OnNegotiationCompletedBeforePush = prePushHook,
103+
};
104+
105+
AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options));
106+
Assert.True(packBuilderCalled);
107+
Assert.True(prePushHandlerCalled);
108+
}
109+
110+
[Fact]
111+
public void CanInvokePrePushCallbackAndFail()
112+
{
113+
bool packBuilderCalled = false;
114+
bool prePushHandlerCalled = false;
115+
PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; };
116+
PrePushHandler prePushHook = (IEnumerable<PushUpdate> updates) =>
117+
{
118+
Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count());
119+
prePushHandlerCalled = true;
120+
return false;
121+
};
122+
123+
AssertPush(repo => repo.Network.Push(repo.Head));
124+
AssertPush(repo => repo.Network.Push(repo.Branches["master"]));
125+
126+
PushOptions options = new PushOptions()
127+
{
128+
OnPushStatusError = OnPushStatusError,
129+
OnPackBuilderProgress = packBuilderCb,
130+
OnNegotiationCompletedBeforePush = prePushHook
131+
};
132+
133+
Assert.Throws<UserCancelledException>(() => { AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); });
134+
135+
Assert.False(packBuilderCalled);
136+
Assert.True(prePushHandlerCalled);
137+
}
138+
81139
[Fact]
82140
public void PushingABranchThatDoesNotTrackAnUpstreamBranchThrows()
83141
{

LibGit2Sharp/Core/GitPushUpdate.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
namespace LibGit2Sharp.Core
55
{
66
[StructLayout(LayoutKind.Sequential)]
7-
internal class GitPushUpdate
7+
internal struct GitPushUpdate
88
{
9-
IntPtr src_refname;
10-
IntPtr dst_refname;
11-
GitOid src;
12-
GitOid dst;
9+
public IntPtr src_refname;
10+
public IntPtr dst_refname;
11+
public GitOid src;
12+
public GitOid dst;
1313
}
1414
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,10 +1141,9 @@ internal delegate int remote_update_tips_callback(
11411141
IntPtr data);
11421142

11431143
internal delegate int push_negotiation_callback(
1144-
IntPtr updates, // GitPushUpdate?
1144+
IntPtr updates,
11451145
UIntPtr len,
1146-
IntPtr payload
1147-
);
1146+
IntPtr payload);
11481147

11491148
internal delegate int push_update_reference_callback(
11501149
IntPtr refName,

LibGit2Sharp/Handlers.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
24
namespace LibGit2Sharp.Handlers
35
{
46
/// <summary>
@@ -71,6 +73,13 @@ namespace LibGit2Sharp.Handlers
7173
/// <returns>True to continue, false to cancel.</returns>
7274
public delegate bool PackBuilderProgressHandler(PackBuilderStage stage, int current, int total);
7375

76+
/// <summary>
77+
/// Provides information about what updates will be performed before a push occurs
78+
/// </summary>
79+
/// <param name="updates">List of updates about to be performed via push</param>
80+
/// <returns>True to continue, false to cancel.</returns>
81+
public delegate bool PrePushHandler(IEnumerable<PushUpdate> updates);
82+
7483
/// <summary>
7584
/// Delegate definition to handle reporting errors when updating references on the remote.
7685
/// </summary>

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
<Compile Include="PatchStats.cs" />
122122
<Compile Include="PeelException.cs" />
123123
<Compile Include="PullOptions.cs" />
124+
<Compile Include="PushUpdate.cs" />
124125
<Compile Include="RecurseSubmodulesException.cs" />
125126
<Compile Include="RefSpec.cs" />
126127
<Compile Include="RefSpecCollection.cs" />

LibGit2Sharp/PushOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,11 @@ public sealed class PushOptions
3939
/// be more than once every 0.5 seconds (in general).
4040
/// </summary>
4141
public PackBuilderProgressHandler OnPackBuilderProgress { get; set; }
42+
43+
/// <summary>
44+
/// Called once between the negotiation step and the upload. It provides
45+
/// information about what updates will be performed.
46+
/// </summary>
47+
public PrePushHandler OnNegotiationCompletedBeforePush { get; set; }
4248
}
4349
}

LibGit2Sharp/PushUpdate.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using LibGit2Sharp.Core;
4+
5+
namespace LibGit2Sharp
6+
{
7+
/// <summary>
8+
/// Represents an update which will be performed on the remote during push
9+
/// </summary>
10+
public class PushUpdate
11+
{
12+
internal PushUpdate(string srcRefName, ObjectId srcOid, string dstRefName, ObjectId dstOid)
13+
{
14+
DestinationObjectId = dstOid;
15+
DestinationRefName = dstRefName;
16+
SourceObjectId = srcOid;
17+
SourceRefName = srcRefName;
18+
}
19+
internal PushUpdate(GitPushUpdate update)
20+
{
21+
DestinationObjectId = update.dst;
22+
DestinationRefName = LaxUtf8Marshaler.FromNative(update.dst_refname);
23+
SourceObjectId = update.src;
24+
SourceRefName = LaxUtf8Marshaler.FromNative(update.src_refname);
25+
}
26+
/// <summary>
27+
/// Empty constructor to support test suites
28+
/// </summary>
29+
protected PushUpdate()
30+
{
31+
DestinationObjectId = ObjectId.Zero;
32+
DestinationRefName = String.Empty;
33+
SourceObjectId = ObjectId.Zero;
34+
SourceRefName = String.Empty;
35+
}
36+
37+
/// <summary>
38+
/// The source name of the reference
39+
/// </summary>
40+
public readonly string SourceRefName;
41+
/// <summary>
42+
/// The name of the reference to update on the server
43+
/// </summary>
44+
public readonly string DestinationRefName;
45+
/// <summary>
46+
/// The current target of the reference
47+
/// </summary>
48+
public readonly ObjectId SourceObjectId;
49+
/// <summary>
50+
/// The new target for the reference
51+
/// </summary>
52+
public readonly ObjectId DestinationObjectId;
53+
}
54+
}

LibGit2Sharp/RemoteCallbacks.cs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal RemoteCallbacks(PushOptions pushOptions)
2828
PackBuilderProgress = pushOptions.OnPackBuilderProgress;
2929
CredentialsProvider = pushOptions.CredentialsProvider;
3030
PushStatusError = pushOptions.OnPushStatusError;
31+
PrePushCallback = pushOptions.OnNegotiationCompletedBeforePush;
3132
}
3233

3334
internal RemoteCallbacks(FetchOptionsBase fetchOptions)
@@ -77,6 +78,11 @@ internal RemoteCallbacks(FetchOptionsBase fetchOptions)
7778
/// </summary>
7879
private readonly PackBuilderProgressHandler PackBuilderProgress;
7980

81+
/// <summary>
82+
/// Called during remote push operation after negotiation, before upload
83+
/// </summary>
84+
private readonly PrePushHandler PrePushCallback;
85+
8086
#endregion
8187

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

8793
internal GitRemoteCallbacks GenerateCallbacks()
8894
{
89-
var callbacks = new GitRemoteCallbacks {version = 1};
95+
var callbacks = new GitRemoteCallbacks { version = 1 };
9096

9197
if (Progress != null)
9298
{
@@ -123,6 +129,11 @@ internal GitRemoteCallbacks GenerateCallbacks()
123129
callbacks.pack_progress = GitPackbuilderProgressHandler;
124130
}
125131

132+
if (PrePushCallback != null)
133+
{
134+
callbacks.push_negotiation = GitPushNegotiationHandler;
135+
}
136+
126137
return callbacks;
127138
}
128139

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

190201
if (onPushError != null)
191202
{
@@ -262,6 +273,49 @@ private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFro
262273
return cred.GitCredentialHandler(out ptr);
263274
}
264275

276+
private int GitPushNegotiationHandler(IntPtr updates, UIntPtr len, IntPtr payload)
277+
{
278+
if (updates == IntPtr.Zero)
279+
{
280+
return (int)GitErrorCode.Error;
281+
}
282+
283+
bool result = false;
284+
try
285+
{
286+
287+
int length = len.ConvertToInt();
288+
PushUpdate[] pushUpdates = new PushUpdate[length];
289+
290+
unsafe
291+
{
292+
IntPtr* ptr = (IntPtr*)updates.ToPointer();
293+
294+
for (int i = 0; i < length; i++)
295+
{
296+
if (ptr[i] == IntPtr.Zero)
297+
{
298+
throw new NullReferenceException("Unexpected null git_push_update pointer was encountered");
299+
}
300+
301+
GitPushUpdate gitPushUpdate = ptr[i].MarshalAs<GitPushUpdate>();
302+
PushUpdate pushUpdate = new PushUpdate(gitPushUpdate);
303+
pushUpdates[i] = pushUpdate;
304+
}
305+
306+
result = PrePushCallback(pushUpdates);
307+
}
308+
}
309+
catch (Exception exception)
310+
{
311+
Log.Write(LogLevel.Error, exception.ToString());
312+
Proxy.giterr_set_str(GitErrorCategory.Callback, exception);
313+
result = false;
314+
}
315+
316+
return Proxy.ConvertResultToCancelFlag(result);
317+
}
318+
265319
#endregion
266320
}
267321
}

0 commit comments

Comments
 (0)