diff --git a/Classes/GTBlob.h b/Classes/GTBlob.h index 42c350535..7e42beb44 100644 --- a/Classes/GTBlob.h +++ b/Classes/GTBlob.h @@ -31,15 +31,37 @@ #import "GTObject.h" -@interface GTBlob : GTObject {} +@interface GTBlob : GTObject -+ (id)blobWithString:(NSString *)string inRepository:(GTRepository *)repository error:(NSError **)error; -+ (id)blobWithData:(NSData *)data inRepository:(GTRepository *)repository error:(NSError **)error; -+ (id)blobWithFile:(NSURL *)file inRepository:(GTRepository *)repository error:(NSError **)error; +// Convenience class methods ++ (instancetype)blobWithString:(NSString *)string inRepository:(GTRepository *)repository error:(NSError **)error; ++ (instancetype)blobWithData:(NSData *)data inRepository:(GTRepository *)repository error:(NSError **)error; ++ (instancetype)blobWithFile:(NSURL *)file inRepository:(GTRepository *)repository error:(NSError **)error; -- (id)initWithString:(NSString *)string inRepository:(GTRepository *)repository error:(NSError **)error; -- (id)initWithData:(NSData *)data inRepository:(GTRepository *)repository error:(NSError **)error; -- (id)initWithFile:(NSURL *)file inRepository:(GTRepository *)repository error:(NSError **)error; +// Convenience wrapper around `-initWithData:inRepository:error` that converts the string to UTF8 data +- (instancetype)initWithString:(NSString *)string inRepository:(GTRepository *)repository error:(NSError **)error; + +// Creates a new blob from the passed data. +// +// This writes data to the repository's object database. +// +// data - The data to write. +// repository - The repository to put the object in. +// error - Will be set if an error occurs. +// +// Returns a newly created blob object, or nil if an error occurs. +- (instancetype)initWithData:(NSData *)data inRepository:(GTRepository *)repository error:(NSError **)error; + +// Creates a new blob from the specified file. +// +// This copies the data from the file to the repository's object database. +// +// data - The file to copy contents from. +// repository - The repository to put the object in. +// error - Will be set if an error occurs. +// +// Returns a newly created blob object, or nil if an error occurs. +- (instancetype)initWithFile:(NSURL *)file inRepository:(GTRepository *)repository error:(NSError **)error; // The underlying `git_object` as a `git_blob` object. - (git_blob *)git_blob __attribute__((objc_returns_inner_pointer)); diff --git a/Classes/GTBlob.m b/Classes/GTBlob.m index e65b11b91..c522e8be8 100644 --- a/Classes/GTBlob.m +++ b/Classes/GTBlob.m @@ -55,6 +55,9 @@ + (id)blobWithFile:(NSURL *)file inRepository:(GTRepository *)repository error:( } - (id)initWithOid:(const git_oid *)oid inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(oid != NULL); + NSParameterAssert(repository != nil); + git_object *obj; int gitError = git_object_lookup(&obj, repository.git_repository, oid, (git_otype) GTObjectTypeBlob); if (gitError < GIT_OK) { @@ -73,6 +76,9 @@ - (id)initWithString:(NSString *)string inRepository:(GTRepository *)repository } - (id)initWithData:(NSData *)data inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(data != nil); + NSParameterAssert(repository != nil); + git_oid oid; int gitError = git_blob_create_frombuffer(&oid, repository.git_repository, [data bytes], data.length); if(gitError < GIT_OK) { @@ -86,8 +92,11 @@ - (id)initWithData:(NSData *)data inRepository:(GTRepository *)repository error: } - (id)initWithFile:(NSURL *)file inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(file != nil); + NSParameterAssert(repository != nil); + git_oid oid; - int gitError = git_blob_create_fromworkdir(&oid, repository.git_repository, [[file path] UTF8String]); + int gitError = git_blob_create_fromdisk(&oid, repository.git_repository, [[file path] fileSystemRepresentation]); if(gitError < GIT_OK) { if(error != NULL) { *error = [NSError git_errorFor:gitError description:@"Failed to create blob from NSURL"]; diff --git a/Classes/GTBranch.h b/Classes/GTBranch.h index 7addf9399..1fd5ad2da 100644 --- a/Classes/GTBranch.h +++ b/Classes/GTBranch.h @@ -30,53 +30,68 @@ @class GTRepository; typedef enum { - GTBranchTypeLocal = 1, - GTBranchTypeRemote + GTBranchTypeLocal = GIT_BRANCH_LOCAL, + GTBranchTypeRemote = GIT_BRANCH_REMOTE, + GTBranchTypeAny = GIT_BRANCH_REMOTE|GIT_BRANCH_LOCAL, } GTBranchType; @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, assign) GTBranch *trackingBranch; +@property (nonatomic, readonly, getter=isHead) BOOL head; + +// 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 type:(GTBranchType)type inRepository:(GTRepository *)repository error:(NSError **)error; -+ (NSString *)localNamePrefix; -+ (NSString *)remoteNamePrefix; +// 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)branchWithReferenceNamed:(NSString *)referenceName inRepository:(GTRepository *)repo error:(NSError **)error; ++ (id)branchWithReference:(GTReference *)ref; -- (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo; -+ (id)branchWithReference:(GTReference *)ref repository:(GTRepository *)repo; +// Designated initializer +- (id)initWithReference:(GTReference *)ref; // 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; -// 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; +// 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. @@ -88,6 +103,37 @@ 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 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 +// +// error - will be filled if an error occurs +// +// 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. // // 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..1ade2a117 100644 --- a/Classes/GTBranch.m +++ b/Classes/GTBranch.m @@ -32,65 +32,103 @@ @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]; -} +#pragma mark - +#pragma mark Lifecycle -- (NSUInteger)hash { - return self.name.hash ^ self.SHA.hash; -} - - -#pragma mark API ++ (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; + } -+ (NSString *)localNamePrefix { - return @"refs/heads/"; + GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:repository]; + return [[self alloc] initWithReference:ref]; } -+ (NSString *)remoteNamePrefix { - return @"refs/remotes/"; ++ (instancetype)branchByLookingUpBranchNamed:(NSString *)name inRepository:(GTRepository *)repository error:(NSError **)error { + return [self branchByLookingUpBranchNamed:name type:GTBranchTypeAny inRepository:repository error:error]; } -+ (id)branchWithName:(NSString *)branchName repository:(GTRepository *)repo error:(NSError **)error { - return [[self alloc] initWithName:branchName repository:repo 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:@"Remote branch lookup failed"]; + return nil; + } + } -+ (id)branchWithReference:(GTReference *)ref repository:(GTRepository *)repo { - return [[self alloc] initWithReference:ref repository:repo]; + GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:repository]; + return [[self alloc] initWithReference:ref]; } -- (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]; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:referenceName inRepository:repo error:error]; if (ref == nil) return nil; - return [self initWithReference:ref repository:repo]; + return [[self alloc] initWithReference:ref]; } -- (id)initWithReference:(GTReference *)ref repository:(GTRepository *)repo { ++ (id)branchWithReference:(GTReference *)ref { + return [[self alloc] initWithReference:ref]; +} + +// Designated initializer +- (id)initWithReference:(GTReference *)ref { NSParameterAssert(ref != nil); - NSParameterAssert(repo != nil); self = [super init]; if (self == nil) return nil; - _repository = repo; _reference = ref; 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; + 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 { @@ -109,39 +147,23 @@ - (NSString *)shortName { return @(name); } -- (NSString *)SHA { - return self.reference.targetSHA; -} - - (NSString *)remoteName { - if (self.branchType == GTBranchTypeLocal) return nil; - - const char *name; - int gitError = git_branch_name(&name, self.reference.git_reference); - if (gitError != GIT_OK) return nil; + int nameLength = git_branch_remote_name(NULL, 0, self.repository.git_repository, self.reference.name.UTF8String); + if (nameLength <= 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); } -- (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]; +- (GTRepository *)repository { + return self.reference.repository; } -- (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 +174,33 @@ - (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; +- (GTBranch *)upstreamBranch { + git_reference *git_ref; + int gitError = git_branch_upstream(&git_ref, self.reference.git_reference); + if (gitError != GIT_OK) return nil; - success = [enumerator hideSHA:mergeBase.SHA error:error]; - if (!success) return nil; + GTReference *ref = [[GTReference alloc] initWithGitReference:git_ref repository:self.repository]; + return [GTBranch branchWithReference:ref]; +} - return [enumerator allObjectsWithError:error]; +- (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 + +- (GTCommit *)targetCommitAndReturnError:(NSError **)error { + if (self.SHA == nil) { + if (error != NULL) *error = GTReference.invalidReferenceError; + return nil; + } + + return [GTCommit lookupWithSHA:self.SHA inRepository:self.repository error:error]; } - (BOOL)deleteWithError:(NSError **)error { @@ -182,9 +213,29 @@ - (BOOL)deleteWithError:(NSError **)error { return YES; } -- (GTBranch *)trackingBranchWithError:(NSError **)error success:(BOOL *)success { +- (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; + + return [[self.class alloc] initWithReference:reloadedRef]; +} + +- (GTBranch *)trackingBranchWithError:(NSError **)error { if (self.branchType == GTBranchTypeRemote) { - if (success != NULL) *success = YES; + if (error != NULL) *error = nil; return self; } @@ -193,32 +244,49 @@ - (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]]; +} + +- (NSUInteger)numberOfCommitsWithError:(NSError **)error { + GTEnumerator *enumerator = [[GTEnumerator alloc] initWithRepository:self.repository error:error]; + if (enumerator == nil) return NSNotFound; - return [[self class] branchWithReference:[[GTReference alloc] initWithGitReference:trackingRef repository:self.repository] repository:self.repository]; + if (![enumerator pushSHA:self.SHA error:error]) return NSNotFound; + return [enumerator countRemainingObjects:error]; } -- (GTBranch *)reloadedBranchWithError:(NSError **)error { - GTReference *reloadedRef = [self.reference reloadedReferenceWithError:error]; - if (reloadedRef == nil) return nil; +- (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; - return [[self.class alloc] initWithReference:reloadedRef repository:self.repository]; + 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 { diff --git a/Classes/GTEnumerator.m b/Classes/GTEnumerator.m index da54395a0..af72708cc 100644 --- a/Classes/GTEnumerator.m +++ b/Classes/GTEnumerator.m @@ -32,7 +32,7 @@ #import "NSError+Git.h" #import "NSString+Git.h" #import "GTRepository.h" -#import "GTRepository+Private.h" +#import "GTObject+Private.h" #import "GTOID.h" @interface GTEnumerator () @@ -147,7 +147,7 @@ - (GTCommit *)nextObjectWithSuccess:(BOOL *)success error:(NSError **)error { } // Ignore error if we can't lookup object and just return nil. - GTCommit *commit = [self.repository lookupObjectByGitOid:&oid objectType:GTObjectTypeCommit error:error]; + GTCommit *commit = [GTCommit lookupWithGitOID:&oid inRepository:self.repository error:error]; if (success != NULL) *success = (commit != nil); return commit; } diff --git a/Classes/GTIndex.m b/Classes/GTIndex.m index f5d946fda..f74d2e0ea 100644 --- a/Classes/GTIndex.m +++ b/Classes/GTIndex.m @@ -31,7 +31,7 @@ #import "GTIndexEntry.h" #import "NSError+Git.h" #import "GTRepository.h" -#import "GTRepository+Private.h" +#import "GTObject+Private.h" #import "GTOID.h" #import "GTTree.h" @@ -164,10 +164,10 @@ - (GTTree *)writeTree:(NSError **)error { int status = git_index_write_tree(&oid, self.git_index); if (status != GIT_OK) { if (error != NULL) *error = [NSError git_errorFor:status description:@"Failed to write index."]; - return NULL; + return nil; } - return [self.repository lookupObjectByGitOid:&oid objectType:GTObjectTypeTree error:NULL]; + return [GTTree lookupWithGitOID:&oid inRepository:self.repository error:error]; } - (NSArray *)entries { diff --git a/Classes/GTObject+Private.h b/Classes/GTObject+Private.h new file mode 100644 index 000000000..b0406b9cb --- /dev/null +++ b/Classes/GTObject+Private.h @@ -0,0 +1,13 @@ +// +// GTObject+Private.h +// ObjectiveGitFramework +// +// Created by Etienne on 15/07/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import + +@interface GTObject () ++ (instancetype)lookupWithGitOID:(const git_oid *)git_oid inRepository:(GTRepository *)repository error:(NSError **)error; +@end diff --git a/Classes/GTObject.h b/Classes/GTObject.h index fc6ec0b39..4ceb939f4 100644 --- a/Classes/GTObject.h +++ b/Classes/GTObject.h @@ -54,6 +54,25 @@ typedef enum { @property (nonatomic, readonly, strong) GTRepository *repository; @property (nonatomic, readonly) GTOID *OID; +// Lookup an object in a repository. +// +// This method will automatically restrict the requested object type depending on +// the class of the receiver. +// For example, if you expect the OID to point to a commit, call it on `GTCommit`. +// Performing the lookup on `GTObject` will give you back an object of the class +// corresponding to the actual object type. +// +// OID - The OID of the object to lookup. +// repository - The repository to perform the lookup in. +// error - Will be set if an error occurs. +// +// Returns the object corresponding to the OID, or nil if an error occurred. ++ (instancetype)lookupWithOID:(GTOID *)OID inRepository:(GTRepository *)repository error:(NSError **)error; + +// Convenience method to lookup an object given its SHA. +// See +lookupObjectWithOID:inRepository:error: ++ (instancetype)lookupWithSHA:(NSString *)SHA inRepository:(GTRepository *)repository error:(NSError **)error; + // Convenience initializers - (id)initWithObj:(git_object *)theObject inRepository:(GTRepository *)theRepo; + (id)objectWithObj:(git_object *)theObject inRepository:(GTRepository *)theRepo; diff --git a/Classes/GTObject.m b/Classes/GTObject.m index e16ac1ff0..6a9e14ae7 100644 --- a/Classes/GTObject.m +++ b/Classes/GTObject.m @@ -44,6 +44,45 @@ @interface GTObject () @implementation GTObject ++ (git_otype)_lookupType { + git_otype type = GIT_OBJ_BAD; + if (self == [GTObject class]) { + type = GIT_OBJ_ANY; + } else if (self == [GTCommit class]) { + type = GIT_OBJ_COMMIT; + } else if (self == [GTTree class]) { + type = GIT_OBJ_TREE; + } else if (self == [GTBlob class]) { + type = GIT_OBJ_BLOB; + } else if (self == [GTTag class]) { + type = GIT_OBJ_TAG; + } + return type; +} + ++ (instancetype)lookupWithGitOID:(const git_oid *)git_oid inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(git_oid != nil); + NSParameterAssert(repository != nil); + + git_object *obj; + int gitError = git_object_lookup(&obj, repository.git_repository, git_oid, self._lookupType); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Object lookup failed"]; + return nil; + } + + return [self objectWithObj:obj inRepository:repository]; + +} + ++ (instancetype)lookupWithOID:(GTOID *)OID inRepository:(GTRepository *)repository error:(NSError **)error { + return [self lookupWithGitOID:OID.git_oid inRepository:repository error:error]; +} + ++ (instancetype)lookupWithSHA:(NSString *)SHA inRepository:(GTRepository *)repository error:(NSError **)error { + return [self lookupWithOID:[GTOID oidWithSHA:SHA] inRepository:repository error:error]; +} + - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p> type: %@, shortSha: %@, sha: %@", NSStringFromClass([self class]), self, self.type, self.shortSHA, self.SHA]; } diff --git a/Classes/GTReference.h b/Classes/GTReference.h index 715681775..2b8dc4774 100644 --- a/Classes/GTReference.h +++ b/Classes/GTReference.h @@ -48,26 +48,21 @@ typedef enum { @property (nonatomic, readonly) const git_oid *git_oid; @property (nonatomic, strong, readonly) GTOID *OID; -// Whether this is a remote-tracking branch. -@property (nonatomic, readonly, getter = isRemote) BOOL remote; -// The reflog for the reference. -@property (nonatomic, readonly, strong) GTReflog *reflog; +// The name of the reference. +@property (nonatomic, readonly, copy) NSString *name; -// Convenience initializers -+ (id)referenceByLookingUpReferencedNamed:(NSString *)refName inRepository:(GTRepository *)theRepo error:(NSError **)error; -- (id)initByLookingUpReferenceNamed:(NSString *)refName inRepository:(GTRepository *)theRepo error:(NSError **)error; +// Whether this is a tag. +@property (nonatomic, readonly, getter = isTag) BOOL tag; -+ (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; +// Whether this is a local branch. +@property (nonatomic, readonly, getter = isBranch) BOOL branch; -+ (id)referenceByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error; -- (id)initByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error; - -- (id)initWithGitReference:(git_reference *)ref repository:(GTRepository *)repository; +// Whether this is a remote-tracking branch. +@property (nonatomic, readonly, getter = isRemote) BOOL remote; -// The underlying `git_reference` object. -- (git_reference *)git_reference __attribute__((objc_returns_inner_pointer)); +// The reflog for the reference. +@property (nonatomic, readonly, strong) GTReflog *reflog; // The target (either GTObject or GTReference) to which the reference points. @property (nonatomic, readonly, copy) id unresolvedTarget; @@ -81,6 +76,16 @@ typedef enum { // The SHA of the target object @property (nonatomic, readonly, copy) NSString *targetSHA; +// Convenience initializers ++ (id)referenceByLookingUpReferenceNamed:(NSString *)refName inRepository:(GTRepository *)theRepo error:(NSError **)error; ++ (id)referenceByCreatingReferenceNamed:(NSString *)refName fromReferenceTarget:(NSString *)target inRepository:(GTRepository *)theRepo error:(NSError **)error; ++ (id)referenceByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error; + +- (id)initWithGitReference:(git_reference *)ref repository:(GTRepository *)repository; + +// The underlying `git_reference` object. +- (git_reference *)git_reference __attribute__((objc_returns_inner_pointer)); + // Updates the on-disk reference to point to the target and returns the updated // reference. // @@ -92,9 +97,6 @@ typedef enum { // Returns the updated reference, or nil if an error occurred. - (GTReference *)referenceByUpdatingTarget:(NSString *)newTarget error:(NSError **)error; -// The name of the reference. -@property (nonatomic, readonly, copy) NSString *name; - // Updates the on-disk reference to the name and returns the renamed reference. // // Note that this does *not* change the receiver's name. diff --git a/Classes/GTReference.m b/Classes/GTReference.m index e09b905b1..4e270c55f 100644 --- a/Classes/GTReference.m +++ b/Classes/GTReference.m @@ -27,7 +27,7 @@ #import "GTOID.h" #import "GTReflog+Private.h" #import "GTRepository.h" -#import "GTRepository+Private.h" +#import "GTObject+Private.h" #import "NSError+Git.h" #import "NSString+Git.h" @@ -51,58 +51,43 @@ @interface GTReference () @implementation GTReference -- (void)dealloc { - if (_git_reference != NULL) { - git_reference_free(_git_reference); - _git_reference = NULL; - } -} - - -#pragma mark API +#pragma mark - +#pragma mark Class methods -- (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]; ++ (NSError *)invalidReferenceError { + return [NSError git_errorFor:GTReferenceErrorCodeInvalidReference description:@"Invalid git_reference."]; } -+ (id)referenceByResolvingSymbolicReference:(GTReference *)symbolicRef error:(NSError **)error { - return [[self alloc] initByResolvingSymbolicReference:symbolicRef error:error]; ++ (BOOL)isValidReferenceName:(NSString *)refName { + return git_reference_is_valid_name(refName.UTF8String) == 1; } -- (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 +95,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 +108,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 { @@ -139,6 +124,46 @@ - (id)initWithGitReference:(git_reference *)ref repository:(GTRepository *)repo return self; } +- (void)dealloc { + if (_git_reference != NULL) { + git_reference_free(_git_reference); + _git_reference = NULL; + } +} + +#pragma mark - +#pragma mark NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p>{ OID: %@, type: %@, remote: %i }", self.class, self, self.OID, referenceTypeToString(self.referenceType), (int)self.remote]; +} + +- (NSUInteger)hash { + return self.name.hash; +} + +- (BOOL)isEqual:(GTReference *)reference { + if (self == reference) return YES; + if (![reference isKindOfClass:GTReference.class]) return NO; + + return [self.repository isEqual:reference.repository] && [self.name isEqual:reference.name] && [self.unresolvedTarget isEqual:reference.unresolvedTarget]; +} + +#pragma mark - +#pragma mark Properties + +- (BOOL)isBranch { + return git_reference_is_branch(self.git_reference) != 0; +} + +- (BOOL)isTag { + return git_reference_is_tag(self.git_reference) != 0; +} + +- (BOOL)isRemote { + return git_reference_is_remote(self.git_reference) != 0; +} + - (NSString *)name { const char *refName = git_reference_name(self.git_reference); if (refName == NULL) return nil; @@ -146,17 +171,15 @@ - (NSString *)name { return @(refName); } -- (GTReference *)referenceByRenaming:(NSString *)newName error:(NSError **)error { - NSParameterAssert(newName != nil); - - git_reference *newRef = NULL; - int gitError = git_reference_rename(&newRef, self.git_reference, newName.UTF8String, 0); - if (gitError != GIT_OK) { - if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to rename reference %@ to %@.", self.name, newName]; - return NO; - } +- (const git_oid *)git_oid { + return git_reference_target(self.git_reference); +} - return [[self.class alloc] initWithGitReference:newRef repository:self.repository]; +- (GTOID *)OID { + const git_oid *oid = self.git_oid; + if (oid == NULL) return nil; + + return [[GTOID alloc] initWithGitOid:oid]; } - (GTReferenceType)referenceType { @@ -168,12 +191,12 @@ - (id)unresolvedTarget { const git_oid *oid = git_reference_target(self.git_reference); if (oid == NULL) return nil; - return [self.repository lookupObjectByGitOid:oid error:NULL]; + return [GTObject lookupWithGitOID:oid inRepository:self.repository error:NULL]; } else if (self.referenceType == GTReferenceTypeSymbolic) { 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; } @@ -194,6 +217,27 @@ - (NSString *)targetSHA { return [self.resolvedTarget SHA]; } +- (GTReflog *)reflog { + return [[GTReflog alloc] initWithReference:self]; +} + + +#pragma mark - +#pragma mark API + +- (GTReference *)referenceByRenaming:(NSString *)newName error:(NSError **)error { + NSParameterAssert(newName != nil); + + git_reference *newRef = NULL; + int gitError = git_reference_rename(&newRef, self.git_reference, newName.UTF8String, 0); + if (gitError != GIT_OK) { + if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to rename reference %@ to %@.", self.name, newName]; + return NO; + } + + return [[self.class alloc] initWithGitReference:newRef repository:self.repository]; +} + - (GTReference *)referenceByUpdatingTarget:(NSString *)newTarget error:(NSError **)error { NSParameterAssert(newTarget != nil); @@ -230,48 +274,8 @@ - (GTReference *)resolvedReferenceWithError:(NSError **)error { return [GTReference referenceByResolvingSymbolicReference:self error:error]; } -- (const git_oid *)git_oid { - return git_reference_target(self.git_reference); -} - -- (GTOID *)OID { - const git_oid *oid = self.git_oid; - if (oid == NULL) return nil; - - return [[GTOID alloc] initWithGitOid:oid]; -} - - (GTReference *)reloadedReferenceWithError:(NSError **)error { - return [[self.class alloc] initByLookingUpReferenceNamed:self.name inRepository:self.repository error:error]; -} - -+ (NSError *)invalidReferenceError { - return [NSError git_errorFor:GTReferenceErrorCodeInvalidReference description:@"Invalid git_reference."]; -} - -- (GTReflog *)reflog { - return [[GTReflog alloc] initWithReference:self]; -} - -+ (BOOL)isValidReferenceName:(NSString *)refName { - return git_reference_is_valid_name(refName.UTF8String) == 1; -} - -#pragma mark NSObject - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p>{ OID: %@, type: %@, remote: %i }", self.class, self, self.OID, referenceTypeToString(self.referenceType), (int)self.remote]; -} - -- (NSUInteger)hash { - return self.name.hash; -} - -- (BOOL)isEqual:(GTReference *)reference { - if (self == reference) return YES; - if (![reference isKindOfClass:GTReference.class]) return NO; - - return [self.repository isEqual:reference.repository] && [self.name isEqual:reference.name] && [self.unresolvedTarget isEqual:reference.unresolvedTarget]; + return [self.class referenceByLookingUpReferenceNamed:self.name inRepository:self.repository error:error]; } @end diff --git a/Classes/GTRepository+Committing.m b/Classes/GTRepository+Committing.m index 4ea745b7d..e2df95bd6 100644 --- a/Classes/GTRepository+Committing.m +++ b/Classes/GTRepository+Committing.m @@ -7,7 +7,7 @@ // #import "GTRepository+Committing.h" -#import "GTRepository+Private.h" +#import "GTObject+Private.h" @implementation GTRepository (Committing) @@ -43,7 +43,7 @@ - (GTCommit *)createCommitWithTree:(GTTree *)tree message:(NSString *)message au return nil; } - return [self lookupObjectByGitOid:&oid objectType:GTObjectTypeCommit error:error]; + return [GTCommit lookupWithGitOID:&oid inRepository:self error:error]; } @end diff --git a/Classes/GTRepository+Private.h b/Classes/GTRepository+Private.h deleted file mode 100644 index 43968ee95..000000000 --- a/Classes/GTRepository+Private.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// GTRepository+Private.h -// ObjectiveGitFramework -// -// Created by Etienne on 15/07/13. -// Copyright (c) 2013 GitHub, Inc. All rights reserved. -// - -#import - -@interface GTRepository () -- (id)lookupObjectByGitOid:(const git_oid *)oid objectType:(GTObjectType)type error:(NSError **)error; -- (id)lookupObjectByGitOid:(const git_oid *)oid error:(NSError **)error; -@end diff --git a/Classes/GTRepository+Stashing.m b/Classes/GTRepository+Stashing.m index a69f885f9..9c6f904b7 100644 --- a/Classes/GTRepository+Stashing.m +++ b/Classes/GTRepository+Stashing.m @@ -8,7 +8,7 @@ #import "GTRepository+Stashing.h" #import "GTOID.h" -#import "GTRepository+Private.h" +#import "GTObject+Private.h" #import "GTSignature.h" #import "NSError+Git.h" @@ -24,8 +24,8 @@ - (GTCommit *)stashChangesWithMessage:(NSString *)message flags:(GTRepositorySta if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to stash."]; return nil; } - - return [self lookupObjectByGitOid:&git_oid error:error]; + + return [GTCommit lookupWithGitOID:&git_oid inRepository:self error:error]; } static int stashEnumerationCallback(size_t index, const char *message, const git_oid *stash_id, void *payload) { diff --git a/Classes/GTRepository.h b/Classes/GTRepository.h index 2cddad3cf..0e17a5af9 100644 --- a/Classes/GTRepository.h +++ b/Classes/GTRepository.h @@ -106,10 +106,16 @@ extern NSString *const GTRepositoryCloneOptionsCredentialProvider; @interface GTRepository : NSObject // The file URL for the repository's working directory. -@property (nonatomic, readonly, strong) NSURL *fileURL; +@property (nonatomic, readonly, strong) NSURL *workingDirectoryURL; + // The file URL for the repository's .git directory. @property (nonatomic, readonly, strong) NSURL *gitDirectoryURL; +// Get an URL appropriate for displaying. +// If the repository is bare, returns the `-gitDirectoryURL`. Otherwise returns +// the `-workingDirectoryURL`. +@property (nonatomic, readonly, strong) NSURL *displayURL; + // Is this a bare repository (one without a working directory)? @property (nonatomic, readonly, getter = isBare) BOOL bare; @@ -169,24 +175,28 @@ extern NSString *const GTRepositoryCloneOptionsCredentialProvider; // returns nil (and fills the error parameter) if an error occurred, or a GTRepository object if successful. + (id)cloneFromURL:(NSURL *)originURL toWorkingDirectory:(NSURL *)workdirURL options:(NSDictionary *)options error:(NSError **)error transferProgressBlock:(void (^)(const git_transfer_progress *))transferProgressBlock checkoutProgressBlock:(void (^)(NSString *path, NSUInteger completedSteps, NSUInteger totalSteps))checkoutProgressBlock; -// Lookup objects in the repo by oid or sha1 -- (id)lookupObjectByOID:(GTOID *)oid objectType:(GTObjectType)type error:(NSError **)error; -- (id)lookupObjectByOID:(GTOID *)oid error:(NSError **)error; -- (id)lookupObjectBySHA:(NSString *)sha objectType:(GTObjectType)type error:(NSError **)error; -- (id)lookupObjectBySHA:(NSString *)sha error:(NSError **)error; - // Lookup an object in the repo using a revparse spec -- (id)lookupObjectByRefspec:(NSString *)spec error:(NSError **)error; +- (id)lookupObjectByRevspec:(NSString *)spec error:(NSError **)error; -// List all references in the repository +// List all reference names in the repository. // -// repository - The GTRepository to list references in -// error(out) - will be filled if an error occurs +// error - If not NULL, this pointer will be set to the actual error that occurred. // -// returns an array of NSStrings holding the names of the references -// returns nil if an error occurred and fills the error parameter +// Returns an array of NSStrings holding the names of the references, nil otherwise +// (and the `error` parameter will be set to the actual error that occurred). - (NSArray *)referenceNamesWithError:(NSError **)error; +// Enumerate over all references is the repository. +// +// error - If not NULL, this pointer will be set to the actual error that occurred. +// block - A block which will be called for each reference. You will be passed +// the reference object in `ref`, `error` will be set to something if `ref` +// is nil, and `stop` will stop the enumeration if it's set to YES. +// +// Returns YES if enumeration was successful, NO otherwise (and the `error` +// parameter will be set to the actual error that occurred). +- (BOOL)enumerateReferencesWithError:(NSError **)error usingBlock:(void (^)(GTReference *reference, NSError *error, BOOL *stop))block; + - (GTReference *)headReferenceWithError:(NSError **)error; // Convenience methods to return branches in the repository @@ -206,15 +216,6 @@ extern NSString *const GTRepositoryCloneOptionsCredentialProvider; // returns number of commits in the current branch or NSNotFound if an error occurred - (NSUInteger)numberOfCommitsInCurrentBranch:(NSError **)error; -// Create a new branch with this name and based off this reference. -// -// name - the name for the new branch -// ref - the reference to create the new branch off -// error(out) - will be filled if an error occurs -// -// returns the new branch or nil if an error occurred. -- (GTBranch *)createBranchNamed:(NSString *)name fromReference:(GTReference *)ref error:(NSError **)error; - // Get the current branch. // // error(out) - will be filled if an error occurs @@ -312,51 +313,6 @@ extern NSString *const GTRepositoryCloneOptionsCredentialProvider; // Returns the index, or nil if an error occurred. - (GTIndex *)indexWithError:(NSError **)error; -// Creates a new lightweight tag in this repository. -// -// name - Name for the tag; this name is validated -// for consistency. It should also not conflict with an -// already existing tag name -// target - Object to which this tag points. This object -// must belong to this repository. -// error - Will be filled with a NSError instance on failuer. -// May be NULL. -// -// Returns YES on success or NO otherwise. -- (BOOL)createLightweightTagNamed:(NSString *)tagName target:(GTObject *)target error:(NSError **)error; - -// Creates an annotated tag in this repo. Existing tags are not overwritten. -// -// tagName - Name for the tag; this name is validated -// for consistency. It should also not conflict with an -// already existing tag name -// theTarget - Object to which this tag points. This object -// must belong to this repository. -// tagger - Signature of the tagger for this tag, and -// of the tagging time -// message - Full message for this tag -// error - Will be filled with a NSError object in case of error. -// May be NULL. -// -// Returns the object ID of the newly created tag or nil on error. -- (GTOID *)OIDByCreatingTagNamed:(NSString *)tagName target:(GTObject *)theTarget tagger:(GTSignature *)theTagger message:(NSString *)theMessage error:(NSError **)error; - -// Creates an annotated tag in this repo. Existing tags are not overwritten. -// -// tagName - Name for the tag; this name is validated -// for consistency. It should also not conflict with an -// already existing tag name -// theTarget - Object to which this tag points. This object -// must belong to this repository. -// tagger - Signature of the tagger for this tag, and -// of the tagging time -// message - Full message for this tag -// error - Will be filled with a NSError object in case of error. -// May be NULL. -// -// Returns the newly created tag or nil on error. -- (GTTag *)createTagNamed:(NSString *)tagName target:(GTObject *)theTarget tagger:(GTSignature *)theTagger message:(NSString *)theMessage error:(NSError **)error; - // Checkout a commit // // targetCommit - The commit to checkout. diff --git a/Classes/GTRepository.m b/Classes/GTRepository.m index 7d5961b2b..77b01b7ec 100644 --- a/Classes/GTRepository.m +++ b/Classes/GTRepository.m @@ -76,7 +76,7 @@ @interface GTRepository () @implementation GTRepository - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> fileURL: %@", self.class, self, self.fileURL]; + return [NSString stringWithFormat:@"<%@: %p> displayURL: %@", self.class, self, self.displayURL]; } - (BOOL)isEqual:(GTRepository *)repo { @@ -113,7 +113,7 @@ + (instancetype)initializeEmptyRepositoryAtFileURL:(NSURL *)localFileURL error:( } + (instancetype)initializeEmptyRepositoryAtFileURL:(NSURL *)localFileURL bare:(BOOL)bare error:(NSError **)error { - if (![localFileURL isFileURL] || localFileURL.path == nil) { + if (!localFileURL.isFileURL || localFileURL.path == nil) { if (error != NULL) *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnsupportedSchemeError userInfo:@{ NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid file path URL to initialize repository.", @"") }]; return NO; } @@ -151,7 +151,7 @@ - (id)initWithURL:(NSURL *)localFileURL error:(NSError **)error { } git_repository *r; - int gitError = git_repository_open(&r, localFileURL.path.UTF8String); + int gitError = git_repository_open(&r, localFileURL.path.fileSystemRepresentation); if (gitError < GIT_OK) { if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to open repository at URL %@.", localFileURL]; return nil; @@ -219,8 +219,14 @@ + (id)cloneFromURL:(NSURL *)originURL toWorkingDirectory:(NSURL *)workdirURL opt cloneOptions.remote_callbacks.transfer_progress = transferProgressCallback; cloneOptions.remote_callbacks.payload = &payload; - const char *remoteURL = originURL.absoluteString.UTF8String; - const char *workingDirectoryPath = workdirURL.path.UTF8String; + // If our originURL is local, convert to a path before handing down. + const char *remoteURL = NULL; + if (originURL.isFileURL) { + remoteURL = originURL.path.fileSystemRepresentation; + } else { + remoteURL = originURL.absoluteString.UTF8String; + } + const char *workingDirectoryPath = workdirURL.path.fileSystemRepresentation; git_repository *repository; int gitError = git_clone(&repository, remoteURL, workingDirectoryPath, &cloneOptions); if (gitError < GIT_OK) { @@ -231,46 +237,7 @@ + (id)cloneFromURL:(NSURL *)originURL toWorkingDirectory:(NSURL *)workdirURL opt return [[self alloc] initWithGitRepository:repository]; } -- (id)lookupObjectByGitOid:(const git_oid *)oid objectType:(GTObjectType)type error:(NSError **)error { - git_object *obj; - - int gitError = git_object_lookup(&obj, self.git_repository, oid, (git_otype)type); - if (gitError < GIT_OK) { - if (error != NULL) { - char oid_str[GIT_OID_HEXSZ+1]; - git_oid_tostr(oid_str, sizeof(oid_str), oid); - *error = [NSError git_errorFor:gitError description:@"Failed to lookup object %s in repository.", oid_str]; - } - return nil; - } - - return [GTObject objectWithObj:obj inRepository:self]; -} - -- (id)lookupObjectByGitOid:(const git_oid *)oid error:(NSError **)error { - return [self lookupObjectByGitOid:oid objectType:GTObjectTypeAny error:error]; -} - -- (id)lookupObjectByOID:(GTOID *)oid objectType:(GTObjectType)type error:(NSError **)error { - return [self lookupObjectByGitOid:oid.git_oid objectType:type error:error]; -} - -- (id)lookupObjectByOID:(GTOID *)oid error:(NSError **)error { - return [self lookupObjectByOID:oid objectType:GTObjectTypeAny error:error]; -} - -- (id)lookupObjectBySHA:(NSString *)sha objectType:(GTObjectType)type error:(NSError **)error { - GTOID *oid = [[GTOID alloc] initWithSHA:sha error:error]; - if (!oid) return nil; - - return [self lookupObjectByOID:oid objectType:type error:error]; -} - -- (id)lookupObjectBySHA:(NSString *)sha error:(NSError **)error { - return [self lookupObjectBySHA:sha objectType:GTObjectTypeAny error:error]; -} - -- (id)lookupObjectByRefspec:(NSString *)spec error:(NSError **)error { +- (id)lookupObjectByRevspec:(NSString *)spec error:(NSError **)error { git_object *obj; int gitError = git_revparse_single(&obj, self.git_repository, spec.UTF8String); if (gitError < GIT_OK) { @@ -291,22 +258,57 @@ - (GTReference *)headReferenceWithError:(NSError **)error { return [[GTReference alloc] initWithGitReference:headRef repository:self]; } +typedef void (^GTRepositoryBranchEnumerationBlock)(GTBranch *branch, BOOL *stop); + +struct GTRepositoryBranchEnumerationInfo { + __unsafe_unretained GTRepository *myself; + __unsafe_unretained GTRepositoryBranchEnumerationBlock block; +}; + +int GTRepositoryForeachBranchCallback(const char *name, git_branch_t type, void *payload) { + struct GTRepositoryBranchEnumerationInfo *info = payload; + + GTBranch *branch = [GTBranch branchByLookingUpBranchNamed:@(name) inRepository:info->myself error:NULL]; + if (!branch) return -1; + + BOOL stop = NO; + info->block(branch, &stop); + + return stop == YES ? -1 : 0; +} + +- (BOOL)enumerateBranchesWithType:(GTBranchType)type error:(NSError **)error usingBlock:(GTRepositoryBranchEnumerationBlock)block { + struct GTRepositoryBranchEnumerationInfo info = { .myself = self, .block = block }; + int gitError = git_branch_foreach(self.git_repository, type, GTRepositoryForeachBranchCallback, &info); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Branch enumeration failed"]; + return NO; + } + + return YES; +} + - (NSArray *)localBranchesWithError:(NSError **)error { - return [self branchesWithPrefix:[GTBranch localNamePrefix] error:error]; + NSMutableArray *localBranches = [NSMutableArray array]; + BOOL success = [self enumerateBranchesWithType:GTBranchTypeLocal error:error usingBlock:^(GTBranch *branch, BOOL *stop) { + [localBranches addObject:branch]; + }]; + + if (success != YES) return nil; + + return [localBranches copy]; } - (NSArray *)remoteBranchesWithError:(NSError **)error { - NSArray *remoteBranches = [self branchesWithPrefix:[GTBranch remoteNamePrefix] error:error]; - if (remoteBranches == nil) return nil; + NSMutableArray *remoteBranches = [NSMutableArray array]; + BOOL success = [self enumerateBranchesWithType:GTBranchTypeRemote error:error usingBlock:^(GTBranch *branch, BOOL *stop) { + if (![branch.shortName isEqualToString:@"HEAD"]) + [remoteBranches addObject:branch]; + }]; - NSMutableArray *filteredList = [NSMutableArray arrayWithCapacity:remoteBranches.count]; - for (GTBranch *branch in remoteBranches) { - if (![branch.shortName isEqualToString:@"HEAD"]) { - [filteredList addObject:branch]; - } - } + if (success != YES) return nil; - return filteredList; + return [remoteBranches copy]; } - (NSArray *)branchesWithPrefix:(NSString *)prefix error:(NSError **)error { @@ -314,9 +316,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]; } } @@ -355,7 +357,7 @@ - (NSArray *)allBranchesWithError:(NSError **)error { static int GTRepositoryForeachTagCallback(const char *name, git_oid *oid, void *payload) { struct GTRepositoryTagEnumerationInfo *info = payload; - GTTag *tag = (GTTag *)[info->myself lookupObjectByGitOid:oid objectType:GTObjectTypeTag error:NULL]; + GTTag *tag = [GTTag lookupWithOID:[GTOID oidWithGitOid:oid] inRepository:info->myself error:NULL]; BOOL stop = NO; info->block(tag, &stop); @@ -394,17 +396,6 @@ - (NSUInteger)numberOfCommitsInCurrentBranch:(NSError **)error { return [currentBranch numberOfCommitsWithError:error]; } -- (GTBranch *)createBranchNamed:(NSString *)name fromReference:(GTReference *)ref error:(NSError **)error { - // make sure the ref is up to date before we branch off it, otherwise we could branch off an older sha - ref = [ref reloadedReferenceWithError:error]; - if (ref == nil) return nil; - - 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]; -} - - (BOOL)isEmpty { return (BOOL) git_repository_is_empty(self.git_repository); } @@ -413,7 +404,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 { @@ -438,7 +429,24 @@ - (NSArray *)referenceNamesWithError:(NSError **)error { return referenceNames; } -- (NSURL *)fileURL { +- (BOOL)enumerateReferencesWithError:(NSError **)error usingBlock:(void (^)(GTReference *reference, NSError *error, BOOL *stop))block { + NSArray *references = [self referenceNamesWithError:error]; + if (!references) return NO; + + for (NSString *refName in references) { + NSError *refError; + BOOL stop = NO; + + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:refName inRepository:self error:&refError]; + + block(ref, refError, &stop); + + if (stop == YES) break; + } + return YES; +} + +- (NSURL *)workingDirectoryURL { const char *path = git_repository_workdir(self.git_repository); // bare repository, you may be looking for gitDirectoryURL if (path == NULL) return nil; @@ -453,6 +461,10 @@ - (NSURL *)gitDirectoryURL { return [NSURL fileURLWithPath:@(path) isDirectory:YES]; } +- (NSURL *)displayURL { + return (self.isBare == YES ? self.gitDirectoryURL : self.workingDirectoryURL); +} + - (BOOL)isBare { return (BOOL)git_repository_is_bare(self.git_repository); } @@ -535,8 +547,8 @@ - (GTCommit *)mergeBaseBetweenFirstOID:(GTOID *)firstOID secondOID:(GTOID *)seco if (error != NULL) *error = [NSError git_errorFor:errorCode description:@"Failed to find merge base between commits %@ and %@.", firstOID.SHA, secondOID.SHA]; return nil; } - - return [self lookupObjectByGitOid:&mergeBase objectType:GTObjectTypeCommit error:error]; + + return [GTCommit lookupWithOID:[GTOID oidWithGitOid:&mergeBase] inRepository:self error:error]; } - (GTObjectDatabase *)objectDatabaseWithError:(NSError **)error { @@ -639,38 +651,6 @@ - (GTSignature *)userSignatureForNow { return [[GTSignature alloc] initWithName:name email:email time:[NSDate date]]; } -#pragma mark Tagging - -- (BOOL)createLightweightTagNamed:(NSString *)tagName target:(GTObject *)target error:(NSError **)error { - NSParameterAssert(tagName != nil); - NSParameterAssert(target != nil); - - git_oid oid; - int gitError = git_tag_create_lightweight(&oid, self.git_repository, tagName.UTF8String, target.git_object, 0); - if (gitError != GIT_OK) { - if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Cannot create lightweight tag"]; - return NO; - } - - return YES; -} - -- (GTOID *)OIDByCreatingTagNamed:(NSString *)tagName target:(GTObject *)theTarget tagger:(GTSignature *)theTagger message:(NSString *)theMessage error:(NSError **)error { - git_oid oid; - int gitError = git_tag_create(&oid, self.git_repository, [tagName UTF8String], theTarget.git_object, theTagger.git_signature, [theMessage UTF8String], 0); - if (gitError != GIT_OK) { - if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to create tag in repository"]; - return nil; - } - - return [GTOID oidWithGitOid:&oid]; -} - -- (GTTag *)createTagNamed:(NSString *)tagName target:(GTObject *)theTarget tagger:(GTSignature *)theTagger message:(NSString *)theMessage error:(NSError **)error { - GTOID *oid = [self OIDByCreatingTagNamed:tagName target:theTarget tagger:theTagger message:theMessage error:error]; - return oid ? [self lookupObjectByOID:oid objectType:GTObjectTypeTag error:error] : nil; -} - #pragma mark Checkout // The type of block passed to -checkout:strategy:progressBlock:notifyBlock:notifyFlags:error: for progress reporting diff --git a/Classes/GTTag.h b/Classes/GTTag.h index 645327349..45fa67b02 100644 --- a/Classes/GTTag.h +++ b/Classes/GTTag.h @@ -31,6 +31,7 @@ @class GTSignature; @class GTRepository; +@class GTReference; @interface GTTag : GTObject {} @@ -49,14 +50,47 @@ // The type of the 'tagged' object. @property (nonatomic, readonly) GTObjectType targetType; +// Creates a tag in a repository. +// +// tagName - Name for the tag; this name is validated for consistency. +// If `force` is `NO`, it should also not conflict with an +// already existing tag name. +// targetObject - The object the tag should point to. It must belong to `repository`. +// message - The message attached to the tag. +// tagger - Signature of the tagger for this tag, and of the tagging time +// force - If YES, an underlying duplicate reference would be deleted. +// repository - The repository to create the tag in. +// error - This optional pointer will be set to a NSError instance if +// the tag's creation fails. +// +// Returns a newly created tag, or nil if the tag creation failed. ++ (instancetype)tagByCreatingTagNamed:(NSString *)tagName target:(GTObject *)targetObject message:(NSString *)message tagger:(GTSignature *)tagger force:(BOOL)force inRepository:(GTRepository *)repository error:(NSError **)error; + +// Creates a lightweight tag in a repository. +// +// tagName - Name for the tag; this name is validated for consistency. +// If `force` is `NO`, it should also not conflict with an +// already existing tag name. +// targetObject - The object the tag should point to. It must belong to `repository`. +// force - If YES, an underlying duplicate reference would be deleted. +// repository - The repository to create the tag in. +// error - This optional pointer will be set to a NSError instance if +// the tag's creation fails. +// +// Returns the reference underlying the new tag, or nil if the tag creation failed. ++ (GTReference *)tagByCreatingLightweightTagNamed:(NSString *)tagName target:(GTObject *)targetObject force:(BOOL)force inRepository:(GTRepository *)repository error:(NSError **)error; + // Recursively peel a tag until a non tag GTObject is found // -// errro - Will be filled with a NSError object on failure. +// error - Will be filled with a NSError object on failure. // May be NULL. // // Returns the found object or nil on error. - (id)objectByPeelingTagError:(NSError **)error; +// Delete the receiver. +- (BOOL)delete:(NSError **)error; + // The underlying `git_object` as a `git_tag` object. - (git_tag *)git_tag __attribute__((objc_returns_inner_pointer)); diff --git a/Classes/GTTag.m b/Classes/GTTag.m index e497c0085..cfbe5b834 100644 --- a/Classes/GTTag.m +++ b/Classes/GTTag.m @@ -34,14 +34,54 @@ #import "GTRepository.h" #import "NSString+Git.h" #import "GTOID.h" +#import "GTObject+Private.h" @implementation GTTag +#pragma mark - +#pragma mark Class methods + ++ (instancetype)tagByCreatingTagNamed:(NSString *)tagName target:(GTObject *)targetObject message:(NSString *)message tagger:(GTSignature *)tagger force:(BOOL)force inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(tagName != nil); + NSParameterAssert(targetObject != nil); + NSParameterAssert(message != nil); + NSParameterAssert(tagger != nil); + NSParameterAssert(repository != nil); + + git_oid git_oid; + int gitError = git_tag_create(&git_oid, repository.git_repository, tagName.UTF8String, targetObject.git_object, tagger.git_signature, message.UTF8String, (force == YES ? 1 : 0)); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Tag creation failed"]; + return nil; + } + + return [GTTag lookupWithGitOID:&git_oid inRepository:repository error:error]; +} + ++ (GTReference *)tagByCreatingLightweightTagNamed:(NSString *)tagName target:(GTObject *)targetObject force:(BOOL)force inRepository:(GTRepository *)repository error:(NSError **)error { + NSParameterAssert(tagName != nil); + NSParameterAssert(targetObject != nil); + NSParameterAssert(repository != nil); + + git_oid gitOid; + int gitError = git_tag_create_lightweight(&gitOid, repository.git_repository, tagName.UTF8String, targetObject.git_object, (force == YES ? 1 : 0)); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Lightweight tag creation failed"]; + return nil; + } + + return [GTReference referenceByLookingUpReferenceNamed:[NSString stringWithFormat:@"refs/tags/%@", tagName] inRepository:repository error:error]; +} + +#pragma mark - +#pragma mark NSObject + - (NSString *)description { return [NSString stringWithFormat:@"<%@: %p> name: %@, message: %@, targetType: %d", NSStringFromClass([self class]), self,self.name, self.message, self.targetType]; } -#pragma mark API +#pragma mark - +#pragma mark Properties - (NSString *)message { return @(git_tag_message(self.git_tag)); @@ -70,6 +110,9 @@ - (git_tag *)git_tag { return (git_tag *) self.git_object; } +#pragma mark - +#pragma mark API + - (id)objectByPeelingTagError:(NSError **)error { git_object *target = nil; int gitError = git_tag_peel(&target, self.git_tag); @@ -81,4 +124,13 @@ - (id)objectByPeelingTagError:(NSError **)error { return [[GTObject alloc] initWithObj:target inRepository:self.repository]; } +- (BOOL)delete:(NSError **)error { + int gitError = git_tag_delete(self.repository.git_repository, self.name.UTF8String); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Tag deletion failed"]; + return NO; + } + return YES; +} + @end diff --git a/ObjectiveGitFramework.xcodeproj/project.pbxproj b/ObjectiveGitFramework.xcodeproj/project.pbxproj index a42b5973a..aa2e152a8 100644 --- a/ObjectiveGitFramework.xcodeproj/project.pbxproj +++ b/ObjectiveGitFramework.xcodeproj/project.pbxproj @@ -139,12 +139,13 @@ 3E0A23E5159E0FDB00A6068F /* GTObjectDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 55C8054C13861F34004DCB0F /* GTObjectDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4D103ADD1819CFAA0029DB24 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D103ADC1819CFAA0029DB24 /* libiconv.dylib */; }; 4D123240178E009E0048F785 /* GTRepositoryCommittingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */; }; + 4D1C40D8182C006D00BE2960 /* GTBlobSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D1C40D7182C006D00BE2960 /* GTBlobSpec.m */; }; 4D26799F178DAF31002A2795 /* GTTreeEntry+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D26799D178DAF31002A2795 /* GTTreeEntry+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4D79C0EE17DF9F4D00997DE4 /* GTCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4D79C0EF17DF9F4D00997DE4 /* GTCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */; }; 4D79C0F717DFAA7100997DE4 /* GTCredential+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */; }; - 4DE864351794A37E00371A65 /* GTRepository+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTRepository+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 4DE864361794A37E00371A65 /* GTRepository+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTRepository+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4DE864351794A37E00371A65 /* GTObject+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTObject+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4DE864361794A37E00371A65 /* GTObject+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTObject+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 55C8054F13861FE7004DCB0F /* GTObjectDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C8054D13861F34004DCB0F /* GTObjectDatabase.m */; }; 55C8055013861FE7004DCB0F /* GTObjectDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C8054D13861F34004DCB0F /* GTObjectDatabase.m */; }; 55C8057A13875578004DCB0F /* NSString+Git.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C8057313874CDF004DCB0F /* NSString+Git.m */; }; @@ -199,12 +200,8 @@ 88EB7E4E14AEBA600046FEA4 /* GTConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 88EB7E4C14AEBA600046FEA4 /* GTConfiguration.m */; }; 88F05A9D16011F6A00B7AD1D /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88F05A9C16011F6A00B7AD1D /* SenTestingKit.framework */; }; 88F05A9E16011F6E00B7AD1D /* ObjectiveGit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* ObjectiveGit.framework */; }; - 88F05AB316011FFD00B7AD1D /* GTBlobTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AA216011FFD00B7AD1D /* GTBlobTest.m */; }; - 88F05AB416011FFD00B7AD1D /* GTBranchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AA316011FFD00B7AD1D /* GTBranchTest.m */; }; - 88F05AB516011FFD00B7AD1D /* GTCommitTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AA416011FFD00B7AD1D /* GTCommitTest.m */; }; + 88F05AB516011FFD00B7AD1D /* GTCommitSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AA416011FFD00B7AD1D /* GTCommitSpec.m */; }; 88F05AB916011FFD00B7AD1D /* GTObjectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AA816011FFD00B7AD1D /* GTObjectTest.m */; }; - 88F05ABA16011FFD00B7AD1D /* GTReferenceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AA916011FFD00B7AD1D /* GTReferenceTest.m */; }; - 88F05ABC16011FFD00B7AD1D /* GTRepositoryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AAB16011FFD00B7AD1D /* GTRepositoryTest.m */; }; 88F05AC41601204200B7AD1D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 88F05A7816011E5400B7AD1D /* InfoPlist.strings */; }; 88F05AC61601209A00B7AD1D /* ObjectiveGit.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AC51601209A00B7AD1D /* ObjectiveGit.m */; }; 88F05AC716012CE500B7AD1D /* NSString+Git.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C8057313874CDF004DCB0F /* NSString+Git.m */; }; @@ -302,7 +299,7 @@ F6CBB47617FFD50500404926 /* NSDate+GTTimeAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30B1E7EC1703522100D0814D /* NSDate+GTTimeAdditions.h */; }; F6CBB47717FFD50500404926 /* GTSubmodule.h in Headers */ = {isa = PBXBuildFile; fileRef = D09C2E341755F16200065E36 /* GTSubmodule.h */; settings = {ATTRIBUTES = (Public, ); }; }; F6CBB47817FFD50500404926 /* GTRepository+Stashing.h in Headers */ = {isa = PBXBuildFile; fileRef = D015F7C817F695E800AD5E1F /* GTRepository+Stashing.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F6CBB47917FFD50500404926 /* GTRepository+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTRepository+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + F6CBB47917FFD50500404926 /* GTObject+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTObject+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; F6CBB47B17FFD50500404926 /* ObjectiveGit.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F05AC51601209A00B7AD1D /* ObjectiveGit.m */; }; F6CBB47C17FFD50500404926 /* NSData+Git.m in Sources */ = {isa = PBXBuildFile; fileRef = BD6C2267131459E700992935 /* NSData+Git.m */; }; F6CBB47D17FFD50500404926 /* GTRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 883CD6AA1600EBC600F57354 /* GTRemote.m */; }; @@ -498,7 +495,7 @@ 306123AB17EA5261006591D4 /* metamacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = metamacros.h; sourceTree = ""; }; 307623AA17C6C8BD00E2CDF1 /* NSArray+StringArraySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+StringArraySpec.m"; sourceTree = ""; }; 30865A90167F503400B1AB6E /* GTDiffSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTDiffSpec.m; sourceTree = ""; }; - 30A269AC17B4878C000FE64E /* GTRepository+StatusSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+StatusSpec.m"; sourceTree = ""; }; + 30A269AC17B4878C000FE64E /* GTRepository+StatusSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "GTRepository+StatusSpec.m"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 30A3D6521667F11C00C49A39 /* GTDiff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTDiff.h; sourceTree = ""; }; 30A3D6531667F11C00C49A39 /* GTDiff.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTDiff.m; sourceTree = ""; }; 30B1E7EC1703522100D0814D /* NSDate+GTTimeAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+GTTimeAdditions.h"; sourceTree = ""; }; @@ -506,21 +503,22 @@ 30B1E7FF1703871900D0814D /* GTTimeAdditionsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTTimeAdditionsSpec.m; sourceTree = ""; }; 30DCBA5A17B45213009B0EBD /* GTStatusDelta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTStatusDelta.h; sourceTree = ""; }; 30DCBA5B17B45213009B0EBD /* GTStatusDelta.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTStatusDelta.m; sourceTree = ""; }; - 30DCBA6117B45A78009B0EBD /* GTRepository+Status.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Status.h"; sourceTree = ""; }; - 30DCBA6217B45A78009B0EBD /* GTRepository+Status.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+Status.m"; sourceTree = ""; }; + 30DCBA6117B45A78009B0EBD /* GTRepository+Status.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "GTRepository+Status.h"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 30DCBA6217B45A78009B0EBD /* GTRepository+Status.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "GTRepository+Status.m"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 30DCBA6F17B4791A009B0EBD /* NSArray+StringArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+StringArray.h"; sourceTree = ""; }; 30DCBA7017B4791A009B0EBD /* NSArray+StringArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+StringArray.m"; sourceTree = ""; }; 30FDC07D16835A8100654BF0 /* GTDiffLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTDiffLine.h; sourceTree = ""; }; 30FDC07E16835A8100654BF0 /* GTDiffLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTDiffLine.m; sourceTree = ""; }; 32DBCF5E0370ADEE00C91783 /* ObjectiveGitFramework_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveGitFramework_Prefix.pch; sourceTree = ""; }; 4D103ADC1819CFAA0029DB24 /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; }; - 4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRepositoryCommittingSpec.m; sourceTree = ""; }; + 4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTRepositoryCommittingSpec.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4D1C40D7182C006D00BE2960 /* GTBlobSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTBlobSpec.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 4D26799D178DAF31002A2795 /* GTTreeEntry+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTTreeEntry+Private.h"; sourceTree = ""; }; 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCredential.h; sourceTree = ""; }; 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCredential.m; sourceTree = ""; }; 4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTCredential+Private.h"; sourceTree = ""; }; 4D8DADBE181A7D9F001B1202 /* libgit2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = libgit2; path = External/libgit2; sourceTree = ""; }; - 4DE864341794A37E00371A65 /* GTRepository+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Private.h"; sourceTree = ""; }; + 4DE864341794A37E00371A65 /* GTObject+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTObject+Private.h"; sourceTree = ""; }; 55C8054C13861F34004DCB0F /* GTObjectDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTObjectDatabase.h; sourceTree = ""; }; 55C8054D13861F34004DCB0F /* GTObjectDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTObjectDatabase.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 55C8057213874CDF004DCB0F /* NSString+Git.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Git.h"; sourceTree = ""; }; @@ -566,12 +564,8 @@ 88F05A7916011E5400B7AD1D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 88F05A7E16011E5400B7AD1D /* ObjectiveGitTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ObjectiveGitTests-Prefix.pch"; sourceTree = ""; }; 88F05A9C16011F6A00B7AD1D /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; - 88F05AA216011FFD00B7AD1D /* GTBlobTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTBlobTest.m; sourceTree = ""; }; - 88F05AA316011FFD00B7AD1D /* GTBranchTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTBranchTest.m; sourceTree = ""; }; - 88F05AA416011FFD00B7AD1D /* GTCommitTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCommitTest.m; sourceTree = ""; }; + 88F05AA416011FFD00B7AD1D /* GTCommitSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCommitSpec.m; sourceTree = ""; }; 88F05AA816011FFD00B7AD1D /* GTObjectTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTObjectTest.m; sourceTree = ""; }; - 88F05AA916011FFD00B7AD1D /* GTReferenceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTReferenceTest.m; sourceTree = ""; }; - 88F05AAB16011FFD00B7AD1D /* GTRepositoryTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRepositoryTest.m; sourceTree = ""; }; 88F05AAF16011FFD00B7AD1D /* ObjectiveGitTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "ObjectiveGitTests-Info.plist"; sourceTree = ""; }; 88F05AC51601209A00B7AD1D /* ObjectiveGit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ObjectiveGit.m; path = Classes/ObjectiveGit.m; sourceTree = ""; }; 88F05AC91601335C00B7AD1D /* Specta.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Specta.xcodeproj; path = specta/Specta.xcodeproj; sourceTree = ""; }; @@ -609,17 +603,17 @@ BDD9C3FF133BA604003708E7 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; BDE4C060130EFE2C00851650 /* NSError+Git.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+Git.h"; sourceTree = ""; }; BDE4C061130EFE2C00851650 /* NSError+Git.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+Git.m"; sourceTree = ""; }; - BDE4C062130EFE2C00851650 /* GTRepository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTRepository.h; sourceTree = ""; }; + BDE4C062130EFE2C00851650 /* GTRepository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = GTRepository.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; BDE4C063130EFE2C00851650 /* GTRepository.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTRepository.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - BDFAF9C1131C1845000508BC /* GTIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTIndex.h; sourceTree = ""; }; - BDFAF9C2131C1845000508BC /* GTIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTIndex.m; sourceTree = ""; }; + BDFAF9C1131C1845000508BC /* GTIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = GTIndex.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + BDFAF9C2131C1845000508BC /* GTIndex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTIndex.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; BDFAF9C7131C1868000508BC /* GTIndexEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTIndexEntry.h; sourceTree = ""; }; BDFAF9C8131C1868000508BC /* GTIndexEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTIndexEntry.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; D00F6815175D373C004DB9D6 /* GTReferenceSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTReferenceSpec.m; sourceTree = ""; }; D015F7C817F695E800AD5E1F /* GTRepository+Stashing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Stashing.h"; sourceTree = ""; }; D015F7C917F695E800AD5E1F /* GTRepository+Stashing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+Stashing.m"; sourceTree = ""; }; - D015F7D417F6965400AD5E1F /* GTRepositoryStashingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRepositoryStashingSpec.m; sourceTree = ""; }; - D03B7C401756AB370034A610 /* GTSubmoduleSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTSubmoduleSpec.m; sourceTree = ""; }; + D015F7D417F6965400AD5E1F /* GTRepositoryStashingSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTRepositoryStashingSpec.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + D03B7C401756AB370034A610 /* GTSubmoduleSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTSubmoduleSpec.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; D040AF6F177B9779001AD9EB /* GTOIDSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTOIDSpec.m; sourceTree = ""; }; D040AF77177B9A9E001AD9EB /* GTSignatureSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTSignatureSpec.m; sourceTree = ""; }; D06D9E001755D10000558C17 /* GTEnumeratorSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTEnumeratorSpec.m; sourceTree = ""; }; @@ -633,7 +627,7 @@ D0A463DB17E57C45000F5021 /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = ""; }; D0A463DD17E57C45000F5021 /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = ""; }; D0A463DE17E57C45000F5021 /* StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticLibrary.xcconfig; sourceTree = ""; }; - D0AC906B172F941F00347DC4 /* GTRepositorySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRepositorySpec.m; sourceTree = ""; }; + D0AC906B172F941F00347DC4 /* GTRepositorySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTRepositorySpec.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; D0D81863174421EB00995A2E /* iOS-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Application.xcconfig"; sourceTree = ""; }; D0D81864174421EB00995A2E /* iOS-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Base.xcconfig"; sourceTree = ""; }; D0D81865174421EB00995A2E /* iOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-StaticLibrary.xcconfig"; sourceTree = ""; }; @@ -821,22 +815,19 @@ isa = PBXGroup; children = ( 88F05A7616011E5400B7AD1D /* Supporting Files */, - 88F05AA216011FFD00B7AD1D /* GTBlobTest.m */, + 4D1C40D7182C006D00BE2960 /* GTBlobSpec.m */, 88A994B916FCE7D400402C7B /* GTBranchSpec.m */, - 88F05AA316011FFD00B7AD1D /* GTBranchTest.m */, - 88F05AA416011FFD00B7AD1D /* GTCommitTest.m */, + 88F05AA416011FFD00B7AD1D /* GTCommitSpec.m */, 88C0BC5817038CF3009E99AA /* GTConfigurationSpec.m */, 30865A90167F503400B1AB6E /* GTDiffSpec.m */, D06D9E001755D10000558C17 /* GTEnumeratorSpec.m */, 8832811E173D8816006D7DCF /* GTIndexSpec.m */, 88F05AA816011FFD00B7AD1D /* GTObjectTest.m */, D00F6815175D373C004DB9D6 /* GTReferenceSpec.m */, - 88F05AA916011FFD00B7AD1D /* GTReferenceTest.m */, 88215482171499BE00D76B76 /* GTReflogSpec.m */, D0AC906B172F941F00347DC4 /* GTRepositorySpec.m */, F6ED8DA0180E713200A32D40 /* GTRemoteSpec.m */, 4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */, - 88F05AAB16011FFD00B7AD1D /* GTRepositoryTest.m */, D03B7C401756AB370034A610 /* GTSubmoduleSpec.m */, 2089E43B17D9A58000F451DA /* GTTagSpec.m */, 30B1E7FF1703871900D0814D /* GTTimeAdditionsSpec.m */, @@ -897,7 +888,7 @@ children = ( BDE4C05F130EFE2C00851650 /* Categories */, BDE4C062130EFE2C00851650 /* GTRepository.h */, - 4DE864341794A37E00371A65 /* GTRepository+Private.h */, + 4DE864341794A37E00371A65 /* GTObject+Private.h */, BDE4C063130EFE2C00851650 /* GTRepository.m */, 30DCBA6117B45A78009B0EBD /* GTRepository+Status.h */, 30DCBA6217B45A78009B0EBD /* GTRepository+Status.m */, @@ -1099,7 +1090,7 @@ 30B1E7EF1703522100D0814D /* NSDate+GTTimeAdditions.h in Headers */, D09C2E371755F16200065E36 /* GTSubmodule.h in Headers */, D015F7CB17F695E800AD5E1F /* GTRepository+Stashing.h in Headers */, - 4DE864361794A37E00371A65 /* GTRepository+Private.h in Headers */, + 4DE864361794A37E00371A65 /* GTObject+Private.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1148,7 +1139,7 @@ D09C2E361755F16200065E36 /* GTSubmodule.h in Headers */, 4D26799F178DAF31002A2795 /* GTTreeEntry+Private.h in Headers */, 306123B117EA5261006591D4 /* metamacros.h in Headers */, - 4DE864351794A37E00371A65 /* GTRepository+Private.h in Headers */, + 4DE864351794A37E00371A65 /* GTObject+Private.h in Headers */, 4D79C0EE17DF9F4D00997DE4 /* GTCredential.h in Headers */, 4D79C0F717DFAA7100997DE4 /* GTCredential+Private.h in Headers */, ); @@ -1192,7 +1183,7 @@ F6CBB47617FFD50500404926 /* NSDate+GTTimeAdditions.h in Headers */, F6CBB47717FFD50500404926 /* GTSubmodule.h in Headers */, F6CBB47817FFD50500404926 /* GTRepository+Stashing.h in Headers */, - F6CBB47917FFD50500404926 /* GTRepository+Private.h in Headers */, + F6CBB47917FFD50500404926 /* GTObject+Private.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1523,12 +1514,9 @@ files = ( 88F05AC816012CEE00B7AD1D /* NSData+Git.m in Sources */, 88F05AC716012CE500B7AD1D /* NSString+Git.m in Sources */, - 88F05AB316011FFD00B7AD1D /* GTBlobTest.m in Sources */, - 88F05AB416011FFD00B7AD1D /* GTBranchTest.m in Sources */, - 88F05AB516011FFD00B7AD1D /* GTCommitTest.m in Sources */, + 4D1C40D8182C006D00BE2960 /* GTBlobSpec.m in Sources */, + 88F05AB516011FFD00B7AD1D /* GTCommitSpec.m in Sources */, 88F05AB916011FFD00B7AD1D /* GTObjectTest.m in Sources */, - 88F05ABA16011FFD00B7AD1D /* GTReferenceTest.m in Sources */, - 88F05ABC16011FFD00B7AD1D /* GTRepositoryTest.m in Sources */, 30865A91167F503400B1AB6E /* GTDiffSpec.m in Sources */, 88A994BA16FCE7D400402C7B /* GTBranchSpec.m in Sources */, 2089E43C17D9A58000F451DA /* GTTagSpec.m in Sources */, diff --git a/ObjectiveGitTests/GTBlobSpec.m b/ObjectiveGitTests/GTBlobSpec.m new file mode 100644 index 000000000..69bad1f98 --- /dev/null +++ b/ObjectiveGitTests/GTBlobSpec.m @@ -0,0 +1,90 @@ +// +// GTBlobSpec.m +// ObjectiveGitFramework +// +// Created by Etienne Samson on 2013-11-07. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "GTBlob.h" + +SpecBegin(GTBlob) + +__block GTRepository *repository; +__block NSString *blobSHA; +__block GTBlob *blob; + +describe(@"blob properties can be accessed", ^{ + beforeEach(^{ + repository = self.bareFixtureRepository; + blobSHA = @"fa49b077972391ad58037050f2a75f74e3671e92"; + blob = [GTBlob lookupWithSHA:blobSHA inRepository:repository error:NULL]; + expect(blob).notTo.beNil(); + }); + + it(@"has a size", ^{ + expect(blob.size).to.equal(9); + }); + + it(@"has content", ^{ + expect(blob.content).to.equal(@"new file\n"); + }); + + it(@"has type", ^{ + expect(blob.type).to.equal(@"blob"); + }); + + it(@"has a SHA", ^{ + expect(blob.SHA).to.equal(blobSHA); + }); +}); + +describe(@"blobs can be created", ^{ + beforeEach(^{ + repository = self.testAppFixtureRepository; + }); + + describe(@"+blobWithString:inRepository:error", ^{ + it(@"works with valid parameters", ^{ + NSError *error = nil; + blob = [GTBlob blobWithString:@"a new blob content" inRepository:repository error:&error]; + expect(error).to.beNil(); + expect(blob).notTo.beNil(); + expect(blob.SHA).notTo.beNil(); + }); + }); + + describe(@"+blobWithData:inRepository:error", ^{ + it(@"works with valid parameters", ^{ + char bytes[] = "100644 example_helper.rb\00\xD3\xD5\xED\x9D A4_\x00 40000 examples"; + NSData *content = [NSData dataWithBytes:bytes length:sizeof(bytes)]; + + NSError *error = nil; + blob = [GTBlob blobWithData:content inRepository:repository error:&error]; + expect(error).to.beNil(); + expect(blob).notTo.beNil(); + expect(blob.SHA).notTo.beNil(); + }); + }); + + describe(@"+blobWithFile:inRepository:error", ^{ + it(@"works with valid parameters", ^{ + NSString *fileContent = @"Test contents\n"; + NSString *fileName = @"myfile.txt"; + NSURL *fileURL = [repository.workingDirectoryURL URLByAppendingPathComponent:fileName]; + + NSError *error = nil; + BOOL success = [fileContent writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:&error]; + expect(success).to.beTruthy(); + expect(error).to.beNil(); + + blob = [GTBlob blobWithFile:fileURL inRepository:repository error:&error]; + expect(error).to.beNil(); + expect(blob).notTo.beNil(); + expect(blob.SHA).notTo.beNil(); + expect(blob.content).to.equal(fileContent); + }); + }); +}); + +SpecEnd diff --git a/ObjectiveGitTests/GTBlobTest.m b/ObjectiveGitTests/GTBlobTest.m deleted file mode 100644 index 69de7b34e..000000000 --- a/ObjectiveGitTests/GTBlobTest.m +++ /dev/null @@ -1,118 +0,0 @@ -// -// GTBlobTest.m -// ObjectiveGitFramework -// -// Created by Timothy Clem on 2/25/11. -// -// The MIT License -// -// Copyright (c) 2011 Tim Clem -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@interface GTBlobTest : GTTestCase { - - GTRepository *repo; - NSString *blobSHA; -} -@end - -@implementation GTBlobTest - -- (void)setUp { - repo = self.bareFixtureRepository; - blobSHA = @"fa49b077972391ad58037050f2a75f74e3671e92"; -} - -- (void)testCanReadBlobData { - NSError *error = nil; - GTBlob *blob = [repo lookupObjectBySHA:blobSHA error:&error]; - STAssertEquals(9, (int)blob.size, nil); - STAssertEqualObjects(@"new file\n", blob.content, nil); - STAssertEqualObjects(@"blob", blob.type, nil); - STAssertEqualObjects(blobSHA, blob.SHA, nil); -} - -// todo -/* -- (void)testCanRewriteBlobData { - - NSError *error = nil; - GTBlob *blob = (GTBlob *)[repo lookupBySha:sha error:&error]; - blob.content = @"my new content"; - STAssertEqualObjects(sha, blob.sha, nil); - - NSString *newSha = [blob writeAndReturnError:&error]; - - STAssertNil(error, [error localizedDescription]); - STAssertEqualObjects(@"2dd916ea1ff086d61fbc1c286079305ffad4e92e", blob.sha, nil); - STAssertEqualObjects(@"2dd916ea1ff086d61fbc1c286079305ffad4e92e", newSha, nil); - rm_loose(blob.sha); -} -*/ - -- (void)testCanWriteNewBlobData { - - NSError *error = nil; - GTBlob *blob = [GTBlob blobWithString:@"a new blob content" inRepository:repo error:&error]; - NSString *newSHA = [blob SHA]; - STAssertNotNil(newSHA, [error localizedDescription]); -} - -- (void)testCanWriteNewBlobData2 { - - NSError *error = nil; - GTBlob *blob = [GTBlob blobWithString:@"a new blob content" inRepository:repo error:&error]; - STAssertNotNil(blob, [error localizedDescription]); -} - -//- (void)testCanGetCompleteContentWithNulls { -// -// NSError *error = nil; -// char bytes[] = "100644 example_helper.rb\00\xD3\xD5\xED\x9D A4_\x00 40000 examples"; -// NSData *content = [NSData dataWithBytes:bytes length:sizeof(bytes)]; -// -// GTBlob *blob = [GTBlob blobWithData:content inRepository:repo error:&error]; -// NSString *newSha = [blob sha]; -// STAssertNotNil(newSha, [error localizedDescription]); -// -// rm_loose(self.class, newSha); -// -// //todo -// /*GTRawObject *obj = [GTRawObject rawObjectWithType:GTObjectTypeBlob data:content]; -// NSString *newSha = [repo write:obj error:&error]; -// -// STAssertNil(error, [error localizedDescription]); -// STAssertNotNil(newSha, nil); -// GTBlob *blob = (GTBlob *)[repo lookupBySha:newSha error:&error]; -// GTRawObject *newObj = [blob readRawAndReturnError:&error]; -// STAssertNil(error, [error localizedDescription]); -// NSLog(@"original content = %@", [obj data]); -// NSLog(@"lookup content = %@", [newObj data]); -// STAssertEqualObjects(newObj.data, obj.data, nil); -// rm_loose(newSha);*/ -//} - -// todo -//- (void)testCanCreateBlobFromFile { -// -//} - -@end diff --git a/ObjectiveGitTests/GTBranchSpec.m b/ObjectiveGitTests/GTBranchSpec.m index 7b1e30e0f..a64370152 100644 --- a/ObjectiveGitTests/GTBranchSpec.m +++ b/ObjectiveGitTests/GTBranchSpec.m @@ -23,40 +23,155 @@ 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(); }); -describe(@"shortName", ^{ - it(@"should use just the branch name for a local branch", ^{ - expect(masterBranch.shortName).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(@"should not include the remote name for a tracking branch", ^{ - expect(trackingBranch.shortName).to.equal(@"master"); + 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); + }); }); -}); -describe(@"remoteName", ^{ - it(@"should return nil for a local branch", ^{ - expect(masterBranch.remoteName).to.beNil(); + 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 return the remote name for a tracking branch", ^{ - expect(trackingBranch.remoteName).to.equal(@"origin"); + 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(@"branchType", ^{ - it(@"should be GTBranchTypeLocal for a local branch", ^{ - expect(masterBranch.branchType).to.equal(GTBranchTypeLocal); +describe(@"basic properties", ^{ + describe(@"local branches", ^{ + it(@"should be GTBranchTypeLocal for a local branch", ^{ + expect(masterBranch.branchType).to.equal(GTBranchTypeLocal); + }); + + 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"); + }); + + 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"); + }); }); }); @@ -127,4 +242,77 @@ }); }); +describe(@"-numberOfCommitsWithError:", ^{ + it(@"should return the count of commits in the branch", ^{ + NSError *error = nil; + NSUInteger commitCount = [masterBranch numberOfCommitsWithError:&error]; + expect(commitCount).to.equal(164); + expect(error).to.beNil(); + }); +}); + +describe(@"-trackingBranchWithError:success:", ^{ + it(@"should return the tracking branch for a local branch that tracks a remote branch", ^{ + NSError *error = nil; + GTBranch *masterBranch = [GTBranch branchByLookingUpBranchNamed:@"master" inRepository:repository error:&error]; + expect(masterBranch).notTo.beNil(); + expect(error).to.beNil(); + + GTBranch *trackingBranch = [masterBranch trackingBranchWithError:&error]; + expect(trackingBranch).notTo.beNil(); + expect(error).to.beNil(); + }); + + it(@"should return nil for a local branch that doesn't track a remote branch", ^{ + NSError *error = nil; + GTReference *otherRef = [GTReference referenceByCreatingReferenceNamed:@"refs/heads/yet-another-branch" fromReferenceTarget:@"6b0c1c8b8816416089c534e474f4c692a76ac14f" inRepository:repository error:&error]; + expect(otherRef).notTo.beNil(); + expect(error).to.beNil(); + + GTBranch *otherBranch = [GTBranch branchWithReference:otherRef]; + expect(otherBranch).notTo.beNil(); + + trackingBranch = [otherBranch trackingBranchWithError:&error]; + expect(trackingBranch).to.beNil(); + expect(error).to.beNil(); + }); + + it(@"should return itself for a remote branch", ^{ + NSError *error = nil; + GTReference *remoteRef = [GTReference referenceByLookingUpReferenceNamed:@"refs/remotes/origin/master" inRepository:repository error:&error]; + expect(remoteRef).notTo.beNil(); + expect(error).to.beNil(); + + GTBranch *remoteBranch = [GTBranch branchWithReference:remoteRef]; + expect(remoteBranch).notTo.beNil(); + + GTBranch *remoteTrackingBranch = [remoteBranch trackingBranchWithError:&error]; + expect(remoteTrackingBranch).to.equal(remoteBranch); + expect(error).to.beNil(); + }); +}); + +// TODO: Test branch renaming, branch upstream +//- (void)testCanRenameBranch { +// +// NSError *error = nil; +// GTRepository *repo = [GTRepository repoByOpeningRepositoryInDirectory:[NSURL URLWithString:TEST_REPO_PATH()] error:&error]; +// STAssertNil(error, [error localizedDescription]); +// +// NSArray *branches = [GTBranch listAllLocalBranchesInRepository:repo error:&error]; +// STAssertNotNil(branches, [error localizedDescription], nil); +// STAssertEquals(2, (int)branches.count, nil); +// +// NSString *newBranchName = [NSString stringWithFormat:@"%@%@", [GTBranch localNamePrefix], @"this_is_the_renamed_branch"]; +// GTBranch *firstBranch = [branches objectAtIndex:0]; +// NSString *originalBranchName = firstBranch.name; +// BOOL success = [firstBranch.reference setName:newBranchName error:&error]; +// STAssertTrue(success, [error localizedDescription]); +// STAssertEqualObjects(firstBranch.name, newBranchName, nil); +// +// success = [firstBranch.reference setName:originalBranchName error:&error]; +// STAssertTrue(success, [error localizedDescription]); +// STAssertEqualObjects(firstBranch.name, originalBranchName, nil); +//} + SpecEnd diff --git a/ObjectiveGitTests/GTBranchTest.m b/ObjectiveGitTests/GTBranchTest.m deleted file mode 100644 index 83252e65a..000000000 --- a/ObjectiveGitTests/GTBranchTest.m +++ /dev/null @@ -1,96 +0,0 @@ -// -// GTBranchTest.m -// ObjectiveGitFramework -// -// Created by Timothy Clem on 3/10/11. -// Copyright 2011 GitHub, Inc. All rights reserved. -// - -@interface GTBranchTest : GTTestCase { - - GTRepository *repo; -} -@end - -@implementation GTBranchTest - -- (void)setUp { - repo = self.bareFixtureRepository; -} - -- (void)testCanOpenHeadInRepo { - - NSError *error = nil; - GTBranch *current = [repo currentBranchWithError:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertNotNil(current, nil); -} - -- (void)testCanListLocalBranchesInRepo { - - NSError *error = nil; - NSArray *branches = [repo localBranchesWithError:&error]; - STAssertNotNil(branches, [error localizedDescription], nil); - STAssertEquals(2, (int)branches.count, nil); -} - -- (void)testCanListRemoteBranchesInRepo { - - NSError *error = nil; - NSArray *branches = [repo remoteBranchesWithError:&error]; - STAssertNotNil(branches, [error localizedDescription], nil); - STAssertEquals(0, (int)branches.count, nil); -} - -- (void)testCanCountCommitsInBranch { - - NSError *error = nil; - GTReference *head = [repo headReferenceWithError:&error]; - STAssertNotNil(head, [error localizedDescription]); - GTBranch *master = [GTBranch branchWithReference:head repository:repo]; - STAssertNotNil(master, [error localizedDescription]); - - NSUInteger n = [master numberOfCommitsWithError:&error]; - STAssertEquals((NSUInteger)3, n, nil); -} - -- (void)testRetainOfBranchCreatedWithRef { - - // Hard to test the autoreleasepool, so manually alloc/init instead. - // This allows us to release the object and test that the branch - // is retaining properly. - NSError *error = nil; - GTReference *head = [[GTReference alloc] initByLookingUpReferenceNamed:@"HEAD" inRepository:repo error:&error]; - STAssertNotNil(head, [error localizedDescription]); - GTBranch *current = [GTBranch branchWithReference:head repository:repo]; - STAssertNotNil(current, [error localizedDescription]); - - - STAssertNotNil(current.reference, nil); -} - -/* -- (void)testCanRenameBranch { - - NSError *error = nil; - GTRepository *repo = [GTRepository repoByOpeningRepositoryInDirectory:[NSURL URLWithString:TEST_REPO_PATH()] error:&error]; - STAssertNil(error, [error localizedDescription]); - - NSArray *branches = [GTBranch listAllLocalBranchesInRepository:repo error:&error]; - STAssertNotNil(branches, [error localizedDescription], nil); - STAssertEquals(2, (int)branches.count, nil); - - NSString *newBranchName = [NSString stringWithFormat:@"%@%@", [GTBranch localNamePrefix], @"this_is_the_renamed_branch"]; - GTBranch *firstBranch = [branches objectAtIndex:0]; - NSString *originalBranchName = firstBranch.name; - BOOL success = [firstBranch.reference setName:newBranchName error:&error]; - STAssertTrue(success, [error localizedDescription]); - STAssertEqualObjects(firstBranch.name, newBranchName, nil); - - success = [firstBranch.reference setName:originalBranchName error:&error]; - STAssertTrue(success, [error localizedDescription]); - STAssertEqualObjects(firstBranch.name, originalBranchName, nil); -} - */ - -@end diff --git a/ObjectiveGitTests/GTCommitSpec.m b/ObjectiveGitTests/GTCommitSpec.m new file mode 100644 index 000000000..a309a0d20 --- /dev/null +++ b/ObjectiveGitTests/GTCommitSpec.m @@ -0,0 +1,62 @@ +// +// GTCommitSpec.m +// ObjectiveGitFramework +// +// Created by Etienne Samson on 2013-11-07. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "GTCommit.h" + +SpecBegin(GTCommit) + +__block GTRepository *repository; + +beforeEach(^{ + repository = self.bareFixtureRepository; +}); + +it(@"can read commit data", ^{ + NSError *error = nil; + NSString *commitSHA = @"8496071c1b46c854b31185ea97743be6a8774479"; + GTCommit *commit = [GTCommit lookupWithSHA:commitSHA inRepository:repository error:&error]; + + expect(commit).notTo.beNil(); + expect(error).to.beNil(); + + expect(commit).to.beInstanceOf(GTCommit.class); + expect(commit.type).to.equal(@"commit"); + expect(commit.SHA).to.equal(commitSHA); + + expect(commit.message).to.equal(@"testing\n"); + expect(commit.messageSummary).to.equal(@"testing"); + expect(commit.messageDetails).to.equal(@""); + expect(commit.commitDate).to.equal([NSDate dateWithTimeIntervalSince1970:1273360386]); + + GTSignature *author = commit.author; + expect(author).notTo.beNil(); + expect(author.name).to.equal(@"Scott Chacon"); + expect(author.email).to.equal(@"schacon@gmail.com"); + expect(author.time).to.equal([NSDate dateWithTimeIntervalSince1970:1273360386]); + + GTSignature *committer = commit.committer; + expect(committer).notTo.beNil(); + expect(committer.name).to.equal(@"Scott Chacon"); + expect(committer.email).to.equal(@"schacon@gmail.com"); + expect(committer.time).to.equal([NSDate dateWithTimeIntervalSince1970:1273360386]); + + expect(commit.tree.SHA).to.equal(@"181037049a54a1eb5fab404658a3a250b44335d7"); + expect(commit.parents.count).to.equal(0); +}); + +it(@"can have multiple parents", ^{ + NSError *error = nil; + NSString *commitSHA = @"a4a7dce85cf63874e984719f4fdd239f5145052f"; + GTCommit *commit = [GTCommit lookupWithSHA:commitSHA inRepository:repository error:&error]; + expect(commit).notTo.beNil(); + expect(error).to.beNil(); + + expect(commit.parents.count).to.equal(2); +}); + +SpecEnd diff --git a/ObjectiveGitTests/GTCommitTest.m b/ObjectiveGitTests/GTCommitTest.m deleted file mode 100644 index e872ba4ac..000000000 --- a/ObjectiveGitTests/GTCommitTest.m +++ /dev/null @@ -1,89 +0,0 @@ -// -// GTCommitTest.m -// ObjectiveGitFramework -// -// Created by Timothy Clem on 2/22/11. -// -// The MIT License -// -// Copyright (c) 2011 Tim Clem -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@interface GTCommitTest : GTTestCase { - - GTRepository *repo; -} -@end - -@implementation GTCommitTest - - -- (void)setUp { - repo = self.bareFixtureRepository; - STAssertNotNil(repo, @"Could not create fixture repository."); -} - -- (void)testCanReadCommitData { - - NSString *commitSHA = @"8496071c1b46c854b31185ea97743be6a8774479"; - NSError *error = nil; - GTObject *obj = [repo lookupObjectBySHA:commitSHA error:&error]; - - STAssertNil(error, [error localizedDescription]); - STAssertNotNil(obj, nil); - STAssertTrue([obj isKindOfClass:[GTCommit class]], nil); - STAssertEqualObjects(obj.type, @"commit", nil); - STAssertEqualObjects(obj.SHA, commitSHA, nil); - - GTCommit *commit = (GTCommit *)obj; - STAssertEqualObjects(commit.message, @"testing\n", nil); - STAssertEqualObjects(commit.messageSummary, @"testing", nil); - STAssertEqualObjects(commit.messageDetails, @"", nil); - STAssertEquals((int)[commit.commitDate timeIntervalSince1970], 1273360386, nil); - - GTSignature *author = commit.author; - STAssertEqualObjects(author.name, @"Scott Chacon", nil); - STAssertEqualObjects(author.email, @"schacon@gmail.com", nil); - STAssertEquals((int)[author.time timeIntervalSince1970], 1273360386, nil); - - GTSignature *committer = commit.committer; - STAssertEqualObjects(committer.name, @"Scott Chacon", nil); - STAssertEqualObjects(committer.email, @"schacon@gmail.com", nil); - STAssertEquals((int)[committer.time timeIntervalSince1970], 1273360386, nil); - - STAssertEqualObjects(commit.tree.SHA, @"181037049a54a1eb5fab404658a3a250b44335d7", nil); - STAssertTrue([commit.parents count] == 0, nil); -} - -- (void)testCanHaveMultipleParents { - - NSString *commitSHA = @"a4a7dce85cf63874e984719f4fdd239f5145052f"; - NSError *error = nil; - GTObject *obj = [repo lookupObjectBySHA:commitSHA error:&error]; - - STAssertNil(error, [error localizedDescription]); - STAssertNotNil(obj, nil); - - GTCommit *commit = (GTCommit *)obj; - STAssertTrue([commit.parents count] == 2, nil); -} - -@end diff --git a/ObjectiveGitTests/GTDiffSpec.m b/ObjectiveGitTests/GTDiffSpec.m index 899a748f2..3da289fdd 100644 --- a/ObjectiveGitTests/GTDiffSpec.m +++ b/ObjectiveGitTests/GTDiffSpec.m @@ -18,10 +18,10 @@ repository = self.testAppFixtureRepository; expect(repository).toNot.beNil(); - firstCommit = (GTCommit *)[repository lookupObjectBySHA:@"8e0e65988d3007867a9f59ca8639ba975ef97e69" objectType:GTObjectTypeCommit error:NULL]; + firstCommit = [GTCommit lookupWithSHA:@"8e0e65988d3007867a9f59ca8639ba975ef97e69" inRepository:repository error:NULL]; expect(firstCommit).toNot.beNil(); - secondCommit = (GTCommit *)[repository lookupObjectBySHA:@"a5840674db1a58cac0b2e7d046b627837a16f217" objectType:GTObjectTypeCommit error:NULL]; + secondCommit = [GTCommit lookupWithSHA:@"a5840674db1a58cac0b2e7d046b627837a16f217" inRepository:repository error:NULL]; expect(secondCommit).toNot.beNil(); }); @@ -70,9 +70,9 @@ expect(repository).toNot.beNil(); setupDiffFromCommitSHAsAndOptions = [^(NSString *firstCommitSHA, NSString *secondCommitSHA, NSDictionary *options) { - firstCommit = (GTCommit *)[repository lookupObjectBySHA:firstCommitSHA objectType:GTObjectTypeCommit error:NULL]; + firstCommit = [GTCommit lookupWithSHA:firstCommitSHA inRepository:repository error:NULL]; expect(firstCommit).toNot.beNil(); - secondCommit = (GTCommit *)[repository lookupObjectBySHA:secondCommitSHA objectType:GTObjectTypeCommit error:NULL]; + secondCommit = [GTCommit lookupWithSHA:secondCommitSHA inRepository:repository error:NULL]; expect(secondCommit).toNot.beNil(); diff = [GTDiff diffOldTree:firstCommit.tree withNewTree:secondCommit.tree inRepository:repository options:options error:NULL]; diff --git a/ObjectiveGitTests/GTObjectTest.m b/ObjectiveGitTests/GTObjectTest.m index ef1014e9f..09b5c0c74 100644 --- a/ObjectiveGitTests/GTObjectTest.m +++ b/ObjectiveGitTests/GTObjectTest.m @@ -42,7 +42,7 @@ - (void)setUp { - (void)testCanLookupEmptyStringFails { NSError *error = nil; - GTObject *obj = [repo lookupObjectBySHA:@"" error:&error]; + GTObject *obj = [GTObject lookupWithSHA:@"" inRepository:repo error:&error]; STAssertNotNil(error, nil); STAssertNil(obj, nil); @@ -52,7 +52,7 @@ - (void)testCanLookupEmptyStringFails { - (void)testCanLookupBadObjectFails { NSError *error = nil; - GTObject *obj = [repo lookupObjectBySHA:@"a496071c1b46c854b31185ea97743be6a8774479" error:&error]; + GTObject *obj = [GTObject lookupWithSHA:@"a496071c1b46c854b31185ea97743be6a8774479" inRepository:repo error:&error]; STAssertNotNil(error, nil); STAssertNil(obj, nil); @@ -62,7 +62,7 @@ - (void)testCanLookupBadObjectFails { - (void)testCanLookupAnObject { NSError *error = nil; - GTObject *obj = [repo lookupObjectBySHA:@"8496071c1b46c854b31185ea97743be6a8774479" error:&error]; + GTObject *obj = [GTObject lookupWithSHA:@"8496071c1b46c854b31185ea97743be6a8774479" inRepository:repo error:&error]; STAssertNil(error, [error localizedDescription]); STAssertNotNil(obj, nil); @@ -73,8 +73,8 @@ - (void)testCanLookupAnObject { - (void)testTwoObjectsAreTheSame { NSError *error = nil; - GTObject *obj1 = [repo lookupObjectBySHA:@"8496071c1b46c854b31185ea97743be6a8774479" error:&error]; - GTObject *obj2 = [repo lookupObjectBySHA:@"8496071c1b46c854b31185ea97743be6a8774479" error:&error]; + GTObject *obj1 = [GTObject lookupWithSHA:@"8496071c1b46c854b31185ea97743be6a8774479" inRepository:repo error:&error]; + GTObject *obj2 = [GTObject lookupWithSHA:@"8496071c1b46c854b31185ea97743be6a8774479" inRepository:repo error:&error]; STAssertNotNil(obj1, nil); STAssertNotNil(obj2, nil); @@ -84,7 +84,7 @@ - (void)testTwoObjectsAreTheSame { - (void)testCanReadRawDataFromObject { NSError *error = nil; - GTObject *obj = [repo lookupObjectBySHA:@"8496071c1b46c854b31185ea97743be6a8774479" error:&error]; + GTObject *obj = [GTObject lookupWithSHA:@"8496071c1b46c854b31185ea97743be6a8774479" inRepository:repo error:&error]; STAssertNotNil(obj, nil); diff --git a/ObjectiveGitTests/GTReferenceSpec.m b/ObjectiveGitTests/GTReferenceSpec.m index 6c29d2ad5..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(); @@ -107,4 +111,72 @@ }); }); +__block GTRepository *bareRepository; + +void (^expectValidReference)(GTReference *ref, NSString *SHA, GTReferenceType type, NSString *name) = ^(GTReference *ref, NSString *SHA, GTReferenceType type, NSString *name) { + expect(ref).notTo.beNil(); + expect(ref.targetSHA).to.equal(SHA); + expect(ref.referenceType).to.equal(type); + expect(ref.name).to.equal(name); +}; + +beforeEach(^{ + bareRepository = self.bareFixtureRepository; +}); + +describe(@"+referenceByLookingUpReferenceNamed:inRepository:error:", ^{ + it(@"should return a valid reference to a branch", ^{ + NSError *error = nil; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:@"refs/heads/master" inRepository:bareRepository error:&error]; + expect(ref).notTo.beNil(); + expect(error).to.beNil(); + + expectValidReference(ref, @"36060c58702ed4c2a40832c51758d5344201d89a", GTReferenceTypeOid, @"refs/heads/master"); + }); + + it(@"should return a valid reference to a tag", ^{ + NSError *error = nil; + GTReference *ref = [GTReference referenceByLookingUpReferenceNamed:@"refs/tags/v0.9" inRepository:bareRepository error:&error]; + expect(ref).notTo.beNil(); + expect(error).to.beNil(); + + expectValidReference(ref, @"5b5b025afb0b4c913b4c338a42934a3863bf3644", GTReferenceTypeOid, @"refs/tags/v0.9"); + }); +}); + +describe(@"+referenceByCreatingReferenceNamed:fromReferenceTarget:inRepository:error:", ^{ + it(@"can create a reference from a symbolic reference", ^{ + NSError *error = nil; + GTReference *ref = [GTReference referenceByCreatingReferenceNamed:@"refs/heads/unit_test" fromReferenceTarget:@"refs/heads/master" inRepository:bareRepository error:&error]; + expect(error).to.beNil(); + expect(ref).notTo.beNil(); + + expectValidReference(ref, @"36060c58702ed4c2a40832c51758d5344201d89a", GTReferenceTypeSymbolic, @"refs/heads/unit_test"); + expect(ref.resolvedReference.name).to.equal(@"refs/heads/master"); + }); + + it(@"can create a reference from an SHA/OID", ^{ + NSError *error = nil; + GTReference *ref = [GTReference referenceByCreatingReferenceNamed:@"refs/heads/unit_test" fromReferenceTarget:@"36060c58702ed4c2a40832c51758d5344201d89a" inRepository:bareRepository error:&error]; + expect(error).to.beNil(); + expect(ref).notTo.beNil(); + + expectValidReference(ref, @"36060c58702ed4c2a40832c51758d5344201d89a", GTReferenceTypeOid, @"refs/heads/unit_test"); + }); +}); + +describe(@"-deleteWithError:", ^{ + it(@"can delete references", ^{ + NSError *error = nil; + GTReference *ref = [GTReference referenceByCreatingReferenceNamed:@"refs/heads/unit_test" fromReferenceTarget:@"36060c58702ed4c2a40832c51758d5344201d89a" inRepository:bareRepository error:&error]; + + expect(error).to.beNil(); + expect(ref).notTo.beNil(); + + BOOL success = [ref deleteWithError:&error]; + expect(success).to.beTruthy(); + expect(error).to.beNil(); + }); +}); + SpecEnd diff --git a/ObjectiveGitTests/GTReferenceTest.m b/ObjectiveGitTests/GTReferenceTest.m deleted file mode 100644 index 22b80d09e..000000000 --- a/ObjectiveGitTests/GTReferenceTest.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// GTReferenceTest.m -// ObjectiveGitFramework -// -// Created by Timothy Clem on 3/2/11. -// Copyright 2011 GitHub Inc. All rights reserved. -// - -@interface GTReferenceTest : GTTestCase { - NSArray *expectedRefs; -} -@end - -@implementation GTReferenceTest - -- (void)setUp { - - expectedRefs = [NSArray arrayWithObjects:@"refs/heads/packed", @"refs/heads/master", @"refs/tags/v0.9", @"refs/tags/v1.0", nil]; -} - -- (void)testCanOpenRef { - GTRepository *repo = self.bareFixtureRepository; - STAssertNotNil(repo, @"Could not create reposiotry"); - NSError *error = nil; - GTReference *ref = [GTReference referenceByLookingUpReferencedNamed:@"refs/heads/master" inRepository:repo error:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertNotNil(ref, nil); - - STAssertEqualObjects(@"36060c58702ed4c2a40832c51758d5344201d89a", ref.targetSHA, nil); - STAssertEquals(GTReferenceTypeOid, ref.referenceType, nil); - STAssertEqualObjects(@"refs/heads/master", ref.name, nil); -} - -- (void)testCanOpenTagRef { - GTRepository *repo = self.bareFixtureRepository; - NSError *error = nil; - GTReference *ref = [GTReference referenceByLookingUpReferencedNamed:@"refs/tags/v0.9" inRepository:repo error:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertNotNil(ref, nil); - - STAssertEqualObjects(@"5b5b025afb0b4c913b4c338a42934a3863bf3644", ref.targetSHA, nil); - STAssertEquals(GTReferenceTypeOid, ref.referenceType, nil); - STAssertEqualObjects(@"refs/tags/v0.9", ref.name, nil); -} - -- (void)testCanCreateRefFromSymbolicRef { - GTRepository *repo = self.bareFixtureRepository; - NSError *error = nil; - GTReference *ref = [GTReference referenceByCreatingReferenceNamed:@"refs/heads/unit_test" fromReferenceTarget:@"refs/heads/master" inRepository:repo error:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertNotNil(ref, nil); - - STAssertEqualObjects(@"refs/heads/master", ref.resolvedReference.name, nil); - STAssertEquals(GTReferenceTypeSymbolic, ref.referenceType, nil); - STAssertEqualObjects(@"refs/heads/unit_test", ref.name, nil); - - BOOL success = [ref deleteWithError:&error]; - STAssertTrue(success, [error localizedDescription]); -} - -- (void)testCanCreateRefFromSha { - - GTRepository *repo = self.bareFixtureRepository; - NSError *error = nil; - GTReference *ref = [GTReference referenceByCreatingReferenceNamed:@"refs/heads/unit_test" fromReferenceTarget:@"36060c58702ed4c2a40832c51758d5344201d89a" inRepository:repo error:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertNotNil(ref, nil); - - STAssertEqualObjects(@"36060c58702ed4c2a40832c51758d5344201d89a", ref.targetSHA, nil); - STAssertEquals(GTReferenceTypeOid, ref.referenceType, nil); - STAssertEqualObjects(@"refs/heads/unit_test", ref.name, nil); - - BOOL success = [ref deleteWithError:&error]; - STAssertTrue(success, [error localizedDescription]); -} - -- (void)testCanListAllReferences { - GTRepository *repo = self.bareFixtureRepository; - NSError *error = nil; - NSArray *refs = [repo referenceNamesWithError:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertEquals(4, (int)refs.count, nil); - - for (NSUInteger i = 0; i < refs.count; i++) { - NSLog(@"%@", [refs objectAtIndex:i]); - STAssertTrue([expectedRefs containsObject:[refs objectAtIndex:i]], nil); - } -} - -@end diff --git a/ObjectiveGitTests/GTRepository+StatusSpec.m b/ObjectiveGitTests/GTRepository+StatusSpec.m index 5398595ee..e8e5c01d7 100644 --- a/ObjectiveGitTests/GTRepository+StatusSpec.m +++ b/ObjectiveGitTests/GTRepository+StatusSpec.m @@ -17,7 +17,7 @@ beforeEach(^{ repository = self.testAppFixtureRepository; - targetFileURL = [repository.fileURL URLByAppendingPathComponent:@"main.m"]; + targetFileURL = [repository.workingDirectoryURL URLByAppendingPathComponent:@"main.m"]; expect(repository).toNot.beNil(); }); @@ -68,7 +68,7 @@ }); it(@"should recognize copied files", ^{ - NSURL *copyLocation = [repository.fileURL URLByAppendingPathComponent:@"main2.m"]; + NSURL *copyLocation = [repository.workingDirectoryURL URLByAppendingPathComponent:@"main2.m"]; expect([NSFileManager.defaultManager copyItemAtURL:targetFileURL toURL:copyLocation error:&err]).to.beTruthy(); expect(err).to.beNil(); updateIndexForSubpathAndExpectStatus(copyLocation.lastPathComponent, GTStatusDeltaStatusCopied); @@ -81,7 +81,7 @@ }); it(@"should recognize renamed files", ^{ - NSURL *moveLocation = [repository.fileURL URLByAppendingPathComponent:@"main-moved.m"]; + NSURL *moveLocation = [repository.workingDirectoryURL URLByAppendingPathComponent:@"main-moved.m"]; expect([NSFileManager.defaultManager moveItemAtURL:targetFileURL toURL:moveLocation error:&err]).to.beTruthy(); expect(err).to.beNil(); expectSubpathToHaveWorkDirStatus(moveLocation.lastPathComponent, GTStatusDeltaStatusRenamed); diff --git a/ObjectiveGitTests/GTRepositorySpec.m b/ObjectiveGitTests/GTRepositorySpec.m index f430d147f..eb344be7a 100644 --- a/ObjectiveGitTests/GTRepositorySpec.m +++ b/ObjectiveGitTests/GTRepositorySpec.m @@ -18,29 +18,151 @@ expect(repository).notTo.beNil(); }); -describe(@"-initializeEmptyRepositoryAtFileURL:bare:error:", ^{ - __block GTRepository * (^createRepository)(BOOL bare); +describe(@"+initializeEmptyRepositoryAtFileURL:bare:error:", ^{ + it(@"should initialize a repository with a working directory by default", ^{ + NSURL *newRepoURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"init-repo"]; + + GTRepository *repository = [GTRepository initializeEmptyRepositoryAtFileURL:newRepoURL bare:NO error:NULL]; + expect(repository).notTo.beNil(); + expect(repository.gitDirectoryURL).notTo.beNil(); + expect(repository.bare).to.beFalsy(); + }); + + it(@"should initialize a bare repository", ^{ + NSURL *newRepoURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"init-repo.git"]; + + GTRepository *repository = [GTRepository initializeEmptyRepositoryAtFileURL:newRepoURL bare:YES error:NULL]; + expect(repository).notTo.beNil(); + expect(repository.gitDirectoryURL).notTo.beNil(); + return repository; + expect(repository.bare).to.beTruthy(); + }); +}); + +describe(@"+repositoryWithURL:error:", ^{ + it(@"should fail to initialize non-existent repos", ^{ + NSError *error = nil; + GTRepository *badRepo = [GTRepository repositoryWithURL:[NSURL fileURLWithPath:@"fake/1235"] error:&error]; + expect(badRepo).to.beNil(); + expect(error).notTo.beNil(); + expect(error.domain).to.equal(GTGitErrorDomain); + expect(error.code).to.equal(GIT_ENOTFOUND); + }); +}); + +describe(@"+cloneFromURL:toWorkingDirectory:options:error:transferProgressBlock:checkoutProgressBlock:", ^{ + __block BOOL transferProgressCalled = NO; + __block BOOL checkoutProgressCalled = NO; + __block void (^transferProgressBlock)(const git_transfer_progress *); + __block void (^checkoutProgressBlock)(NSString *, NSUInteger, NSUInteger); + __block NSURL *originURL; + __block NSURL *workdirURL; + + // TODO: Make real remote tests using a repo somewhere beforeEach(^{ - createRepository = ^(BOOL bare) { - NSURL *newRepoURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"unit_test"]]; - [NSFileManager.defaultManager removeItemAtURL:newRepoURL error:NULL]; + transferProgressCalled = NO; + checkoutProgressCalled = NO; + transferProgressBlock = ^(const git_transfer_progress *progress) { + transferProgressCalled = YES; + }; + checkoutProgressBlock = ^(NSString *path, NSUInteger completedSteps, NSUInteger totalSteps) { + checkoutProgressCalled = YES; + }; + + workdirURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"temp-repo"]; + }); + + describe(@"with local repositories", ^{ + beforeEach(^{ + originURL = self.bareFixtureRepository.gitDirectoryURL; + }); - GTRepository *repository = [GTRepository initializeEmptyRepositoryAtFileURL:newRepoURL bare:bare error:NULL]; + it(@"should handle normal clones", ^{ + NSError *error = nil; + repository = [GTRepository cloneFromURL:originURL toWorkingDirectory:workdirURL options:nil error:&error transferProgressBlock:transferProgressBlock checkoutProgressBlock:checkoutProgressBlock]; expect(repository).notTo.beNil(); - expect(repository.gitDirectoryURL).notTo.beNil(); - return repository; - }; + expect(error).to.beNil(); + expect(transferProgressCalled).to.beTruthy(); + expect(checkoutProgressCalled).to.beTruthy(); + + expect(repository.isBare).to.beFalsy(); + + GTReference *head = [repository headReferenceWithError:&error]; + expect(head).notTo.beNil(); + expect(error).to.beNil(); + expect(head.targetSHA).to.equal(@"36060c58702ed4c2a40832c51758d5344201d89a"); + expect(head.referenceType).to.equal(GTReferenceTypeOid); + }); + + it(@"should handle bare clones", ^{ + NSError *error = nil; + NSDictionary *options = @{ GTRepositoryCloneOptionsBare: @YES }; + repository = [GTRepository cloneFromURL:originURL toWorkingDirectory:workdirURL options:options error:&error transferProgressBlock:transferProgressBlock checkoutProgressBlock:checkoutProgressBlock]; + expect(repository).notTo.beNil(); + expect(error).to.beNil(); + expect(transferProgressCalled).to.beTruthy(); + expect(checkoutProgressCalled).to.beFalsy(); + + expect(repository.isBare).to.beTruthy(); + + GTReference *head = [repository headReferenceWithError:&error]; + expect(head).notTo.beNil(); + expect(error).to.beNil(); + expect(head.targetSHA).to.equal(@"36060c58702ed4c2a40832c51758d5344201d89a"); + expect(head.referenceType).to.equal(GTReferenceTypeOid); + }); + + it(@"should have set a valid remote URL", ^{ + NSError *error = nil; + GTRepository *repo = [GTRepository cloneFromURL:originURL toWorkingDirectory:workdirURL options:nil error:&error transferProgressBlock:transferProgressBlock checkoutProgressBlock:checkoutProgressBlock]; + expect(repo).notTo.beNil(); + expect(error).to.beNil(); + + // FIXME: Move that to a method in GTRepository ? + // Or use the new initializers in GTRemote that are waiting in #224 + git_remote *remote; + git_remote_load(&remote, repo.git_repository, "origin"); + GTRemote *originRemote = [[GTRemote alloc] initWithGitRemote:remote]; + expect(originRemote.URLString).to.equal(originURL.path); + }); }); +}); - it(@"should initialize a repository with a working directory by default", ^{ - GTRepository *repository = createRepository(NO); - expect(repository.bare).to.beFalsy(); +describe(@"-headReferenceWithError:", ^{ + it(@"should allow HEAD to be looked up", ^{ + NSError *error = nil; + GTReference *head = [self.bareFixtureRepository headReferenceWithError:&error]; + expect(head).notTo.beNil(); + expect(error).to.beNil(); + expect(head.targetSHA).to.equal(@"36060c58702ed4c2a40832c51758d5344201d89a"); + expect(head.referenceType).to.equal(GTReferenceTypeOid); }); - it(@"should initialize a bare repository", ^{ - GTRepository *repository = createRepository(YES); - expect(repository.bare).to.beTruthy(); + it(@"should fail to return HEAD for an unborn repo", ^{ + GTRepository *repo = self.blankFixtureRepository; + expect(repo.isHEADUnborn).to.beTruthy(); + + NSError *error = nil; + GTReference *head = [repo headReferenceWithError:&error]; + expect(head).to.beNil(); + expect(error).notTo.beNil(); + expect(error.domain).to.equal(GTGitErrorDomain); + expect(error.code).to.equal(GIT_EUNBORNBRANCH); + }); +}); + +describe(@"-isEmpty", ^{ + it(@"should return NO for a non-empty repository", ^{ + expect(repository.isEmpty).to.beFalsy(); + }); + + it(@"should return YES for a new repository", ^{ + NSError *error = nil; + NSURL *fileURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"newrepo"]; + GTRepository *newRepo = [GTRepository initializeEmptyRepositoryAtFileURL:fileURL error:&error]; + expect(newRepo.isEmpty).to.beTruthy(); + [NSFileManager.defaultManager removeItemAtURL:fileURL error:NULL]; }); }); @@ -64,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(); @@ -87,43 +209,55 @@ }); }); -describe(@"-OIDByCreatingTagNamed:target:tagger:message:error", ^{ - it(@"should create a new tag",^{ +describe(@"-currentBranchWithError:", ^{ + it(@"should return the current branch", ^{ NSError *error = nil; - NSString *SHA = @"0c37a5391bbff43c37f0d0371823a5509eed5b1d"; - GTRepository *repo = self.bareFixtureRepository; - GTTag *tag = (GTTag *)[repo lookupObjectBySHA:SHA error:&error]; + GTBranch *currentBranch = [repository currentBranchWithError:&error]; + expect(currentBranch).notTo.beNil(); + expect(error).to.beNil(); + expect(currentBranch.reference.name).to.equal(@"refs/heads/master"); + }); +}); - GTOID *newOID = [repo OIDByCreatingTagNamed:@"a_new_tag" target:tag.target tagger:tag.tagger message:@"my tag\n" error:&error]; - expect(newOID).notTo.beNil(); +describe(@"-localBranchesWithError:", ^{ + it(@"should return the local branches", ^{ + NSError *error = nil; + NSArray *branches = [repository localBranchesWithError:&error]; + expect(branches).notTo.beNil(); + expect(error).to.beNil(); + expect(branches.count).to.equal(13); + }); +}); - tag = (GTTag *)[repo lookupObjectByOID:newOID error:&error]; +describe(@"-remoteBranchesWithError:", ^{ + it(@"should return remote branches", ^{ + NSError *error = nil; + NSArray *branches = [repository remoteBranchesWithError:&error]; + expect(branches).notTo.beNil(); expect(error).to.beNil(); - expect(tag).notTo.beNil(); - expect(newOID.SHA).to.equal(tag.SHA); - expect(tag.type).to.equal(@"tag"); - expect(tag.message).to.equal(@"my tag\n"); - expect(tag.name).to.equal(@"a_new_tag"); - expect(tag.target.SHA).to.equal(@"5b5b025afb0b4c913b4c338a42934a3863bf3644"); - expect(tag.targetType).to.equal(GTObjectTypeCommit); + expect(branches.count).to.equal(1); + GTBranch *remoteBranch = branches[0]; + expect(remoteBranch.reference.name).to.equal(@"refs/remotes/origin/master"); }); +}); - it(@"should fail to create an already existing tag", ^{ +describe(@"-referenceNamesWithError:", ^{ + it(@"should return reference names", ^{ NSError *error = nil; - NSString *SHA = @"0c37a5391bbff43c37f0d0371823a5509eed5b1d"; - GTRepository *repo = self.bareFixtureRepository; - GTTag *tag = (GTTag *)[repo lookupObjectBySHA:SHA error:&error]; + NSArray *refs = [self.bareFixtureRepository referenceNamesWithError:&error]; + expect(refs).notTo.beNil(); + expect(error).to.beNil(); - GTOID *OID = [repo OIDByCreatingTagNamed:tag.name target:tag.target tagger:tag.tagger message:@"new message" error:&error]; - expect(OID).to.beNil(); - expect(error).notTo.beNil(); + expect(refs.count).to.equal(4); + NSArray *expectedRefs = @[ @"refs/heads/master", @"refs/tags/v0.9", @"refs/tags/v1.0", @"refs/heads/packed" ]; + expect(refs).to.equal(expectedRefs); }); }); 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]; @@ -133,7 +267,7 @@ it(@"should allow commits", ^{ NSError *error = nil; - GTCommit *commit = [repository lookupObjectBySHA:@"1d69f3c0aeaf0d62e25591987b93b8ffc53abd77" objectType:GTObjectTypeCommit error:&error]; + GTCommit *commit = [GTCommit lookupWithSHA:@"1d69f3c0aeaf0d62e25591987b93b8ffc53abd77" inRepository:repository error:&error]; expect(commit).to.beTruthy(); expect(error.localizedDescription).to.beNil(); BOOL result = [repository checkoutCommit:commit strategy:GTCheckoutStrategyAllowConflicts error:&error progressBlock:nil]; @@ -142,4 +276,65 @@ }); }); +describe(@"-resetToCommit:withResetType:error:", ^{ + beforeEach(^{ + repository = self.bareFixtureRepository; + }); + + it(@"should move HEAD when used", ^{ + NSError *error = nil; + GTReference *originalHead = [repository headReferenceWithError:NULL]; + NSString *resetTargetSHA = @"8496071c1b46c854b31185ea97743be6a8774479"; + + GTCommit *commit = [GTCommit lookupWithSHA:resetTargetSHA inRepository:repository error:NULL]; + expect(commit).notTo.beNil(); + GTCommit *originalHeadCommit = [GTCommit lookupWithSHA:originalHead.targetSHA inRepository:repository error:NULL]; + expect(originalHeadCommit).notTo.beNil(); + + BOOL success = [repository resetToCommit:commit withResetType:GTRepositoryResetTypeSoft error:&error]; + expect(success).to.beTruthy(); + expect(error).to.beNil(); + + GTReference *head = [repository headReferenceWithError:&error]; + expect(head).notTo.beNil(); + expect(head.targetSHA).to.equal(resetTargetSHA); + + success = [repository resetToCommit:originalHeadCommit withResetType:GTRepositoryResetTypeSoft error:&error]; + expect(success).to.beTruthy(); + expect(error).to.beNil(); + + head = [repository headReferenceWithError:&error]; + expect(head.targetSHA).to.equal(originalHead.targetSHA); + }); +}); + +describe(@"-lookupObjectByRevspec:error:", ^{ + void (^expectSHAForRevspec)(NSString *SHA, NSString *revspec) = ^(NSString *SHA, NSString *revspec) { + NSError *error = nil; + GTObject *obj = [repository lookupObjectByRevspec:revspec error:&error]; + + if (SHA != nil) { + expect(error).to.beNil(); + expect(obj).notTo.beNil(); + expect(obj.SHA).to.equal(SHA); + } else { + expect(error).notTo.beNil(); + expect(obj).to.beNil(); + } + };; + + beforeEach(^{ + repository = self.bareFixtureRepository; + }); + + it(@"should parse various revspecs", ^{ + expectSHAForRevspec(@"36060c58702ed4c2a40832c51758d5344201d89a", @"master"); + expectSHAForRevspec(@"5b5b025afb0b4c913b4c338a42934a3863bf3644", @"master~"); + expectSHAForRevspec(@"8496071c1b46c854b31185ea97743be6a8774479", @"master@{2}"); + expectSHAForRevspec(nil, @"master^2"); + expectSHAForRevspec(nil, @""); + expectSHAForRevspec(@"0c37a5391bbff43c37f0d0371823a5509eed5b1d", @"v1.0"); + }); +}); + SpecEnd diff --git a/ObjectiveGitTests/GTRepositoryStashingSpec.m b/ObjectiveGitTests/GTRepositoryStashingSpec.m index 892083dcb..c32334310 100644 --- a/ObjectiveGitTests/GTRepositoryStashingSpec.m +++ b/ObjectiveGitTests/GTRepositoryStashingSpec.m @@ -28,7 +28,7 @@ }); it(@"should create a stash with modified file content", ^{ - NSURL *fileURL = [repository.fileURL URLByAppendingPathComponent:@"README.md"]; + NSURL *fileURL = [repository.workingDirectoryURL URLByAppendingPathComponent:@"README.md"]; NSString *newContent = @"foobar"; NSString *oldContent = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:NULL]; @@ -46,7 +46,7 @@ }); it(@"should create a stash with uncommitted changes", ^{ - NSURL *fileURL = [repository.fileURL URLByAppendingPathComponent:@"README.md"]; + NSURL *fileURL = [repository.workingDirectoryURL URLByAppendingPathComponent:@"README.md"]; NSString *newContent = @"foobar"; NSString *oldContent = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:NULL]; @@ -64,7 +64,7 @@ }); it(@"should fail to create a stash with an untracked file using default options", ^{ - expect([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL]).to.beTruthy(); + expect([@"foobar" writeToURL:[repository.workingDirectoryURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL]).to.beTruthy(); NSError *error = nil; GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagDefault error:&error]; @@ -76,7 +76,7 @@ }); it(@"should stash an untracked file when enabled", ^{ - expect([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL]).to.beTruthy(); + expect([@"foobar" writeToURL:[repository.workingDirectoryURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL]).to.beTruthy(); NSError *error = nil; GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagIncludeUntracked error:&error]; @@ -90,7 +90,7 @@ for (int i = stashCount; i >= 0; i--) { NSString *filename = [NSString stringWithFormat:@"new-test-file-%i", i]; - expect([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:filename] atomically:YES encoding:NSUTF8StringEncoding error:NULL]).to.beTruthy(); + expect([@"foobar" writeToURL:[repository.workingDirectoryURL URLByAppendingPathComponent:filename] atomically:YES encoding:NSUTF8StringEncoding error:NULL]).to.beTruthy(); NSString *message = [NSString stringWithFormat:@"stash %i", i]; diff --git a/ObjectiveGitTests/GTRepositoryTest.m b/ObjectiveGitTests/GTRepositoryTest.m deleted file mode 100644 index 4656ee0ca..000000000 --- a/ObjectiveGitTests/GTRepositoryTest.m +++ /dev/null @@ -1,223 +0,0 @@ -// -// GTRepositoryTest.m -// ObjectiveGitFramework -// -// Created by Timothy Clem on 2/21/11. -// -// The MIT License -// -// Copyright (c) 2011 Tim Clem -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@interface GTRepositoryTest : GTTestCase { - - GTRepository *repo; - NSString *testContent; - GTObjectType testContentType; -} -@end - - -@implementation GTRepositoryTest - -- (void)setUp { - repo = self.bareFixtureRepository; - testContent = @"my test data\n"; - testContentType = GTObjectTypeBlob; -} - -- (void)removeDirectoryAtURL:(NSURL *)url { - NSFileManager *fm = [[NSFileManager alloc] init]; - NSError *error = nil; - - if([fm fileExistsAtPath:url.path]) { - STAssertTrue([fm removeItemAtPath:url.path error:&error], [error localizedDescription]); - } -} - -- (void)testFailsToOpenNonExistentRepo { - - NSError *error = nil; - GTRepository *badRepo = [GTRepository repositoryWithURL:[NSURL fileURLWithPath:@"fake/1235"] error:&error]; - - STAssertNil(badRepo, nil); - STAssertNotNil(error, nil); - NSLog(@"error = %@", [error localizedDescription]); -} - -- (void)testLookupHead { - - NSError *error = nil; - GTReference *head = [repo headReferenceWithError:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertEqualObjects(head.targetSHA, @"36060c58702ed4c2a40832c51758d5344201d89a", nil); - STAssertEquals(head.referenceType, GTReferenceTypeOid, nil); -} - -- (void)testIsEmpty { - STAssertFalse([repo isEmpty], nil); -} - -- (void)testCanReset { - NSError *err = nil; - GTRepository *aRepo = self.bareFixtureRepository; - STAssertNotNil(aRepo, @"Repository failed to initialise"); - GTReference *originalHead = [aRepo headReferenceWithError:NULL]; - NSString *resetTargetSha = @"8496071c1b46c854b31185ea97743be6a8774479"; - - GTCommit *commit = (GTCommit *)[aRepo lookupObjectBySHA:resetTargetSha error:NULL]; - - BOOL success = [aRepo resetToCommit:commit withResetType:GTRepositoryResetTypeSoft error:&err]; - STAssertTrue(success, @"Failed to reset, error given: %@", err); - GTReference *head = [aRepo headReferenceWithError:&err]; - STAssertEqualObjects(head.targetSHA, resetTargetSha, @"Reset failed to move head to given commit"); - - GTCommit *originalHeadCommit = (GTCommit *)[aRepo lookupObjectBySHA:originalHead.targetSHA error:NULL]; - [aRepo resetToCommit:originalHeadCommit withResetType:GTRepositoryResetTypeSoft error:NULL]; - head = [aRepo headReferenceWithError:&err]; - STAssertEqualObjects(head.unresolvedTarget, originalHead.unresolvedTarget, @"Reset failed to move head back to the original position"); -} - -- (void)expectSHA:(NSString*)sha forRefspec:(NSString*)refspec { - NSError *err = nil; - GTObject *obj = [repo lookupObjectByRefspec:refspec error:&err]; - - if (sha != nil) { - STAssertEquals((NSInteger)GIT_OK, err.code, @"git_revparse_single didn't return 0: %d", err.code); - STAssertNotNil(obj, @"Couldn't find object for %@", refspec); - STAssertEqualObjects(sha, obj.SHA, @"Revparse '%@': expected %@, got %@", refspec, sha, obj.SHA); - } else { - STAssertTrue(err.code != (NSInteger)GIT_OK, @"Expected error code, got 0"); - STAssertNil(obj, @"Got object when expected none for %@", refspec); - } -} - -- (void)testCanRevparse { - [self expectSHA:@"36060c58702ed4c2a40832c51758d5344201d89a" forRefspec:@"master"]; - [self expectSHA:@"5b5b025afb0b4c913b4c338a42934a3863bf3644" forRefspec:@"master~"]; - [self expectSHA:@"8496071c1b46c854b31185ea97743be6a8774479" forRefspec:@"master@{2}"]; - [self expectSHA:nil forRefspec:@"master^2"]; - [self expectSHA:nil forRefspec:@""]; - [self expectSHA:@"0c37a5391bbff43c37f0d0371823a5509eed5b1d" forRefspec:@"v1.0"]; - - GTObject *obj = [repo lookupObjectByRefspec:@"master" error:nil]; - STAssertNotNil(obj, @"Call with nil error should still work"); -} - - -- (void)testCanClone { - __block BOOL transferProgressCalled = NO; - __block BOOL checkoutProgressCalled = NO; - void (^transferProgressBlock)(const git_transfer_progress *) = ^(const git_transfer_progress *progress) { - transferProgressCalled = YES; - }; - void (^checkoutProgressBlock)(NSString *, NSUInteger, NSUInteger) = ^(NSString *path, NSUInteger completedSteps, NSUInteger totalSteps) { - checkoutProgressCalled = YES; - }; - NSURL *originURL = self.bareFixtureRepository.fileURL; //[NSURL URLWithString: @"https://github.com/libgit2/TestGitRepository"]; - NSURL *workdirURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"unit_test"]]; - NSError *err; - - [self removeDirectoryAtURL:workdirURL]; - - repo = [GTRepository cloneFromURL:originURL toWorkingDirectory:workdirURL options:NULL error:&err transferProgressBlock:transferProgressBlock checkoutProgressBlock:checkoutProgressBlock]; - - STAssertNotNil(repo, err.localizedDescription); - STAssertFalse([repo isBare], @"Standard repo should not be bare"); - STAssertTrue(transferProgressCalled, @"Transfer progress handler never called"); - STAssertTrue(checkoutProgressCalled, @"checkout progress handler never called"); - - GTReference *head = [repo headReferenceWithError:&err]; - STAssertNotNil(head, err.localizedDescription); - STAssertEqualObjects(head.targetSHA, @"36060c58702ed4c2a40832c51758d5344201d89a", nil); - STAssertEquals(head.referenceType, GTReferenceTypeOid, nil); -} - -- (void)testCanCloneBarely { - __block BOOL transferProgressCalled = NO; - __block BOOL checkoutProgressCalled = NO; - void (^transferProgressBlock)(const git_transfer_progress *) = ^(const git_transfer_progress *progress) { - transferProgressCalled = YES; - }; - void (^checkoutProgressBlock)(NSString *, NSUInteger, NSUInteger) = ^(NSString *path, NSUInteger completedSteps, NSUInteger totalSteps) { - checkoutProgressCalled = YES; - }; - NSURL *originURL = self.bareFixtureRepository.fileURL; //[NSURL URLWithString: @"https://github.com/libgit2/TestGitRepository"]; - NSURL *workdirURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"unit_test"]]; - NSDictionary *options = @{ GTRepositoryCloneOptionsBare: @YES }; - NSError *err; - - [self removeDirectoryAtURL:workdirURL]; - - repo = [GTRepository cloneFromURL:originURL toWorkingDirectory:workdirURL options:options error:&err transferProgressBlock:transferProgressBlock checkoutProgressBlock:checkoutProgressBlock]; - - STAssertNotNil(repo, err.localizedDescription); - STAssertTrue([repo isBare], @"Bare repo should be bare"); - STAssertTrue(transferProgressCalled, @"Transfer progress handler never called"); - STAssertFalse(checkoutProgressCalled, @"Checkout progress handler was called for bare repo"); - - GTReference *head = [repo headReferenceWithError:&err]; - STAssertNotNil(head, err.localizedDescription); - STAssertEqualObjects(head.targetSHA, @"36060c58702ed4c2a40832c51758d5344201d89a", nil); - STAssertEquals(head.referenceType, GTReferenceTypeOid, nil); -} - -//- (void) testCanGetRemotes { -// NSArray* remotesArray = [repo remoteNames]; -// -// STAssertTrue( [remotesArray containsObject: @"github"], @"remotes name did not contain expected remote" ); -// STAssertTrue( [repo hasRemoteNamed: @"github"], @"remotes name was not found by query function" ); -// -//} - -// This messes other tests up b/c it writes a new HEAD, but doesn't set it back again -/* -- (void)testLookupHeadThenCommitAndThenLookupHeadAgain { - - NSError *error = nil; - GTReference *head = [repo headAndReturnError:&error]; - STAssertNil(error, [error localizedDescription]); - STAssertEqualObjects(head.target, @"36060c58702ed4c2a40832c51758d5344201d89a", nil); - STAssertEqualObjects(head.type, @"commit", nil); - - NSString *tsha = @"c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b"; - GTObject *aObj = [repo lookupBySha:tsha error:&error]; - - STAssertNotNil(aObj, [error localizedDescription]); - STAssertTrue([aObj isKindOfClass:[GTTree class]], nil); - GTTree *tree = (GTTree *)aObj; - GTSignature *person = [[[GTSignature alloc] - initWithName:@"Tim" - email:@"tclem@github.com" - time:[NSDate date]] autorelease]; - GTCommit *commit = [GTCommit commitInRepo:repo updateRefNamed:@"HEAD" author:person committer:person message:@"new message" tree:tree parents:nil error:&error]; - STAssertNotNil(commit, [error localizedDescription]); - NSLog(@"wrote sha %@", commit.sha); - - head = [repo headAndReturnError:&error]; - STAssertNotNil(head, [error localizedDescription]); - - STAssertEqualObjects(head.target, commit.sha, nil); - - rm_loose(commit.sha); -} -*/ -@end diff --git a/ObjectiveGitTests/GTSubmoduleSpec.m b/ObjectiveGitTests/GTSubmoduleSpec.m index a20f0779a..8999a4e69 100644 --- a/ObjectiveGitTests/GTSubmoduleSpec.m +++ b/ObjectiveGitTests/GTSubmoduleSpec.m @@ -80,7 +80,7 @@ GTSubmodule *submodule = [repo submoduleWithName:@"new_submodule" error:NULL]; expect(submodule).to.beNil(); - NSURL *gitmodulesURL = [repo.fileURL URLByAppendingPathComponent:@".gitmodules"]; + NSURL *gitmodulesURL = [repo.workingDirectoryURL URLByAppendingPathComponent:@".gitmodules"]; NSMutableString *gitmodules = [NSMutableString stringWithContentsOfURL:gitmodulesURL usedEncoding:NULL error:NULL]; expect(gitmodules).notTo.beNil(); @@ -143,7 +143,7 @@ expect(submoduleRepo).notTo.beNil(); expect(error).to.beNil(); - expect(submoduleRepo.fileURL).to.equal([repo.fileURL URLByAppendingPathComponent:@"Test_App"]); + expect(submoduleRepo.workingDirectoryURL).to.equal([repo.workingDirectoryURL URLByAppendingPathComponent:@"Test_App"]); expect(submoduleRepo.bare).to.beFalsy(); expect(submoduleRepo.empty).to.beFalsy(); expect(submoduleRepo.HEADDetached).to.beTruthy(); @@ -154,7 +154,7 @@ GTRepository *submoduleRepo = [submodule submoduleRepository:NULL]; expect(submoduleRepo).notTo.beNil(); - GTCommit *newHEAD = (id)[submoduleRepo lookupObjectBySHA:@"82dc47f6ba3beecab33080a1136d8913098e1801" objectType:GTObjectTypeCommit error:NULL]; + GTCommit *newHEAD = [GTCommit lookupWithSHA:@"82dc47f6ba3beecab33080a1136d8913098e1801" inRepository:submoduleRepo error:NULL]; expect(newHEAD).notTo.beNil(); expect([submoduleRepo resetToCommit:newHEAD withResetType:GTRepositoryResetTypeHard error:NULL]).to.beTruthy(); @@ -225,7 +225,7 @@ expect(submoduleRepo).notTo.beNil(); expect(error).to.beNil(); - expect(submoduleRepo.fileURL).to.equal([repo.fileURL URLByAppendingPathComponent:@"Test_App2"]); + expect(submoduleRepo.workingDirectoryURL).to.equal([repo.workingDirectoryURL URLByAppendingPathComponent:@"Test_App2"]); expect(submoduleRepo.bare).to.beFalsy(); expect(submoduleRepo.empty).to.beFalsy(); expect(submoduleRepo.HEADDetached).to.beTruthy(); diff --git a/ObjectiveGitTests/GTTagSpec.m b/ObjectiveGitTests/GTTagSpec.m index f1416c53a..18333f615 100644 --- a/ObjectiveGitTests/GTTagSpec.m +++ b/ObjectiveGitTests/GTTagSpec.m @@ -11,12 +11,13 @@ SpecBegin(GTTag) __block GTTag *tag; +__block GTRepository *repository; beforeEach(^{ NSError *error = nil; - GTRepository *repo = self.bareFixtureRepository; + repository = self.bareFixtureRepository; NSString *tagSHA = @"0c37a5391bbff43c37f0d0371823a5509eed5b1d"; - tag = (GTTag *)[repo lookupObjectBySHA:tagSHA error:&error]; + tag = [GTTag lookupWithSHA:tagSHA inRepository:repository error:&error]; expect(error).to.beFalsy(); expect(tag).to.beTruthy(); expect(tagSHA).to.equal(tag.SHA); @@ -35,4 +36,74 @@ expect(signature.email).to.equal(@"schacon@gmail.com"); }); + +describe(@"+tagByCreatingTagNamed:target:message:tagger:force:inRepository:error:", ^{ + __block NSString *originalTagSHA = nil; + __block GTTag *originalTag = nil; + beforeEach(^{ + originalTagSHA = @"0c37a5391bbff43c37f0d0371823a5509eed5b1d"; + originalTag = [GTTag lookupWithSHA:originalTagSHA inRepository:repository error:NULL]; + }); + + it(@"should create a new tag",^{ + NSError *error = nil; + + GTTag *tag = [GTTag tagByCreatingTagNamed:@"a_new_tag" target:originalTag.target message:@"my tag\n" tagger:originalTag.tagger force:NO inRepository:repository error:&error]; + expect(error).to.beNil(); + expect(tag).notTo.beNil(); + expect(tag.type).to.equal(@"tag"); + expect(tag.message).to.equal(@"my tag\n"); + expect(tag.name).to.equal(@"a_new_tag"); + expect(tag.target.SHA).to.equal(@"5b5b025afb0b4c913b4c338a42934a3863bf3644"); + expect(tag.targetType).to.equal(GTObjectTypeCommit); + }); + + it(@"should fail to create an already existing tag", ^{ + NSError *error = nil; + GTTag *tag = [GTTag tagByCreatingTagNamed:originalTag.name target:originalTag.target message:@"my tag\n" tagger:originalTag.tagger force:NO inRepository:repository error:&error]; + expect(tag).to.beNil(); + expect(error.domain).to.equal(GTGitErrorDomain); + expect(error.code).to.equal(GIT_EEXISTS); + }); + + it(@"should delete an existing tag if `force` is YES", ^{ + NSError *error = nil; + GTTag *tag = [GTTag tagByCreatingTagNamed:originalTag.name target:originalTag.target message:@"my tag\n" tagger:originalTag.tagger force:YES inRepository:repository error:&error]; + expect(tag).notTo.beNil(); + expect(error).to.beNil(); + }); +}); + +describe(@"+tagByCreatingLightweightTagNamed:target:force:inRepository:error:", ^{ + __block NSString *originalTagSHA = nil; + __block GTTag *originalTag = nil; + beforeEach(^{ + originalTagSHA = @"0c37a5391bbff43c37f0d0371823a5509eed5b1d"; + originalTag = [GTTag lookupWithSHA:originalTagSHA inRepository:repository error:NULL]; + }); + + it(@"should create a new lightweight tag", ^{ + NSError *error = nil; + GTReference *tagReference = [GTTag tagByCreatingLightweightTagNamed:@"another-tag" target:originalTag.target force:NO inRepository:repository error:&error]; + expect(tagReference).notTo.beNil(); + expect(error).to.beNil(); + expect(tagReference.targetSHA).to.equal(originalTag.target.SHA); + }); + + it(@"should fail to create an already existing tag", ^{ + NSError *error = nil; + GTReference *tagReference = [GTTag tagByCreatingLightweightTagNamed:originalTag.name target:originalTag.target force:NO inRepository:repository error:&error]; + expect(tagReference).to.beNil(); + expect(error.domain).to.equal(GTGitErrorDomain); + expect(error.code).to.equal(GIT_EEXISTS); + }); + + it(@"should delete an existing tag if `force` is YES", ^{ + NSError *error = nil; + GTReference *tagReference = [GTTag tagByCreatingLightweightTagNamed:originalTag.name target:originalTag.target force:YES inRepository:repository error:&error]; + expect(tagReference).notTo.beNil(); + expect(error).to.beNil(); + }); +}); + SpecEnd diff --git a/ObjectiveGitTests/GTTestCase.h b/ObjectiveGitTests/GTTestCase.h index 2d67cc2d8..b29edee85 100644 --- a/ObjectiveGitTests/GTTestCase.h +++ b/ObjectiveGitTests/GTTestCase.h @@ -29,4 +29,10 @@ // A repository containing conflicts. - (GTRepository *)conflictedFixtureRepository; +// A pristine repository (bare). +- (GTRepository *)blankBareFixtureRepository; + +// A pristine repository. +- (GTRepository *)blankFixtureRepository; + @end diff --git a/ObjectiveGitTests/GTTestCase.m b/ObjectiveGitTests/GTTestCase.m index 14651cbc1..6b2b2cf2a 100644 --- a/ObjectiveGitTests/GTTestCase.m +++ b/ObjectiveGitTests/GTTestCase.m @@ -114,6 +114,22 @@ - (GTRepository *)conflictedFixtureRepository { return [self fixtureRepositoryNamed:@"conflicted-repo"]; } +- (GTRepository *)blankFixtureRepository { + NSURL *repoURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"blank-repo"]; + + GTRepository *repository = [GTRepository initializeEmptyRepositoryAtFileURL:repoURL bare:NO error:NULL]; + STAssertNotNil(repository, @"Couldn't create a blank repository"); + return repository; +} + +- (GTRepository *)blankBareFixtureRepository { + NSURL *repoURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"blank-repo.git"]; + + GTRepository *repository = [GTRepository initializeEmptyRepositoryAtFileURL:repoURL bare:YES error:NULL]; + STAssertNotNil(repository, @"Couldn't create a blank repository"); + return repository; +} + #pragma mark Properties - (NSBundle *)mainTestBundle { diff --git a/ObjectiveGitTests/GTTreeBuilderSpec.m b/ObjectiveGitTests/GTTreeBuilderSpec.m index f36b4ec64..62eb8476b 100644 --- a/ObjectiveGitTests/GTTreeBuilderSpec.m +++ b/ObjectiveGitTests/GTTreeBuilderSpec.m @@ -29,8 +29,8 @@ GTRepository *repo = self.bareFixtureRepository; expect(repo).notTo.beNil(); - - GTTree *tree = (GTTree *)[repo lookupObjectBySHA:testTreeSHA error:NULL]; + + GTTree *tree = [GTTree lookupWithSHA:testTreeSHA inRepository:repo error:NULL]; expect(tree).notTo.beNil(); GTTreeBuilder *builder = [[GTTreeBuilder alloc] initWithTree:tree error:&error]; @@ -134,8 +134,8 @@ GTTree *writtenTree = [builder writeTreeToRepository:repo error:&error]; expect(writtenTree).notTo.beNil(); expect(error).to.beNil(); - - GTTree *readTree = (GTTree *)[repo lookupObjectBySHA:writtenTree.SHA objectType:GTObjectTypeTree error:&error]; + + GTTree *readTree = [GTTree lookupWithSHA:writtenTree.SHA inRepository:repo error:&error]; expect(readTree).notTo.beNil(); expect(error).to.beNil(); }); diff --git a/ObjectiveGitTests/GTTreeSpec.m b/ObjectiveGitTests/GTTreeSpec.m index fc0f12454..cda740817 100644 --- a/ObjectiveGitTests/GTTreeSpec.m +++ b/ObjectiveGitTests/GTTreeSpec.m @@ -19,7 +19,7 @@ GTRepository *repo = self.bareFixtureRepository; expect(repo).notTo.beNil(); - tree = (GTTree *)[repo lookupObjectBySHA:testTreeSHA error:NULL]; + tree = [GTTree lookupWithSHA:testTreeSHA inRepository:repo error:NULL]; expect(tree).notTo.beNil(); });