18
18
#import " NSError+Git.h"
19
19
20
20
#import " git2/errors.h"
21
+ #import " git2/remote.h"
22
+ #import " git2/push.h"
21
23
22
24
NSString *const GTRepositoryRemoteOptionsCredentialProvider = @" GTRepositoryRemoteOptionsCredentialProvider" ;
23
25
26
+ typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *stats, BOOL *stop);
27
+ typedef void (^GTRemotePushTransferProgressBlock)(unsigned int current, unsigned int total, size_t bytes, BOOL *stop);
28
+
24
29
@implementation GTRepository (RemoteOperations)
25
30
26
31
#pragma mark -
27
32
#pragma mark Common Remote code
28
33
29
- typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *stats, BOOL *stop);
30
-
31
34
typedef struct {
32
35
GTCredentialAcquireCallbackInfo credProvider;
33
36
__unsafe_unretained GTRemoteFetchTransferProgressBlock fetchProgressBlock;
34
- __unsafe_unretained GTRemoteFetchTransferProgressBlock pushProgressBlock;
37
+ __unsafe_unretained GTRemotePushTransferProgressBlock pushProgressBlock;
35
38
git_direction direction;
36
39
} GTRemoteConnectionInfo;
37
40
@@ -46,6 +49,25 @@ int GTRemoteFetchTransferProgressCallback(const git_transfer_progress *stats, vo
46
49
return (stop == YES ? GIT_EUSER : 0 );
47
50
}
48
51
52
+ int GTRemotePushTransferProgressCallback (unsigned int current, unsigned int total, size_t bytes, void *payload) {
53
+ GTRemoteConnectionInfo *pushPayload = payload;
54
+
55
+ BOOL stop = NO ;
56
+ if (pushPayload->pushProgressBlock ) {
57
+ pushPayload->pushProgressBlock (current, total, bytes, &stop);
58
+ }
59
+
60
+ return (stop == YES ? GIT_EUSER : 0 );
61
+ }
62
+
63
+ static int GTRemotePushRefspecStatusCallback (const char *ref, const char *msg, void *data) {
64
+ if (msg != NULL ) {
65
+ return GIT_ERROR;
66
+ }
67
+
68
+ return GIT_OK;
69
+ }
70
+
49
71
#pragma mark -
50
72
#pragma mark Fetch
51
73
@@ -145,4 +167,136 @@ - (NSArray *)fetchHeadEntriesWithError:(NSError **)error {
145
167
return entries;
146
168
}
147
169
170
+ #pragma mark - Push (Public)
171
+
172
+ - (BOOL )pushBranch : (GTBranch *)branch toRemote : (GTRemote *)remote withOptions : (NSDictionary *)options error : (NSError **)error progress : (GTRemotePushTransferProgressBlock)progressBlock {
173
+ NSParameterAssert (branch != nil );
174
+ NSParameterAssert (remote != nil );
175
+
176
+ return [self pushBranches: @[ branch ] toRemote: remote withOptions: options error: error progress: progressBlock];
177
+ }
178
+
179
+ - (BOOL )pushBranches : (NSArray *)branches toRemote : (GTRemote *)remote withOptions : (NSDictionary *)options error : (NSError **)error progress : (GTRemotePushTransferProgressBlock)progressBlock {
180
+ NSParameterAssert (branches != nil );
181
+ NSParameterAssert (branches.count != 0 );
182
+ NSParameterAssert (remote != nil );
183
+
184
+ NSMutableArray *refspecs = nil ;
185
+ // Build refspecs for the passed in branches
186
+ refspecs = [NSMutableArray arrayWithCapacity: branches.count];
187
+ for (GTBranch *branch in branches) {
188
+ // Default remote reference for when branch doesn't exist on remote - create with same short name
189
+ NSString *remoteBranchReference = [NSString stringWithFormat: @" refs/heads/%@ " , branch.shortName];
190
+
191
+ BOOL success = NO ;
192
+ GTBranch *trackingBranch = [branch trackingBranchWithError: error success: &success];
193
+
194
+ if (success && trackingBranch) {
195
+ // Use remote branch short name from trackingBranch, which could be different
196
+ // (e.g. refs/heads/master:refs/heads/my_master)
197
+ remoteBranchReference = [NSString stringWithFormat: @" refs/heads/%@ " , trackingBranch.shortName];
198
+ }
199
+
200
+ [refspecs addObject: [NSString stringWithFormat: @" refs/heads/%@ :%@ " , branch.shortName, remoteBranchReference]];
201
+ }
202
+
203
+ return [self pushRefspecs: refspecs toRemote: remote withOptions: options error: error progress: progressBlock];
204
+ }
205
+
206
+ #pragma mark - Push (Private)
207
+
208
+ - (BOOL )pushRefspecs : (NSArray *)refspecs toRemote : (GTRemote *)remote withOptions : (NSDictionary *)options error : (NSError **)error progress : (GTRemotePushTransferProgressBlock)progressBlock {
209
+ int gitError;
210
+ GTCredentialProvider *credProvider = options[GTRepositoryRemoteOptionsCredentialProvider];
211
+
212
+ GTRemoteConnectionInfo connectionInfo = {
213
+ .credProvider = { .credProvider = credProvider },
214
+ .direction = GIT_DIRECTION_PUSH,
215
+ .pushProgressBlock = progressBlock,
216
+ };
217
+
218
+ git_remote_callbacks remote_callbacks = GIT_REMOTE_CALLBACKS_INIT;
219
+ remote_callbacks.credentials = (credProvider != nil ? GTCredentialAcquireCallback : NULL ),
220
+ remote_callbacks.transfer_progress = GTRemoteFetchTransferProgressCallback,
221
+ remote_callbacks.payload = &connectionInfo,
222
+
223
+ gitError = git_remote_set_callbacks (remote.git_remote , &remote_callbacks);
224
+ if (gitError != GIT_OK) {
225
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Failed to set callbacks on remote" ];
226
+ return NO ;
227
+ }
228
+
229
+ gitError = git_remote_connect (remote.git_remote , GIT_DIRECTION_PUSH);
230
+ if (gitError != GIT_OK) {
231
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Failed to connect remote" ];
232
+ return NO ;
233
+ }
234
+ @onExit {
235
+ git_remote_disconnect (remote.git_remote );
236
+ // Clear out callbacks by overwriting with an effectively empty git_remote_callbacks struct
237
+ git_remote_set_callbacks (remote.git_remote , &((git_remote_callbacks)GIT_REMOTE_CALLBACKS_INIT));
238
+ };
239
+
240
+ git_push *push;
241
+ gitError = git_push_new (&push, remote.git_remote );
242
+ if (gitError != GIT_OK) {
243
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Push object creation failed" failureReason: @" Failed to create push object for remote \" %@ \" " , self ];
244
+ return NO ;
245
+ }
246
+ @onExit {
247
+ git_push_free (push);
248
+ };
249
+
250
+ git_push_options push_options = GIT_PUSH_OPTIONS_INIT;
251
+
252
+ gitError = git_push_set_options (push, &push_options);
253
+ if (gitError != GIT_OK) {
254
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Failed to add options" ];
255
+ return NO ;
256
+ }
257
+
258
+ GTRemoteConnectionInfo payload = {
259
+ .pushProgressBlock = progressBlock,
260
+ };
261
+ gitError = git_push_set_callbacks (push, NULL , NULL , GTRemotePushTransferProgressCallback, &payload);
262
+ if (gitError != GIT_OK) {
263
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Setting push callbacks failed" ];
264
+ return NO ;
265
+ }
266
+
267
+ for (NSString *refspec in refspecs) {
268
+ gitError = git_push_add_refspec (push, refspec.UTF8String );
269
+ if (gitError != GIT_OK) {
270
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Adding reference failed" failureReason: @" Failed to add refspec \" %@ \" to push object" , refspec];
271
+ return NO ;
272
+ }
273
+ }
274
+
275
+ gitError = git_push_finish (push);
276
+ if (gitError != GIT_OK) {
277
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Push to remote failed" ];
278
+ return NO ;
279
+ }
280
+
281
+ int unpackSuccessful = git_push_unpack_ok (push);
282
+ if (unpackSuccessful == 0 ) {
283
+ if (error != NULL ) *error = [NSError errorWithDomain: GTGitErrorDomain code: GIT_ERROR userInfo: @{ NSLocalizedDescriptionKey : @" Unpacking failed" }];
284
+ return NO ;
285
+ }
286
+
287
+ gitError = git_push_update_tips (push, self.userSignatureForNow .git_signature , NULL );
288
+ if (gitError != GIT_OK) {
289
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" Update tips failed" ];
290
+ return NO ;
291
+ }
292
+
293
+ gitError = git_push_status_foreach (push, GTRemotePushRefspecStatusCallback, NULL );
294
+ if (gitError != GIT_OK) {
295
+ if (error != NULL ) *error = [NSError git_errorFor: gitError description: @" One or references failed to update" ];
296
+ return NO ;
297
+ }
298
+
299
+ return YES ;
300
+ }
301
+
148
302
@end
0 commit comments