Skip to content

Commit 8828d01

Browse files
committed
Merge pull request #419 from phatblat/ben/pr/push
Add Push
2 parents 9f433d2 + 938c7e3 commit 8828d01

File tree

4 files changed

+421
-5
lines changed

4 files changed

+421
-5
lines changed

ObjectiveGit/GTRepository+RemoteOperations.h

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88

99
#import "GTRepository.h"
1010

11+
@class GTFetchHeadEntry;
12+
1113
/// A `GTCredentialProvider`, that will be used to authenticate against the remote.
1214
extern NSString *const GTRepositoryRemoteOptionsCredentialProvider;
1315

14-
@class GTFetchHeadEntry;
15-
1616
@interface GTRepository (RemoteOperations)
1717

18+
#pragma mark - Fetch
19+
1820
/// Fetch a remote.
1921
///
2022
/// remote - The remote to fetch from.
@@ -43,4 +45,34 @@ extern NSString *const GTRepositoryRemoteOptionsCredentialProvider;
4345
/// Retruns an array with GTFetchHeadEntry objects
4446
- (NSArray *)fetchHeadEntriesWithError:(NSError **)error;
4547

48+
#pragma mark - Push
49+
50+
/// Push a single branch to a remote.
51+
///
52+
/// branch - The branch to push.
53+
/// remote - The remote to push to.
54+
/// options - Options applied to the push operation.
55+
/// Recognized options are:
56+
/// `GTRepositoryRemoteOptionsCredentialProvider`
57+
/// error - The error if one occurred. Can be NULL.
58+
/// progressBlock - An optional callback for monitoring progress.
59+
///
60+
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
61+
/// will point to an error describing what happened).
62+
- (BOOL)pushBranch:(GTBranch *)branch toRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(void (^)(unsigned int current, unsigned int total, size_t bytes, BOOL *stop))progressBlock;
63+
64+
/// Push an array of branches to a remote.
65+
///
66+
/// branches - An array of branches to push.
67+
/// remote - The remote to push to.
68+
/// options - Options applied to the push operation.
69+
/// Recognized options are:
70+
/// `GTRepositoryRemoteOptionsCredentialProvider`
71+
/// error - The error if one occurred. Can be NULL.
72+
/// progressBlock - An optional callback for monitoring progress.
73+
///
74+
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
75+
/// will point to an error describing what happened).
76+
- (BOOL)pushBranches:(NSArray *)branches toRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(void (^)(unsigned int current, unsigned int total, size_t bytes, BOOL *stop))progressBlock;
77+
4678
@end

ObjectiveGit/GTRepository+RemoteOperations.m

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,23 @@
1818
#import "NSError+Git.h"
1919

2020
#import "git2/errors.h"
21+
#import "git2/remote.h"
22+
#import "git2/push.h"
2123

2224
NSString *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-
3134
typedef 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

ObjectiveGitFramework.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@
306306
DD3D9513182A81E1004AF532 /* GTBlame.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3D9511182A81E1004AF532 /* GTBlame.m */; };
307307
DD3D951C182AB25C004AF532 /* GTBlameHunk.h in Headers */ = {isa = PBXBuildFile; fileRef = DD3D951A182AB25C004AF532 /* GTBlameHunk.h */; settings = {ATTRIBUTES = (Public, ); }; };
308308
DD3D951D182AB25C004AF532 /* GTBlameHunk.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3D951B182AB25C004AF532 /* GTBlameHunk.m */; };
309+
F8E4A2911A170CA6006485A8 /* GTRemotePushSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E4A2901A170CA6006485A8 /* GTRemotePushSpec.m */; };
309310
/* End PBXBuildFile section */
310311

311312
/* Begin PBXContainerItemProxy section */
@@ -554,6 +555,7 @@
554555
DD3D951B182AB25C004AF532 /* GTBlameHunk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTBlameHunk.m; sourceTree = "<group>"; };
555556
E46931A7172740D300F2077D /* update_libgit2 */ = {isa = PBXFileReference; lastKnownFileType = text; name = update_libgit2; path = script/update_libgit2; sourceTree = "<group>"; };
556557
E46931A8172740D300F2077D /* update_libgit2_ios */ = {isa = PBXFileReference; lastKnownFileType = text; name = update_libgit2_ios; path = script/update_libgit2_ios; sourceTree = "<group>"; };
558+
F8E4A2901A170CA6006485A8 /* GTRemotePushSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRemotePushSpec.m; sourceTree = "<group>"; };
557559
/* End PBXFileReference section */
558560

559561
/* Begin PBXFrameworksBuildPhase section */
@@ -703,6 +705,7 @@
703705
88F05AA816011FFD00B7AD1D /* GTObjectSpec.m */,
704706
D00F6815175D373C004DB9D6 /* GTReferenceSpec.m */,
705707
88215482171499BE00D76B76 /* GTReflogSpec.m */,
708+
F8E4A2901A170CA6006485A8 /* GTRemotePushSpec.m */,
706709
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */,
707710
200578C418932A82001C06C3 /* GTBlameSpec.m */,
708711
D0AC906B172F941F00347DC4 /* GTRepositorySpec.m */,
@@ -1250,6 +1253,7 @@
12501253
88E353061982EA6B0051001F /* GTRepositoryAttributesSpec.m in Sources */,
12511254
88234B2618F2FE260039972E /* GTRepositoryResetSpec.m in Sources */,
12521255
5BE612931745EEBC00266D8C /* GTTreeBuilderSpec.m in Sources */,
1256+
F8E4A2911A170CA6006485A8 /* GTRemotePushSpec.m in Sources */,
12531257
D06D9E011755D10000558C17 /* GTEnumeratorSpec.m in Sources */,
12541258
D03B7C411756AB370034A610 /* GTSubmoduleSpec.m in Sources */,
12551259
D00F6816175D373C004DB9D6 /* GTReferenceSpec.m in Sources */,

0 commit comments

Comments
 (0)