From 126b39434523087a490541ba77c3b6571c75123c Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 13:36:12 +0100 Subject: [PATCH 01/12] Reorganize GTBranch. --- Classes/GTBranch.h | 34 +++++++------ Classes/GTBranch.m | 124 +++++++++++++++++++++++++-------------------- 2 files changed, 86 insertions(+), 72 deletions(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index 7addf9399..d16eb7fbe 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -36,13 +36,14 @@ typedef enum { @interface GTBranch : NSObject +@property (nonatomic, readonly, strong) GTRepository *repository; +@property (nonatomic, readonly, strong) GTReference *reference; @property (nonatomic, readonly) NSString *name; @property (nonatomic, readonly) NSString *shortName; -@property (nonatomic, readonly) NSString *SHA; @property (nonatomic, readonly) NSString *remoteName; +@property (nonatomic, readonly) NSString *SHA; @property (nonatomic, readonly) GTBranchType branchType; -@property (nonatomic, readonly, strong) GTRepository *repository; -@property (nonatomic, readonly, strong) GTReference *reference; +@property (nonatomic) GTBranch *upstreamBranch; + (NSString *)localNamePrefix; + (NSString *)remoteNamePrefix; @@ -61,22 +62,9 @@ typedef enum { // returns a GTCommit object or nil if an error occurred - (GTCommit *)targetCommitAndReturnError:(NSError **)error; -// Count all commits in this branch -// -// error(out) - will be filled if an error occurs -// -// returns number of commits in the branch or NSNotFound if an error occurred -- (NSUInteger)numberOfCommitsWithError:(NSError **)error; - -- (NSArray *)uniqueCommitsRelativeToBranch:(GTBranch *)otherBranch error:(NSError **)error; - // Deletes the local branch and nils out the reference. - (BOOL)deleteWithError:(NSError **)error; -// If the receiver is a local branch, looks up and returns its tracking branch. -// If the receiver is a remote branch, returns self. If no tracking branch was -// found, returns nil and sets `success` to YES. -- (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success; // Reloads the branch's reference and creates a new branch based off that newly // loaded reference. @@ -88,6 +76,20 @@ typedef enum { // Returns the reloaded branch, or nil if an error occurred. - (GTBranch *)reloadedBranchWithError:(NSError **)error; +// If the receiver is a local branch, looks up and returns its tracking branch. +// If the receiver is a remote branch, returns self. If no tracking branch was +// found, returns nil and sets `success` to YES. +- (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success; + +// Count all commits in this branch +// +// error(out) - will be filled if an error occurs +// +// returns number of commits in the branch or NSNotFound if an error occurred +- (NSUInteger)numberOfCommitsWithError:(NSError **)error; + +- (NSArray *)uniqueCommitsRelativeToBranch:(GTBranch *)otherBranch error:(NSError **)error; + // Calculate the ahead/behind count from this branch to the given branch. // // ahead - The number of commits which are unique to the receiver. Cannot be diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index 7d57c3db6..5c869918a 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -32,23 +32,8 @@ @implementation GTBranch -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> name: %@, shortName: %@, sha: %@, remoteName: %@, repository: %@", NSStringFromClass([self class]), self, self.name, self.shortName, self.SHA, self.remoteName, self.repository]; -} - -- (BOOL)isEqual:(GTBranch *)otherBranch { - if (otherBranch == self) return YES; - if (![otherBranch isKindOfClass:self.class]) return NO; - - return [self.name isEqual:otherBranch.name] && [self.SHA isEqual:otherBranch.SHA]; -} - -- (NSUInteger)hash { - return self.name.hash ^ self.SHA.hash; -} - - -#pragma mark API +#pragma mark - +#pragma mark Class methods + (NSString *)localNamePrefix { return @"refs/heads/"; @@ -58,6 +43,9 @@ + (NSString *)remoteNamePrefix { return @"refs/remotes/"; } +#pragma mark - +#pragma mark Lifecycle + + (id)branchWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error { return [[self alloc] initWithName:branchName repository:repo error:error]; } @@ -89,6 +77,27 @@ - (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo { return self; } +#pragma mark - +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p> name: %@, shortName: %@, sha: %@, remoteName: %@, repository: %@", NSStringFromClass([self class]), self, self.name, self.shortName, self.SHA, self.remoteName, self.repository]; +} + +- (BOOL)isEqual:(GTBranch *)otherBranch { + if (otherBranch == self) return YES; + if (![otherBranch isKindOfClass:self.class]) return NO; + + return [self.name isEqual:otherBranch.name] && [self.SHA isEqual:otherBranch.SHA]; +} + +- (NSUInteger)hash { + return self.name.hash ^ self.SHA.hash; +} + +#pragma mark - +#pragma mark Properties + - (NSString *)name { return self.reference.name; } @@ -109,10 +118,6 @@ - (NSString *)shortName { return @(name); } -- (NSString *)SHA { - return self.reference.targetSHA; -} - - (NSString *)remoteName { if (self.branchType == GTBranchTypeLocal) return nil; @@ -127,21 +132,8 @@ - (NSString *)remoteName { return [[NSString alloc] initWithBytes:name length:end - name encoding:NSUTF8StringEncoding]; } -- (GTCommit *)targetCommitAndReturnError:(NSError **)error { - if (self.SHA == nil) { - if (error != NULL) *error = GTReference.invalidReferenceError; - return nil; - } - - return [self.repository lookupObjectBySHA:self.SHA objectType:GTObjectTypeCommit error:error]; -} - -- (NSUInteger)numberOfCommitsWithError:(NSError **)error { - GTEnumerator *enumerator = [[GTEnumerator alloc] initWithRepository:self.repository error:error]; - if (enumerator == nil) return NSNotFound; - - if (![enumerator pushSHA:self.SHA error:error]) return NSNotFound; - return [enumerator countRemainingObjects:error]; +- (NSString *)SHA { + return self.reference.targetSHA; } - (GTBranchType)branchType { @@ -152,24 +144,16 @@ - (GTBranchType)branchType { } } -- (NSArray *)uniqueCommitsRelativeToBranch:(GTBranch *)otherBranch error:(NSError **)error { - NSParameterAssert(otherBranch != nil); - - GTCommit *mergeBase = [self.repository mergeBaseBetweenFirstOID:self.reference.OID secondOID:otherBranch.reference.OID error:error]; - if (mergeBase == nil) return nil; - - GTEnumerator *enumerator = [[GTEnumerator alloc] initWithRepository:self.repository error:error]; - if (enumerator == nil) return nil; - - [enumerator resetWithOptions:GTEnumeratorOptionsTimeSort]; - - BOOL success = [enumerator pushSHA:self.SHA error:error]; - if (!success) return nil; +#pragma mark - +#pragma mark API - success = [enumerator hideSHA:mergeBase.SHA error:error]; - if (!success) return nil; +- (GTCommit *)targetCommitAndReturnError:(NSError **)error { + if (self.SHA == nil) { + if (error != NULL) *error = GTReference.invalidReferenceError; + return nil; + } - return [enumerator allObjectsWithError:error]; + return [self.repository lookupObjectBySHA:self.SHA objectType:GTObjectTypeCommit error:error]; } - (BOOL)deleteWithError:(NSError **)error { @@ -182,6 +166,13 @@ - (BOOL)deleteWithError:(NSError **)error { return YES; } +- (GTBranch *)reloadedBranchWithError:(NSError **)error { + GTReference *reloadedRef = [self.reference reloadedReferenceWithError:error]; + if (reloadedRef == nil) return nil; + + return [[self.class alloc] initWithReference:reloadedRef repository:self.repository]; +} + - (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success { if (self.branchType == GTBranchTypeRemote) { if (success != NULL) *success = YES; @@ -214,11 +205,32 @@ - (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success return [[self class] branchWithReference:[[GTReference alloc] initWithGitReference:trackingRef repository:self.repository] repository:self.repository]; } -- (GTBranch *)reloadedBranchWithError:(NSError **)error { - GTReference *reloadedRef = [self.reference reloadedReferenceWithError:error]; - if (reloadedRef == nil) return nil; +- (NSUInteger)numberOfCommitsWithError:(NSError **)error { + GTEnumerator *enumerator = [[GTEnumerator alloc] initWithRepository:self.repository error:error]; + if (enumerator == nil) return NSNotFound; - return [[self.class alloc] initWithReference:reloadedRef repository:self.repository]; + if (![enumerator pushSHA:self.SHA error:error]) return NSNotFound; + return [enumerator countRemainingObjects:error]; +} + +- (NSArray *)uniqueCommitsRelativeToBranch:(GTBranch *)otherBranch error:(NSError **)error { + NSParameterAssert(otherBranch != nil); + + GTCommit *mergeBase = [self.repository mergeBaseBetweenFirstOID:self.reference.OID secondOID:otherBranch.reference.OID error:error]; + if (mergeBase == nil) return nil; + + GTEnumerator *enumerator = [[GTEnumerator alloc] initWithRepository:self.repository error:error]; + if (enumerator == nil) return nil; + + [enumerator resetWithOptions:GTEnumeratorOptionsTimeSort]; + + BOOL success = [enumerator pushSHA:self.SHA error:error]; + if (!success) return nil; + + success = [enumerator hideSHA:mergeBase.SHA error:error]; + if (!success) return nil; + + return [enumerator allObjectsWithError:error]; } - (BOOL)calculateAhead:(size_t *)ahead behind:(size_t *)behind relativeTo:(GTBranch *)branch error:(NSError **)error { From 361e671a3b71c9690b95bcfc7d2c18c48722a760 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 19:13:05 +0100 Subject: [PATCH 02/12] Gotta love documentation. --- Classes/GTBranch.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index d16eb7fbe..2383a89fb 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -57,9 +57,9 @@ typedef enum { // Get the target commit for this branch // -// error(out) - will be filled if an error occurs +// error - A pointer which will point to a valid error if the target can't be found. // -// returns a GTCommit object or nil if an error occurred +// Returns a GTCommit object or nil if an error occurred. - (GTCommit *)targetCommitAndReturnError:(NSError **)error; // Deletes the local branch and nils out the reference. @@ -83,11 +83,20 @@ typedef enum { // Count all commits in this branch // -// error(out) - will be filled if an error occurs +// error - will be filled if an error occurs // -// returns number of commits in the branch or NSNotFound if an error occurred +// Returns the number of commits in the receiver or `NSNotFound` if an error occurred - (NSUInteger)numberOfCommitsWithError:(NSError **)error; +// Get the unique commits between branches. +// +// This method returns an array representing the unique commits +// that exist between the receiver and `otherBranch`. +// +// otherBranch - The branch to compare against. +// error - Will be set if an error occurs. +// +// Returns an array of GTCommits, or nil if an error occurred. - (NSArray *)uniqueCommitsRelativeToBranch:(GTBranch *)otherBranch error:(NSError **)error; // Calculate the ahead/behind count from this branch to the given branch. From 0cdd6bedd423e4eda30944e5c1d7c050212cc091 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sat, 9 Nov 2013 11:44:29 +0100 Subject: [PATCH 03/12] Add new branch initializers based on the libgit2 functions. --- Classes/GTBranch.h | 24 ++++++++++++++++++++++- Classes/GTBranch.m | 29 ++++++++++++++++++++++++++++ ObjectiveGitTests/GTBranchSpec.m | 2 +- ObjectiveGitTests/GTRepositorySpec.m | 4 ++-- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index 2383a89fb..942eca4aa 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -48,13 +48,35 @@ typedef enum { + (NSString *)localNamePrefix; + (NSString *)remoteNamePrefix; +// Lookup a branch by name. +// +// name - The branch name to lookup. +// repository - The repository to lookup the branch in. +// error - A pointer which will point to a valid error if the lookup fails. +// +// Returns the branch object with that name, or nil if an error occurred. ++ (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRepository *)repository error:(NSError **)error; + +// Create a branch from a name and target. +// +// name - The name of the branch to create. +// commit - The commit the branch should point to. +// force - If set to YES, a branch with same name would be deleted. +// repository - The repository to create the branch in. +// error - A pointer which will point to a valid error if the creation fails. +// +// Returns a newly created branch object, or nil if the branch couldn't be created. ++ (instancetype)branchByCreatingBranchNamed:(NSString *)name target:(GTCommit *)commit force:(BOOL)force inRepository:(GTRepository *)repository error:(NSError **)error; + // Convenience initializers - (id)initWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error; + (id)branchWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error; -- (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo; + (id)branchWithReference:(GTReference *)ref repository:(GTRepository *)repo; +// Designated initializer +- (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo; + // Get the target commit for this branch // // error - A pointer which will point to a valid error if the target can't be found. diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index 5c869918a..33456fb0a 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -46,6 +46,34 @@ + (NSString *)remoteNamePrefix { #pragma mark - #pragma mark Lifecycle ++ (instancetype)branchByCreatingBranchNamed:(NSString *)name target:(GTCommit *)commit force:(BOOL)force inRepository:(GTRepository *)repository error:(NSError **)error { + git_reference *git_ref; + int gitError = git_branch_create(&git_ref, repository.git_repository, name.UTF8String, commit.git_commit, (force ? 1 : 0)); + if (gitError != GIT_OK) { + if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Branch creation failed"]; + return nil; + } + + GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:repository]; + return [[self alloc] initWithReference:ref repository:repository]; +} + ++ (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRepository *)repository error:(NSError **)error { + git_reference *git_ref; + // First try local branches + int gitError = git_branch_lookup(&git_ref, repository.git_repository, name.UTF8String, GIT_BRANCH_LOCAL); + if (gitError != GIT_OK) { + int gitError = git_branch_lookup(&git_ref, repository.git_repository, name.UTF8String, GIT_BRANCH_REMOTE); + if (gitError != GIT_OK) { + if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Branch lookup failed"]; + return nil; + } + } + + GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:repository]; + return [[self alloc] initWithReference:ref repository:repository]; +} + + (id)branchWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error { return [[self alloc] initWithName:branchName repository:repo error:error]; } @@ -64,6 +92,7 @@ - (id)initWithName:(NSString *)branchName repository:(GTRepository *)repo error: return [self initWithReference:ref repository:repo]; } +// Designated initializer - (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo { NSParameterAssert(ref != nil); NSParameterAssert(repo != nil); diff --git a/ObjectiveGitTests/GTBranchSpec.m b/ObjectiveGitTests/GTBranchSpec.m index e1ff7c0ac..b06a04f14 100644 --- a/ObjectiveGitTests/GTBranchSpec.m +++ b/ObjectiveGitTests/GTBranchSpec.m @@ -139,7 +139,7 @@ describe(@"-trackingBranchWithError:success:", ^{ it(@"should return the tracking branch for a local branch that tracks a remote branch", ^{ NSError *error = nil; - GTBranch *masterBranch = [GTBranch branchWithName:@"refs/heads/master" repository:repository error:&error]; + GTBranch *masterBranch = [GTBranch branchByLookingUpBranchNamed:@"master" inRepository:repository error:&error]; expect(masterBranch).notTo.beNil(); expect(error).to.beNil(); diff --git a/ObjectiveGitTests/GTRepositorySpec.m b/ObjectiveGitTests/GTRepositorySpec.m index 4d1ddee5a..8b6d58c81 100644 --- a/ObjectiveGitTests/GTRepositorySpec.m +++ b/ObjectiveGitTests/GTRepositorySpec.m @@ -186,11 +186,11 @@ describe(@"-mergeBaseBetweenFirstOID:secondOID:error:", ^{ it(@"should find the merge base between two branches", ^{ NSError *error = nil; - GTBranch *masterBranch = [[GTBranch alloc] initWithName:@"refs/heads/master" repository:repository error:&error]; + GTBranch *masterBranch = [GTBranch branchByLookingUpBranchNamed:@"master" inRepository:repository error:&error]; expect(masterBranch).notTo.beNil(); expect(error).to.beNil(); - GTBranch *otherBranch = [[GTBranch alloc] initWithName:@"refs/heads/other-branch" repository:repository error:&error]; + GTBranch *otherBranch = [GTBranch branchByLookingUpBranchNamed:@"other-branch" inRepository:repository error:&error]; expect(otherBranch).notTo.beNil(); expect(error).to.beNil(); From f211658f934a10aecffcfcb2cfada33df90398e4 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 16:20:51 +0100 Subject: [PATCH 04/12] Use the reference's repository instead of asking for one. --- Classes/GTBranch.h | 8 +++----- Classes/GTBranch.m | 34 +++++++++++++++----------------- Classes/GTRepository.m | 10 +++++----- ObjectiveGitTests/GTBranchSpec.m | 4 ++-- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index 942eca4aa..be75c9311 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -69,13 +69,11 @@ typedef enum { + (instancetype)branchByCreatingBranchNamed:(NSString *)name target:(GTCommit *)commit force:(BOOL)force inRepository:(GTRepository *)repository error:(NSError **)error; // Convenience initializers -- (id)initWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error; -+ (id)branchWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error; - -+ (id)branchWithReference:(GTReference *)ref repository:(GTRepository *)repo; ++ (id)branchWithReferenceNamed:(NSString *)referenceName inRepository:(GTRepository *)repo error:(NSError **)error; ++ (id)branchWithReference:(GTReference *)ref; // Designated initializer -- (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo; +- (id)initWithReference:(GTReference *)ref; // Get the target commit for this branch // diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index 33456fb0a..44ef9f9f5 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -55,7 +55,7 @@ + (instancetype)branchByCreatingBranchNamed:(NSString *)name target:(GTCommit *) } GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:repository]; - return [[self alloc] initWithReference:ref repository:repository]; + return [[self alloc] initWithReference:ref]; } + (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRepository *)repository error:(NSError **)error { @@ -71,36 +71,30 @@ + (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRe } GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:repository]; - return [[self alloc] initWithReference:ref repository:repository]; + return [[self alloc] initWithReference:ref]; } -+ (id)branchWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error { - return [[self alloc] initWithName:branchName repository:repo error:error]; -} - -+ (id)branchWithReference:(GTReference *)ref repository:(GTRepository *)repo { - return [[self alloc] initWithReference:ref repository:repo]; -} - -- (id)initWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error { - NSParameterAssert(branchName != nil); ++ (id)branchWithReferenceNamed:(NSString *)referenceName inRepository:(GTRepository *)repo error:(NSError **)error { + NSParameterAssert(referenceName != nil); NSParameterAssert(repo != nil); GTReference *ref = [GTReference referenceByLookingUpReferencedNamed:branchName inRepository:repo error:error]; if (ref == nil) return nil; - return [self initWithReference:ref repository:repo]; + return [[self alloc] initWithReference:ref]; +} + ++ (id)branchWithReference:(GTReference *)ref { + return [[self alloc] initWithReference:ref]; } // Designated initializer -- (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo { +- (id)initWithReference:(GTReference *)ref { NSParameterAssert(ref != nil); - NSParameterAssert(repo != nil); self = [super init]; if (self == nil) return nil; - _repository = repo; _reference = ref; return self; @@ -161,6 +155,10 @@ - (NSString *)remoteName { return [[NSString alloc] initWithBytes:name length:end - name encoding:NSUTF8StringEncoding]; } +- (GTRepository *)repository { + return self.reference.repository; +} + - (NSString *)SHA { return self.reference.targetSHA; } @@ -199,7 +197,7 @@ - (GTBranch *)reloadedBranchWithError:(NSError **)error { GTReference *reloadedRef = [self.reference reloadedReferenceWithError:error]; if (reloadedRef == nil) return nil; - return [[self.class alloc] initWithReference:reloadedRef repository:self.repository]; + return [[self.class alloc] initWithReference:reloadedRef]; } - (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success { @@ -231,7 +229,7 @@ - (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success if (success != NULL) *success = YES; - return [[self class] branchWithReference:[[GTReference alloc] initWithGitReference:trackingRef repository:self.repository] repository:self.repository]; + return [[self class] branchWithReference:[[GTReference alloc] initWithGitReference:trackingRef repository:self.repository]]; } - (NSUInteger)numberOfCommitsWithError:(NSError **)error { diff --git a/Classes/GTRepository.m b/Classes/GTRepository.m index 38ddd9475..d2634923c 100644 --- a/Classes/GTRepository.m +++ b/Classes/GTRepository.m @@ -320,9 +320,9 @@ - (NSArray *)branchesWithPrefix:(NSString *)prefix error:(NSError **)error { if (references == nil) return nil; NSMutableArray *branches = [NSMutableArray array]; - for (NSString *ref in references) { - if ([ref hasPrefix:prefix]) { - GTBranch *b = [GTBranch branchWithName:ref repository:self error:error]; + for (NSString *refName in references) { + if ([refName hasPrefix:prefix]) { + GTBranch *b = [GTBranch branchWithReferenceNamed:refName inRepository:self error:error]; if (b != nil) [branches addObject:b]; } } @@ -408,7 +408,7 @@ - (GTBranch *)createBranchNamed:(NSString *)name fromReference:(GTReference *)re GTReference *newRef = [GTReference referenceByCreatingReferenceNamed:[NSString stringWithFormat:@"%@%@", [GTBranch localNamePrefix], name] fromReferenceTarget:[ref.resolvedTarget SHA] inRepository:self error:error]; if (newRef == nil) return nil; - return [GTBranch branchWithReference:newRef repository:self]; + return [GTBranch branchWithReference:newRef]; } - (BOOL)isEmpty { @@ -419,7 +419,7 @@ - (GTBranch *)currentBranchWithError:(NSError **)error { GTReference *head = [self headReferenceWithError:error]; if (head == nil) return nil; - return [GTBranch branchWithReference:head repository:self]; + return [GTBranch branchWithReference:head]; } - (NSArray *)localCommitsRelativeToRemoteBranch:(GTBranch *)remoteBranch error:(NSError **)error { diff --git a/ObjectiveGitTests/GTBranchSpec.m b/ObjectiveGitTests/GTBranchSpec.m index b06a04f14..ae8b38c32 100644 --- a/ObjectiveGitTests/GTBranchSpec.m +++ b/ObjectiveGitTests/GTBranchSpec.m @@ -156,7 +156,7 @@ expect(otherRef).notTo.beNil(); expect(error).to.beNil(); - GTBranch *otherBranch = [GTBranch branchWithReference:otherRef repository:repository]; + GTBranch *otherBranch = [GTBranch branchWithReference:otherRef]; expect(otherBranch).notTo.beNil(); BOOL success = NO; @@ -172,7 +172,7 @@ expect(remoteRef).notTo.beNil(); expect(error).to.beNil(); - GTBranch *remoteBranch = [GTBranch branchWithReference:remoteRef repository:repository]; + GTBranch *remoteBranch = [GTBranch branchWithReference:remoteRef]; expect(remoteBranch).notTo.beNil(); BOOL success = NO; From b00b007f7e1cc73a2e97c2164880e0599ba6f0ce Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 16:37:10 +0100 Subject: [PATCH 05/12] Use the libgit2 enum for those. --- Classes/GTBranch.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index be75c9311..7f68a29b6 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -30,8 +30,8 @@ @class GTRepository; typedef enum { - GTBranchTypeLocal = 1, - GTBranchTypeRemote + GTBranchTypeLocal = GIT_BRANCH_LOCAL, + GTBranchTypeRemote = GIT_BRANCH_REMOTE, } GTBranchType; @interface GTBranch : NSObject From 04a4b82c531f5b0a87059f10c50b399c0e7491c8 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 16:39:34 +0100 Subject: [PATCH 06/12] Cut down the number of initialization methods on GTReference. --- Classes/GTBranch.m | 2 +- Classes/GTReference.h | 7 +---- Classes/GTReference.m | 44 ++++++++++------------------ ObjectiveGitTests/GTBranchSpec.m | 2 +- ObjectiveGitTests/GTReferenceSpec.m | 16 ++++++---- ObjectiveGitTests/GTRepositorySpec.m | 2 +- 6 files changed, 30 insertions(+), 43 deletions(-) diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index 44ef9f9f5..72ce93a05 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -78,7 +78,7 @@ + (id)branchWithReferenceNamed:(NSString *)referenceName inRepository:(GTReposit NSParameterAssert(referenceName != nil); NSParameterAssert(repo != nil); - GTReference *ref = [GTReference referenceByLookingUpReferencedNamed:branchName inRepository:repo error:error]; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:referenceName inRepository:repo error:error]; if (ref == nil) return nil; return [[self alloc] initWithReference:ref]; diff --git a/Classes/GTReference.h b/Classes/GTReference.h index 715681775..34749f2f2 100644 --- a/Classes/GTReference.h +++ b/Classes/GTReference.h @@ -55,14 +55,9 @@ typedef enum { @property (nonatomic, readonly, strong) GTReflog *reflog; // Convenience initializers -+ (id)referenceByLookingUpReferencedNamed:(NSString *)refName inRepository:(GTRepository *)theRepo error:(NSError **)error; -- (id)initByLookingUpReferenceNamed:(NSString *)refName inRepository:(GTRepository *)theRepo error:(NSError **)error; - ++ (id)referenceByLookingUpReferenceNamed:(NSString *)refName inRepository:(GTRepository *)theRepo error:(NSError **)error; + (id)referenceByCreatingReferenceNamed:(NSString *)refName fromReferenceTarget:(NSString *)target inRepository:(GTRepository *)theRepo error:(NSError **)error; -- (id)initByCreatingReferenceNamed:(NSString *)refName fromReferenceTarget:(NSString *)target inRepository:(GTRepository *)theRepo error:(NSError **)error; - + (id)referenceByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error; -- (id)initByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error; - (id)initWithGitReference:(git_reference *)ref repository:(GTRepository *)repository; diff --git a/Classes/GTReference.m b/Classes/GTReference.m index e09b905b1..31eea1306 100644 --- a/Classes/GTReference.m +++ b/Classes/GTReference.m @@ -65,44 +65,32 @@ - (BOOL)isRemote { return git_reference_is_remote(self.git_reference) != 0; } -+ (id)referenceByLookingUpReferencedNamed:(NSString *)refName inRepository:(GTRepository *)theRepo error:(NSError **)error { - return [[self alloc] initByLookingUpReferenceNamed:refName inRepository:theRepo error:error]; -} - -+ (id)referenceByCreatingReferenceNamed:(NSString *)refName fromReferenceTarget:(NSString *)target inRepository:(GTRepository *)theRepo error:(NSError **)error { - return [[self alloc] initByCreatingReferenceNamed:refName fromReferenceTarget:target inRepository:theRepo error:error]; -} - -+ (id)referenceByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error { - return [[self alloc] initByResolvingSymbolicReference:symbolicRef error:error]; -} - -- (id)initByLookingUpReferenceNamed:(NSString *)refName inRepository:(GTRepository *)repo error:(NSError **)error { - NSParameterAssert(refName != nil); - NSParameterAssert(repo != nil); ++ (id)referenceByLookingUpReferenceNamed:(NSString *)referenceName inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(referenceName != nil); + NSParameterAssert(repository != nil); git_reference *ref = NULL; - int gitError = git_reference_lookup(&ref, repo.git_repository, refName.UTF8String); + int gitError = git_reference_lookup(&ref, repository.git_repository, referenceName.UTF8String); if (gitError != GIT_OK) { - if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to lookup reference %@.", refName]; + if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Reference lookup failed" failureReason:@"The reference named \"%@\" couldn't be resolved in \"%@\"", referenceName, repository.gitDirectoryURL.path]; return nil; } - return [self initWithGitReference:ref repository:repo]; + return [[self alloc] initWithGitReference:ref repository:repository]; } -- (id)initByCreatingReferenceNamed:(NSString *)refName fromReferenceTarget:(NSString *)target inRepository:(GTRepository *)repo error:(NSError **)error { - NSParameterAssert(refName != nil); ++ (id)referenceByCreatingReferenceNamed:(NSString *)referenceName fromReferenceTarget:(NSString *)target inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(referenceName != nil); NSParameterAssert(target != nil); - NSParameterAssert(repo != nil); + NSParameterAssert(repository != nil); GTOID *oid = [GTOID oidWithSHA:target]; int gitError = GIT_OK; git_reference *ref; if (oid != nil) { - gitError = git_reference_create(&ref, repo.git_repository, refName.UTF8String, oid.git_oid, 0); + gitError = git_reference_create(&ref, repository.git_repository, referenceName.UTF8String, oid.git_oid, 0); } else { - gitError = git_reference_symbolic_create(&ref, repo.git_repository, refName.UTF8String, target.UTF8String, 0); + gitError = git_reference_symbolic_create(&ref, repository.git_repository, referenceName.UTF8String, target.UTF8String, 0); } if (gitError != GIT_OK) { @@ -110,10 +98,10 @@ - (id)initByCreatingReferenceNamed:(NSString *)refName fromReferenceTarget:(NSSt return nil; } - return [self initWithGitReference:ref repository:repo]; + return [[self alloc] initWithGitReference:ref repository:repository]; } -- (id)initByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error { ++ (id)referenceByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error { NSParameterAssert(symbolicRef != nil); git_reference *ref = NULL; @@ -123,7 +111,7 @@ - (id)initByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError return nil; } - return [self initWithGitReference:ref repository:symbolicRef.repository]; + return [[self alloc] initWithGitReference:ref repository:symbolicRef.repository]; } - (id)initWithGitReference:(git_reference *)ref repository:(GTRepository *)repo { @@ -173,7 +161,7 @@ - (id)unresolvedTarget { NSString *refName = @(git_reference_symbolic_target(self.git_reference)); if (refName == NULL) return nil; - return [self.class referenceByLookingUpReferencedNamed:refName inRepository:self.repository error:NULL]; + return [self.class referenceByLookingUpReferenceNamed:refName inRepository:self.repository error:NULL]; } return nil; } @@ -242,7 +230,7 @@ - (GTOID *)OID { } - (GTReference *)reloadedReferenceWithError:(NSError **)error { - return [[self.class alloc] initByLookingUpReferenceNamed:self.name inRepository:self.repository error:error]; + return [self.class referenceByLookingUpReferenceNamed:self.name inRepository:self.repository error:error]; } + (NSError *)invalidReferenceError { diff --git a/ObjectiveGitTests/GTBranchSpec.m b/ObjectiveGitTests/GTBranchSpec.m index ae8b38c32..13a3f1258 100644 --- a/ObjectiveGitTests/GTBranchSpec.m +++ b/ObjectiveGitTests/GTBranchSpec.m @@ -168,7 +168,7 @@ it(@"should return itself for a remote branch", ^{ NSError *error = nil; - GTReference *remoteRef = [GTReference referenceByLookingUpReferencedNamed:@"refs/remotes/origin/master" inRepository:repository error:&error]; + GTReference *remoteRef = [GTReference referenceByLookingUpReferenceNamed:@"refs/remotes/origin/master" inRepository:repository error:&error]; expect(remoteRef).notTo.beNil(); expect(error).to.beNil(); diff --git a/ObjectiveGitTests/GTReferenceSpec.m b/ObjectiveGitTests/GTReferenceSpec.m index dac0588b0..759de5280 100644 --- a/ObjectiveGitTests/GTReferenceSpec.m +++ b/ObjectiveGitTests/GTReferenceSpec.m @@ -16,17 +16,21 @@ }); it(@"should compare equal to the same reference", ^{ - expect([[GTReference alloc] initByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:NULL]).to.equal([[GTReference alloc] initByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:NULL]); + GTReference *firstRef = [GTReference referenceByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:NULL]; + GTReference *secondRef = [GTReference referenceByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:NULL]; + expect(firstRef).to.equal(secondRef); }); it(@"should compare unequal to a different reference", ^{ - expect([[GTReference alloc] initByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:NULL]).notTo.equal([[GTReference alloc] initByLookingUpReferenceNamed:@"refs/remotes/origin/master" inRepository:repository error:NULL]); + GTReference *masterRef = [GTReference referenceByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:NULL]; + GTReference *originMasterRef = [GTReference referenceByLookingUpReferenceNamed:@"refs/remotes/origin/master" inRepository:repository error:NULL]; + expect(masterRef).notTo.equal(originMasterRef); }); describe(@"remote property", ^{ it(@"should be YES for a remote-tracking branch", ^{ NSError *error = nil; - GTReference *ref = [[GTReference alloc] initByLookingUpReferenceNamed:@"refs/remotes/origin/master" inRepository:repository error:&error]; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:@"refs/remotes/origin/master" inRepository:repository error:&error]; expect(ref).notTo.beNil(); expect(error).to.beNil(); @@ -36,7 +40,7 @@ it(@"should be NO for a local branch", ^{ NSError *error = nil; - GTReference *ref = [[GTReference alloc] initByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:&error]; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:@"refs/heads/master" inRepository:repository error:&error]; expect(ref).notTo.beNil(); expect(error).to.beNil(); @@ -123,7 +127,7 @@ describe(@"+referenceByLookingUpReferenceNamed:inRepository:error:", ^{ it(@"should return a valid reference to a branch", ^{ NSError *error = nil; - GTReference *ref = [GTReference referenceByLookingUpReferencedNamed:@"refs/heads/master" inRepository:bareRepository error:&error]; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:@"refs/heads/master" inRepository:bareRepository error:&error]; expect(ref).notTo.beNil(); expect(error).to.beNil(); @@ -132,7 +136,7 @@ it(@"should return a valid reference to a tag", ^{ NSError *error = nil; - GTReference *ref = [GTReference referenceByLookingUpReferencedNamed:@"refs/tags/v0.9" inRepository:bareRepository error:&error]; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:@"refs/tags/v0.9" inRepository:bareRepository error:&error]; expect(ref).notTo.beNil(); expect(error).to.beNil(); diff --git a/ObjectiveGitTests/GTRepositorySpec.m b/ObjectiveGitTests/GTRepositorySpec.m index 8b6d58c81..61c1fe16e 100644 --- a/ObjectiveGitTests/GTRepositorySpec.m +++ b/ObjectiveGitTests/GTRepositorySpec.m @@ -290,7 +290,7 @@ describe(@"-checkout:strategy:error:progressBlock:", ^{ it(@"should allow references", ^{ NSError *error = nil; - GTReference *ref = [GTReference referenceByLookingUpReferencedNamed:@"refs/heads/other-branch" inRepository:repository error:&error]; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:@"refs/heads/other-branch" inRepository:repository error:&error]; expect(ref).to.beTruthy(); expect(error.localizedDescription).to.beNil(); BOOL result = [repository checkoutReference:ref strategy:GTCheckoutStrategyAllowConflicts error:&error progressBlock:nil]; From bb261eeb0115a7f3ea13a4bf91debd5297f95b6f Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 16:42:16 +0100 Subject: [PATCH 07/12] Use libgit2 function to get the branch name. --- Classes/GTBranch.m | 6 +++++- ObjectiveGitTests/GTBranchSpec.m | 10 ++++++++++ ObjectiveGitTests/GTRepositorySpec.m | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index 72ce93a05..19e7c2d81 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -122,7 +122,11 @@ - (NSUInteger)hash { #pragma mark Properties - (NSString *)name { - return self.reference.name; + const char *charName; + int gitError = git_branch_name(&charName, self.reference.git_reference); + if (gitError != GIT_OK || charName == NULL) return nil; + + return @(charName); } - (NSString *)shortName { diff --git a/ObjectiveGitTests/GTBranchSpec.m b/ObjectiveGitTests/GTBranchSpec.m index 13a3f1258..8f7ab0e7b 100644 --- a/ObjectiveGitTests/GTBranchSpec.m +++ b/ObjectiveGitTests/GTBranchSpec.m @@ -30,6 +30,16 @@ expect(error).to.beNil(); }); +describe(@"name", ^{ + it(@"should use just the branch name for a local branch", ^{ + expect(masterBranch.name).to.equal(@"master"); + }); + + it(@"should include the remote name for a tracking branch", ^{ + expect(trackingBranch.name).to.equal(@"origin/master"); + }); +}); + describe(@"shortName", ^{ it(@"should use just the branch name for a local branch", ^{ expect(masterBranch.shortName).to.equal(@"master"); diff --git a/ObjectiveGitTests/GTRepositorySpec.m b/ObjectiveGitTests/GTRepositorySpec.m index 61c1fe16e..63507fb32 100644 --- a/ObjectiveGitTests/GTRepositorySpec.m +++ b/ObjectiveGitTests/GTRepositorySpec.m @@ -215,7 +215,7 @@ GTBranch *currentBranch = [repository currentBranchWithError:&error]; expect(currentBranch).notTo.beNil(); expect(error).to.beNil(); - expect(currentBranch.name).to.equal(@"refs/heads/master"); + expect(currentBranch.reference.name).to.equal(@"refs/heads/master"); }); }); @@ -237,7 +237,7 @@ expect(error).to.beNil(); expect(branches.count).to.equal(1); GTBranch *remoteBranch = branches[0]; - expect(remoteBranch.name).to.equal(@"refs/remotes/origin/master"); + expect(remoteBranch.reference.name).to.equal(@"refs/remotes/origin/master"); }); }); From b5616525ceb5ed3fc5c16d0270dec2376ae76202 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 16:51:16 +0100 Subject: [PATCH 08/12] Use libgit2 function to get remote name. --- Classes/GTBranch.m | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index 19e7c2d81..c46e36a24 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -146,17 +146,14 @@ - (NSString *)shortName { } - (NSString *)remoteName { - if (self.branchType == GTBranchTypeLocal) return nil; + int nameLength = git_branch_remote_name(NULL, 0, self.repository.git_repository, self.reference.name.UTF8String); + if (nameLength <= GIT_OK) return nil; - const char *name; - int gitError = git_branch_name(&name, self.reference.git_reference); - if (gitError != GIT_OK) return nil; - - // Find out where the remote name ends. - const char *end = strchr(name, '/'); - if (end == NULL || end == name) return nil; + char *nameChar = malloc(nameLength); + int gitError = git_branch_remote_name(nameChar, nameLength, self.repository.git_repository, self.reference.name.UTF8String); + if (gitError <= GIT_OK) return nil; - return [[NSString alloc] initWithBytes:name length:end - name encoding:NSUTF8StringEncoding]; + return @(nameChar); } - (GTRepository *)repository { From 9c50e6921e1908252ba9d060225117d75a9c2249 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 16:51:38 +0100 Subject: [PATCH 09/12] Some more API for GTBranch. --- Classes/GTBranch.h | 5 ++++- Classes/GTBranch.m | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index 7f68a29b6..493b29d56 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -43,7 +43,8 @@ typedef enum { @property (nonatomic, readonly) NSString *remoteName; @property (nonatomic, readonly) NSString *SHA; @property (nonatomic, readonly) GTBranchType branchType; -@property (nonatomic) GTBranch *upstreamBranch; +@property (nonatomic, assign) GTBranch *trackingBranch; +@property (nonatomic, readonly, getter=isHead) BOOL head; + (NSString *)localNamePrefix; + (NSString *)remoteNamePrefix; @@ -85,6 +86,8 @@ typedef enum { // Deletes the local branch and nils out the reference. - (BOOL)deleteWithError:(NSError **)error; +// Renames the branch. Setting `force` to YES to delete another branch with the same name. +- (BOOL)rename:(NSString *)name force:(BOOL)force error:(NSError **)error; // Reloads the branch's reference and creates a new branch based off that newly // loaded reference. diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index c46e36a24..e85de44d1 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -172,6 +172,23 @@ - (GTBranchType)branchType { } } +- (GTBranch *)upstreamBranch { + git_reference *git_ref; + int gitError = git_branch_upstream(&git_ref, self.reference.git_reference); + if (gitError != GIT_OK) return nil; + + GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:self.repository]; + return [GTBranch branchWithReference:ref]; +} + +- (void)setUpstreamBranch:(GTBranch *)branch { + git_branch_set_upstream(self.reference.git_reference, (branch ? branch.name.UTF8String : NULL)); +} + +- (BOOL)isHead { + return (git_branch_is_head(self.reference.git_reference) ? YES : NO); +} + #pragma mark - #pragma mark API @@ -194,6 +211,19 @@ - (BOOL)deleteWithError:(NSError **)error { return YES; } +- (BOOL)rename:(NSString *)name force:(BOOL)force error:(NSError **)error { + git_reference *git_ref; + int gitError = git_branch_move(&git_ref, self.reference.git_reference, name.UTF8String, (force ? 0 : 1)); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Rename branch failed"]; + return NO; + } + + _reference = [[GTReference alloc] initWithGitReference:git_ref repository:self.repository]; + + return YES; +} + - (GTBranch *)reloadedBranchWithError:(NSError **)error { GTReference *reloadedRef = [self.reference reloadedReferenceWithError:error]; if (reloadedRef == nil) return nil; From 2db69381b764a2ca9f7bd2e38fbf4db9347b53df Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 17:23:40 +0100 Subject: [PATCH 10/12] Add a way to restrict branch lookups by type. --- Classes/GTBranch.h | 11 +++++++++-- Classes/GTBranch.m | 23 ++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index 493b29d56..02f93985f 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -32,6 +32,7 @@ typedef enum { GTBranchTypeLocal = GIT_BRANCH_LOCAL, GTBranchTypeRemote = GIT_BRANCH_REMOTE, + GTBranchTypeAny = GIT_BRANCH_REMOTE|GIT_BRANCH_LOCAL, } GTBranchType; @interface GTBranch : NSObject @@ -49,14 +50,20 @@ typedef enum { + (NSString *)localNamePrefix; + (NSString *)remoteNamePrefix; -// Lookup a branch by name. +// Lookup a branch by name. Performs a `GTBranchTypeAny` lookup. +// See `+branchByLookingUpBranchNamed:type:inRepository:error:`. ++ (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRepository *)repository error:(NSError **)error; + +// Lookup a branch by name and branch type. // // name - The branch name to lookup. +// type - The type of lookup to perform. If `GTBranchTypeAny` is passed, +// local branches are checked first. // repository - The repository to lookup the branch in. // error - A pointer which will point to a valid error if the lookup fails. // // Returns the branch object with that name, or nil if an error occurred. -+ (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRepository *)repository error:(NSError **)error; ++ (instancetype)branchByLookingUpBranchNamed:(NSString *)name type:(GTBranchType)type inRepository:(GTRepository *)repository error:(NSError **)error; // Create a branch from a name and target. // diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index e85de44d1..f22b42196 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -59,13 +59,26 @@ + (instancetype)branchByCreatingBranchNamed:(NSString *)name target:(GTCommit *) } + (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRepository *)repository error:(NSError **)error { - git_reference *git_ref; - // First try local branches - int gitError = git_branch_lookup(&git_ref, repository.git_repository, name.UTF8String, GIT_BRANCH_LOCAL); - if (gitError != GIT_OK) { + return [self branchByLookingUpBranchNamed:name type:GTBranchTypeAny inRepository:repository error:error]; +} + ++ (instancetype)branchByLookingUpBranchNamed:(NSString *)name type:(GTBranchType)type inRepository:(GTRepository *)repository error:(NSError **)error { + git_reference *git_ref = NULL; + + // If any is requested, we'll perform the local lookup first. + int gitError = GIT_ENOTFOUND; // Must be != GIT_OK for "any" lookups + if ((type & GTBranchTypeLocal)) { + gitError = git_branch_lookup(&git_ref, repository.git_repository, name.UTF8String, GIT_BRANCH_LOCAL); + if (gitError != GIT_OK) { + if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Local branch lookup failed"]; + if (type == GTBranchTypeLocal) return nil; // Local-only lookup failed, bail with nil. + if (error != NULL) *error = nil; // We're doing 'any' lookup, so drop the error. + } + } + if ((gitError != GIT_OK) && (type & ~GTBranchTypeLocal)) { int gitError = git_branch_lookup(&git_ref, repository.git_repository, name.UTF8String, GIT_BRANCH_REMOTE); if (gitError != GIT_OK) { - if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Branch lookup failed"]; + if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Remote branch lookup failed"]; return nil; } } From a17178f00725df5bb40674cb7cd2f1fc4dd02707 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 18:20:38 +0100 Subject: [PATCH 11/12] Remove `success` parameter from `-trackingBranchWithError:success`. --- Classes/GTBranch.h | 14 +++++++++++--- Classes/GTBranch.m | 10 +++------- ObjectiveGitTests/GTBranchSpec.m | 16 ++++------------ 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index 02f93985f..9e5224fff 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -106,10 +106,18 @@ typedef enum { // Returns the reloaded branch, or nil if an error occurred. - (GTBranch *)reloadedBranchWithError:(NSError **)error; +// Fetch the branch's remote tracking branch. +// // If the receiver is a local branch, looks up and returns its tracking branch. -// If the receiver is a remote branch, returns self. If no tracking branch was -// found, returns nil and sets `success` to YES. -- (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success; +// If the receiver is a remote branch, returns self. +// If no tracking branch was found, returns nil with a nil error. +// +// error - The error if one occurred. +// +// Returns the tracking branch for the reciever if it's a local branch, +// nil if the receiver is a remote branch but without an error set, +// or nil if there was an error (and the error pointer will be set. +- (GTBranch *)trackingBranchWithError:(NSError **)error; // Count all commits in this branch // diff --git a/Classes/GTBranch.m b/Classes/GTBranch.m index f22b42196..ea046ee6e 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -244,9 +244,9 @@ - (GTBranch *)reloadedBranchWithError:(NSError **)error { return [[self.class alloc] initWithReference:reloadedRef]; } -- (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success { +- (GTBranch *)trackingBranchWithError:(NSError **)error { if (self.branchType == GTBranchTypeRemote) { - if (success != NULL) *success = YES; + if (error != NULL) *error = nil; return self; } @@ -255,24 +255,20 @@ - (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success // GIT_ENOTFOUND means no tracking branch found. if (gitError == GIT_ENOTFOUND) { - if (success != NULL) *success = YES; + if (error != NULL) *error = nil; return nil; } if (gitError != GIT_OK) { - if (success != NULL) *success = NO; if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to create reference to tracking branch from %@", self]; return nil; } if (trackingRef == NULL) { - if (success != NULL) *success = NO; if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Got a NULL remote ref for %@", self]; return nil; } - if (success != NULL) *success = YES; - return [[self class] branchWithReference:[[GTReference alloc] initWithGitReference:trackingRef repository:self.repository]]; } diff --git a/ObjectiveGitTests/GTBranchSpec.m b/ObjectiveGitTests/GTBranchSpec.m index 8f7ab0e7b..a804f1872 100644 --- a/ObjectiveGitTests/GTBranchSpec.m +++ b/ObjectiveGitTests/GTBranchSpec.m @@ -23,10 +23,8 @@ expect(masterBranch).notTo.beNil(); expect(error).to.beNil(); - BOOL success = NO; - trackingBranch = [masterBranch trackingBranchWithError:&error success:&success]; + trackingBranch = [masterBranch trackingBranchWithError:&error]; expect(trackingBranch).notTo.equal(masterBranch); - expect(success).to.beTruthy(); expect(error).to.beNil(); }); @@ -153,10 +151,8 @@ expect(masterBranch).notTo.beNil(); expect(error).to.beNil(); - BOOL success = NO; - GTBranch *trackingBranch = [masterBranch trackingBranchWithError:&error success:&success]; + GTBranch *trackingBranch = [masterBranch trackingBranchWithError:&error]; expect(trackingBranch).notTo.beNil(); - expect(success).to.beTruthy(); expect(error).to.beNil(); }); @@ -169,10 +165,8 @@ GTBranch *otherBranch = [GTBranch branchWithReference:otherRef]; expect(otherBranch).notTo.beNil(); - BOOL success = NO; - trackingBranch = [otherBranch trackingBranchWithError:&error success:&success]; + trackingBranch = [otherBranch trackingBranchWithError:&error]; expect(trackingBranch).to.beNil(); - expect(success).to.beTruthy(); expect(error).to.beNil(); }); @@ -185,10 +179,8 @@ GTBranch *remoteBranch = [GTBranch branchWithReference:remoteRef]; expect(remoteBranch).notTo.beNil(); - BOOL success = NO; - GTBranch *remoteTrackingBranch = [remoteBranch trackingBranchWithError:&error success:&success]; + GTBranch *remoteTrackingBranch = [remoteBranch trackingBranchWithError:&error]; expect(remoteTrackingBranch).to.equal(remoteBranch); - expect(success).to.beTruthy(); expect(error).to.beNil(); }); }); From 865c48aca709ba4b5d94ade289d0e19df29d9a68 Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 10 Nov 2013 21:54:12 +0100 Subject: [PATCH 12/12] Tests. --- ObjectiveGitTests/GTBranchSpec.m | 159 ++++++++++++++++++++++++++----- 1 file changed, 133 insertions(+), 26 deletions(-) diff --git a/ObjectiveGitTests/GTBranchSpec.m b/ObjectiveGitTests/GTBranchSpec.m index a804f1872..a64370152 100644 --- a/ObjectiveGitTests/GTBranchSpec.m +++ b/ObjectiveGitTests/GTBranchSpec.m @@ -28,43 +28,150 @@ expect(error).to.beNil(); }); -describe(@"name", ^{ - it(@"should use just the branch name for a local branch", ^{ - expect(masterBranch.name).to.equal(@"master"); +describe(@"branch initialization", ^{ + describe(@"+branchByLookingUpBranchNamed:type:inRepository:error:", ^{ + it(@"works for local branches", ^{ + NSError *error = nil; + GTBranch *branch = [GTBranch branchByLookingUpBranchNamed:@"master" type:GTBranchTypeLocal inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + expect(branch.branchType).to.equal(GTBranchTypeLocal); + }); + + it(@"works for remote branches", ^{ + NSError *error = nil; + GTBranch *branch = [GTBranch branchByLookingUpBranchNamed:@"origin/master" type:GTBranchTypeRemote inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + expect(branch.branchType).to.equal(GTBranchTypeRemote); + }); + + it(@"fails for non-existing branches", ^{ + NSError *error = nil; + GTBranch *branch = [GTBranch branchByLookingUpBranchNamed:@"plonk" type:GTBranchTypeRemote inRepository:repository error:&error]; + expect(branch).to.beNil(); + expect(error.domain).to.equal(GTGitErrorDomain); + expect(error.code).to.equal(GIT_ENOTFOUND); + }); + + it(@"finds local branches before remote ones in an 'any' lookup", ^{ + NSError *error = nil; + GTReference *ref = [GTReference referenceByCreatingReferenceNamed:@"refs/heads/origin/master" fromReferenceTarget:@"refs/heads/master" inRepository:repository error:NULL]; + expect(ref).notTo.beNil(); + + GTBranch *duplicateBranch = [GTBranch branchWithReference:ref]; + expect(duplicateBranch).notTo.beNil(); + expect(duplicateBranch.branchType).to.equal(GTBranchTypeLocal); + + GTBranch *remoteBranch = [GTBranch branchByLookingUpBranchNamed:@"origin/master" type:GTBranchTypeRemote inRepository:repository error:NULL]; + expect(remoteBranch).notTo.beNil(); + expect(remoteBranch.branchType).to.equal(GTBranchTypeRemote); + + GTBranch *branch = [GTBranch branchByLookingUpBranchNamed:@"origin/master" type:GTBranchTypeAny inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + expect(branch.branchType).to.equal(GTBranchTypeLocal); + expect(branch).to.equal(duplicateBranch); + }); + + it(@"find remote branches if there's no ambiguity in an 'any' lookup", ^{ + NSError *error = nil; + + GTBranch *branch = [GTBranch branchByLookingUpBranchNamed:@"origin/master" type:GTBranchTypeAny inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + expect(branch.branchType).to.equal(GTBranchTypeRemote); + }); }); - it(@"should include the remote name for a tracking branch", ^{ - expect(trackingBranch.name).to.equal(@"origin/master"); - }); -}); - -describe(@"shortName", ^{ - it(@"should use just the branch name for a local branch", ^{ - expect(masterBranch.shortName).to.equal(@"master"); + describe(@"+branchByCreatingBranchNamed:target:force:inRepository:error:", ^{ + it(@"works with a non-conflicting name", ^{ + NSError *error = nil; + GTCommit *targetCommit = [masterBranch targetCommitAndReturnError:NULL]; + GTBranch *branch = [GTBranch branchByCreatingBranchNamed:@"my-branch" target:targetCommit force:NO inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + expect(branch.branchType).to.equal(GTBranchTypeLocal); + }); + + it(@"fails with an already existing name", ^{ + NSError *error = nil; + GTCommit *targetCommit = [masterBranch targetCommitAndReturnError:NULL]; + GTBranch *branch = [GTBranch branchByCreatingBranchNamed:@"1-and_more" target:targetCommit force:NO inRepository:repository error:&error]; + expect(branch).to.beNil(); + expect(error.domain).to.equal(GTGitErrorDomain); + expect(error.code).to.equal(GIT_EEXISTS); + }); + + it(@"delete the offending branch if creation is forced", ^{ + NSError *error = nil; + GTCommit *targetCommit = [masterBranch targetCommitAndReturnError:NULL]; + GTBranch *branch = [GTBranch branchByCreatingBranchNamed:@"1-and_more" target:targetCommit force:YES inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + }); }); - it(@"should not include the remote name for a tracking branch", ^{ - expect(trackingBranch.shortName).to.equal(@"master"); + describe(@"+branchWithReferenceNamed:inRepository:error:", ^{ + it(@"works for existing local references", ^{ + NSError *error = nil; + GTBranch *branch = [GTBranch branchWithReferenceNamed:@"refs/heads/1-and_more" inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + }); + + it(@"works for existing remote references", ^{ + NSError *error = nil; + GTBranch *branch = [GTBranch branchWithReferenceNamed:@"refs/remotes/origin/master" inRepository:repository error:&error]; + expect(branch).notTo.beNil(); + expect(error).to.beNil(); + }); + + it(@"fails for non existent references", ^{ + NSError *error = nil; + GTBranch *branch = [GTBranch branchWithReferenceNamed:@"refs/heads/nowhere" inRepository:repository error:&error]; + expect(branch).to.beNil(); + expect(error.domain).to.equal(GTGitErrorDomain); + expect(error.code).to.equal(GIT_ENOTFOUND); + }); }); }); -describe(@"remoteName", ^{ - it(@"should return nil for a local branch", ^{ - expect(masterBranch.remoteName).to.beNil(); - }); +describe(@"basic properties", ^{ + describe(@"local branches", ^{ + it(@"should be GTBranchTypeLocal for a local branch", ^{ + expect(masterBranch.branchType).to.equal(GTBranchTypeLocal); + }); - it(@"should return the remote name for a tracking branch", ^{ - expect(trackingBranch.remoteName).to.equal(@"origin"); - }); -}); + it(@"should use just the branch name in their name", ^{ + expect(masterBranch.name).to.equal(@"master"); + }); + + it(@"should use just the branch name in their short name", ^{ + expect(masterBranch.shortName).to.equal(@"master"); + }); -describe(@"branchType", ^{ - it(@"should be GTBranchTypeLocal for a local branch", ^{ - expect(masterBranch.branchType).to.equal(GTBranchTypeLocal); + it(@"should return nil for their remote name", ^{ + expect(masterBranch.remoteName).to.beNil(); + }); }); - it(@"should be GTBranchTypeRemote for a tracking branch", ^{ - expect(trackingBranch.branchType).to.equal(GTBranchTypeRemote); + describe(@"remote branches", ^{ + it(@"should have a GTBranchTypeLocal branch type", ^{ + expect(trackingBranch.branchType).to.equal(GTBranchTypeRemote); + }); + + it(@"should include the remote name in their name", ^{ + expect(trackingBranch.name).to.equal(@"origin/master"); + }); + + it(@"should not include the remote name in their short name", ^{ + expect(trackingBranch.shortName).to.equal(@"master"); + }); + + it(@"should return the remote name for their remote name", ^{ + expect(trackingBranch.remoteName).to.equal(@"origin"); + }); }); });