Skip to content

Add Push #419

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 18 commits into from
Dec 8, 2014
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
36 changes: 34 additions & 2 deletions ObjectiveGit/GTRepository+RemoteOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

#import "GTRepository.h"

@class GTFetchHeadEntry;

/// A `GTCredentialProvider`, that will be used to authenticate against the remote.
extern NSString *const GTRepositoryRemoteOptionsCredentialProvider;

@class GTFetchHeadEntry;

@interface GTRepository (RemoteOperations)

#pragma mark - Fetch

/// Fetch a remote.
///
/// remote - The remote to fetch from.
Expand Down Expand Up @@ -43,4 +45,34 @@ extern NSString *const GTRepositoryRemoteOptionsCredentialProvider;
/// Retruns an array with GTFetchHeadEntry objects
- (NSArray *)fetchHeadEntriesWithError:(NSError **)error;

#pragma mark - Push

/// Push a single branch to a remote.
///
/// branch - The branch to push.
/// remote - The remote to push to.
/// options - Options applied to the push operation.
/// Recognized options are:
/// `GTRepositoryRemoteOptionsCredentialProvider`
/// error - The error if one occurred. Can be NULL.
/// progressBlock - An optional callback for monitoring progress.
///
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
/// will point to an error describing what happened).
- (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;

/// Push an array of branches to a remote.
///
/// branches - An array of branches to push.
/// remote - The remote to push to.
/// options - Options applied to the push operation.
/// Recognized options are:
/// `GTRepositoryRemoteOptionsCredentialProvider`
/// error - The error if one occurred. Can be NULL.
/// progressBlock - An optional callback for monitoring progress.
///
/// Returns YES if the push was successful, NO otherwise (and `error`, if provided,
/// will point to an error describing what happened).
- (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;

@end
160 changes: 157 additions & 3 deletions ObjectiveGit/GTRepository+RemoteOperations.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,23 @@
#import "NSError+Git.h"

#import "git2/errors.h"
#import "git2/remote.h"
#import "git2/push.h"

NSString *const GTRepositoryRemoteOptionsCredentialProvider = @"GTRepositoryRemoteOptionsCredentialProvider";

typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *stats, BOOL *stop);
typedef void (^GTRemotePushTransferProgressBlock)(unsigned int current, unsigned int total, size_t bytes, BOOL *stop);

@implementation GTRepository (RemoteOperations)

#pragma mark -
#pragma mark Common Remote code

typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *stats, BOOL *stop);

typedef struct {
GTCredentialAcquireCallbackInfo credProvider;
__unsafe_unretained GTRemoteFetchTransferProgressBlock fetchProgressBlock;
__unsafe_unretained GTRemoteFetchTransferProgressBlock pushProgressBlock;
__unsafe_unretained GTRemotePushTransferProgressBlock pushProgressBlock;
git_direction direction;
} GTRemoteConnectionInfo;

Expand All @@ -46,6 +49,25 @@ int GTRemoteFetchTransferProgressCallback(const git_transfer_progress *stats, vo
return (stop == YES ? GIT_EUSER : 0);
}

int GTRemotePushTransferProgressCallback(unsigned int current, unsigned int total, size_t bytes, void *payload) {
GTRemoteConnectionInfo *pushPayload = payload;

BOOL stop = NO;
if (pushPayload->pushProgressBlock) {
pushPayload->pushProgressBlock(current, total, bytes, &stop);
}

return (stop == YES ? GIT_EUSER : 0);
}

static int GTRemotePushRefspecStatusCallback(const char *ref, const char *msg, void *data) {
if (msg != NULL) {
return GIT_ERROR;
}

return GIT_OK;
}

#pragma mark -
#pragma mark Fetch

Expand Down Expand Up @@ -145,4 +167,136 @@ - (NSArray *)fetchHeadEntriesWithError:(NSError **)error {
return entries;
}

#pragma mark - Push (Public)

- (BOOL)pushBranch:(GTBranch *)branch toRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(GTRemotePushTransferProgressBlock)progressBlock {
NSParameterAssert(branch != nil);
NSParameterAssert(remote != nil);

return [self pushBranches:@[ branch ] toRemote:remote withOptions:options error:error progress:progressBlock];
}

- (BOOL)pushBranches:(NSArray *)branches toRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(GTRemotePushTransferProgressBlock)progressBlock {
NSParameterAssert(branches != nil);
NSParameterAssert(branches.count != 0);
NSParameterAssert(remote != nil);

NSMutableArray *refspecs = nil;
// Build refspecs for the passed in branches
refspecs = [NSMutableArray arrayWithCapacity:branches.count];
for (GTBranch *branch in branches) {
// Default remote reference for when branch doesn't exist on remote - create with same short name
NSString *remoteBranchReference = [NSString stringWithFormat:@"refs/heads/%@", branch.shortName];

BOOL success = NO;
GTBranch *trackingBranch = [branch trackingBranchWithError:error success:&success];

if (success && trackingBranch) {
// Use remote branch short name from trackingBranch, which could be different
// (e.g. refs/heads/master:refs/heads/my_master)
remoteBranchReference = [NSString stringWithFormat:@"refs/heads/%@", trackingBranch.shortName];
}

[refspecs addObject:[NSString stringWithFormat:@"refs/heads/%@:%@", branch.shortName, remoteBranchReference]];
}

return [self pushRefspecs:refspecs toRemote:remote withOptions:options error:error progress:progressBlock];
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if refspecs is nil?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good question :) We probably need at least one refspec in order to push. So, perhaps branches can't be nil.

}

#pragma mark - Push (Private)

- (BOOL)pushRefspecs:(NSArray *)refspecs toRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(GTRemotePushTransferProgressBlock)progressBlock {
int gitError;
GTCredentialProvider *credProvider = options[GTRepositoryRemoteOptionsCredentialProvider];

GTRemoteConnectionInfo connectionInfo = {
.credProvider = { .credProvider = credProvider },
.direction = GIT_DIRECTION_PUSH,
.pushProgressBlock = progressBlock,
};

git_remote_callbacks remote_callbacks = GIT_REMOTE_CALLBACKS_INIT;
remote_callbacks.credentials = (credProvider != nil ? GTCredentialAcquireCallback : NULL),
remote_callbacks.transfer_progress = GTRemoteFetchTransferProgressCallback,
remote_callbacks.payload = &connectionInfo,

gitError = git_remote_set_callbacks(remote.git_remote, &remote_callbacks);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to set callbacks on remote"];
return NO;
}

gitError = git_remote_connect(remote.git_remote, GIT_DIRECTION_PUSH);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to connect remote"];
return NO;
}
@onExit {
git_remote_disconnect(remote.git_remote);
// Clear out callbacks by overwriting with an effectively empty git_remote_callbacks struct
git_remote_set_callbacks(remote.git_remote, &((git_remote_callbacks)GIT_REMOTE_CALLBACKS_INIT));
};

git_push *push;
gitError = git_push_new(&push, remote.git_remote);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Push object creation failed" failureReason:@"Failed to create push object for remote \"%@\"", self];
return NO;
}
@onExit {
git_push_free(push);
};

git_push_options push_options = GIT_PUSH_OPTIONS_INIT;

gitError = git_push_set_options(push, &push_options);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to add options"];
return NO;
}

GTRemoteConnectionInfo payload = {
.pushProgressBlock = progressBlock,
};
gitError = git_push_set_callbacks(push, NULL, NULL, GTRemotePushTransferProgressCallback, &payload);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Setting push callbacks failed"];
return NO;
}

for (NSString *refspec in refspecs) {
gitError = git_push_add_refspec(push, refspec.UTF8String);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Adding reference failed" failureReason:@"Failed to add refspec \"%@\" to push object", refspec];
return NO;
}
}

gitError = git_push_finish(push);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Push to remote failed"];
return NO;
}

int unpackSuccessful = git_push_unpack_ok(push);
if (unpackSuccessful == 0) {
if (error != NULL) *error = [NSError errorWithDomain:GTGitErrorDomain code:GIT_ERROR userInfo:@{ NSLocalizedDescriptionKey: @"Unpacking failed" }];
return NO;
}

gitError = git_push_update_tips(push, self.userSignatureForNow.git_signature, NULL);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Update tips failed"];
return NO;
}

gitError = git_push_status_foreach(push, GTRemotePushRefspecStatusCallback, NULL);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"One or references failed to update"];
return NO;
}

return YES;
}

@end
4 changes: 4 additions & 0 deletions ObjectiveGitFramework.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@
DD3D9513182A81E1004AF532 /* GTBlame.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3D9511182A81E1004AF532 /* GTBlame.m */; };
DD3D951C182AB25C004AF532 /* GTBlameHunk.h in Headers */ = {isa = PBXBuildFile; fileRef = DD3D951A182AB25C004AF532 /* GTBlameHunk.h */; settings = {ATTRIBUTES = (Public, ); }; };
DD3D951D182AB25C004AF532 /* GTBlameHunk.m in Sources */ = {isa = PBXBuildFile; fileRef = DD3D951B182AB25C004AF532 /* GTBlameHunk.m */; };
F8E4A2911A170CA6006485A8 /* GTRemotePushSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = F8E4A2901A170CA6006485A8 /* GTRemotePushSpec.m */; };
/* End PBXBuildFile section */

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

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -703,6 +705,7 @@
88F05AA816011FFD00B7AD1D /* GTObjectSpec.m */,
D00F6815175D373C004DB9D6 /* GTReferenceSpec.m */,
88215482171499BE00D76B76 /* GTReflogSpec.m */,
F8E4A2901A170CA6006485A8 /* GTRemotePushSpec.m */,
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */,
200578C418932A82001C06C3 /* GTBlameSpec.m */,
D0AC906B172F941F00347DC4 /* GTRepositorySpec.m */,
Expand Down Expand Up @@ -1250,6 +1253,7 @@
88E353061982EA6B0051001F /* GTRepositoryAttributesSpec.m in Sources */,
88234B2618F2FE260039972E /* GTRepositoryResetSpec.m in Sources */,
5BE612931745EEBC00266D8C /* GTTreeBuilderSpec.m in Sources */,
F8E4A2911A170CA6006485A8 /* GTRemotePushSpec.m in Sources */,
D06D9E011755D10000558C17 /* GTEnumeratorSpec.m in Sources */,
D03B7C411756AB370034A610 /* GTSubmoduleSpec.m in Sources */,
D00F6816175D373C004DB9D6 /* GTReferenceSpec.m in Sources */,
Expand Down
Loading