1818#import " NSError+Git.h"
1919
2020#import " git2/errors.h"
21+ #import " git2/remote.h"
22+ #import " git2/push.h"
2123
2224NSString *const GTRepositoryRemoteOptionsCredentialProvider = @" GTRepositoryRemoteOptionsCredentialProvider" ;
2325
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+
2429@implementation GTRepository (RemoteOperations)
2530
2631#pragma mark -
2732#pragma mark Common Remote code
2833
29- typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *stats, BOOL *stop);
30-
3134typedef struct {
3235 GTCredentialAcquireCallbackInfo credProvider;
3336 __unsafe_unretained GTRemoteFetchTransferProgressBlock fetchProgressBlock;
34- __unsafe_unretained GTRemoteFetchTransferProgressBlock pushProgressBlock;
37+ __unsafe_unretained GTRemotePushTransferProgressBlock pushProgressBlock;
3538 git_direction direction;
3639} GTRemoteConnectionInfo;
3740
@@ -46,6 +49,25 @@ int GTRemoteFetchTransferProgressCallback(const git_transfer_progress *stats, vo
4649 return (stop == YES ? GIT_EUSER : 0 );
4750}
4851
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+
4971#pragma mark -
5072#pragma mark Fetch
5173
@@ -145,4 +167,136 @@ - (NSArray *)fetchHeadEntriesWithError:(NSError **)error {
145167 return entries;
146168}
147169
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+
148302@end
0 commit comments