Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Worktree support #642

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ObjectiveGit/GTBlameHunk.m
Original file line number Diff line number Diff line change
@@ -39,7 +39,9 @@ - (GTSignature *)finalSignature {
}

- (NSString *)originalPath {
return @(self.git_blame_hunk.orig_path);
NSString *path = @(self.git_blame_hunk.orig_path);
NSAssert(path, @"string was nil");
return path;
}

- (BOOL)isBoundary {
5 changes: 4 additions & 1 deletion ObjectiveGit/GTConfiguration.m
Original file line number Diff line number Diff line change
@@ -112,7 +112,10 @@ - (BOOL)deleteValueForKey:(NSString *)key error:(NSError **)error {
static int configCallback(const git_config_entry *entry, void *payload) {
NSMutableArray *configurationKeysArray = (__bridge NSMutableArray *)payload;

[configurationKeysArray addObject:@(entry->name)];
NSString *name = @(entry->name);
NSCAssert(name, @"string was nil");

[configurationKeysArray addObject:name];

return 0;
}
5 changes: 3 additions & 2 deletions ObjectiveGit/GTDiffFile.m
Original file line number Diff line number Diff line change
@@ -22,8 +22,9 @@ - (instancetype)initWithGitDiffFile:(git_diff_file)file {
self = [super init];
if (self == nil) return nil;

_path = @(file.path);
if (_path == nil) return nil;
NSString *path = @(file.path);
if (path == nil) return nil;
_path = path;

_git_diff_file = file;
_size = (NSUInteger)file.size;
11 changes: 7 additions & 4 deletions ObjectiveGit/GTFilterSource.m
Original file line number Diff line number Diff line change
@@ -27,10 +27,13 @@ - (instancetype)initWithGitFilterSource:(const git_filter_source *)source {
self = [super init];
if (self == nil) return nil;

const char *path = git_repository_workdir(git_filter_source_repo(source));
_repositoryURL = [NSURL fileURLWithPath:@(path)];

_path = @(git_filter_source_path(source));
NSString *path = @(git_repository_workdir(git_filter_source_repo(source)));
NSAssert(path, @"workdir was nil");
_repositoryURL = [NSURL fileURLWithPath:path];

path = @(git_filter_source_path(source));
NSAssert(path, @"path was nil");
_path = path;

const git_oid *gitOid = git_filter_source_id(source);
if (gitOid != NULL) _OID = [[GTOID alloc] initWithGitOid:gitOid];
4 changes: 3 additions & 1 deletion ObjectiveGit/GTIndexEntry.m
Original file line number Diff line number Diff line change
@@ -74,7 +74,9 @@ - (instancetype)initWithGitIndexEntry:(const git_index_entry *)entry {
#pragma mark Properties

- (NSString *)path {
return @(self.git_index_entry->path);
NSString *path = @(self.git_index_entry->path);
NSAssert(path, @"path is nil");
return path;
}

- (int)flags {
4 changes: 3 additions & 1 deletion ObjectiveGit/GTNote.m
Original file line number Diff line number Diff line change
@@ -42,7 +42,9 @@ - (git_note *)git_note {
}

- (NSString *)note {
return @(git_note_message(self.git_note));
NSString *message = @(git_note_message(self.git_note));
NSAssert(message, @"message is nil");
return message;
}

- (GTSignature *)author {
8 changes: 5 additions & 3 deletions ObjectiveGit/GTReference.m
Original file line number Diff line number Diff line change
@@ -118,10 +118,12 @@ - (BOOL)isNote {
}

- (NSString *)name {
const char *refName = git_reference_name(self.git_reference);
NSAssert(refName != nil, @"Unexpected nil name");
const char *cRefName = git_reference_name(self.git_reference);
NSAssert(cRefName != nil, @"Unexpected nil name");

return @(refName);
NSString *refName = @(cRefName);
NSAssert(refName, @"refname is nil");
return refName;
}

- (GTReference *)referenceByRenaming:(NSString *)newName error:(NSError **)error {
10 changes: 8 additions & 2 deletions ObjectiveGit/GTRepository+RemoteOperations.m
Original file line number Diff line number Diff line change
@@ -126,9 +126,15 @@ int GTFetchHeadEntriesCallback(const char *ref_name, const char *remote_url, con
GTRepository *repository = entriesPayload->repository;
GTRemoteEnumerateFetchHeadEntryBlock enumerationBlock = entriesPayload->enumerationBlock;

GTReference *reference = [repository lookUpReferenceWithName:@(ref_name) error:NULL];
NSString *refName = @(ref_name);
NSCAssert(refName, @"refName is nil");

GTFetchHeadEntry *entry = [[GTFetchHeadEntry alloc] initWithReference:reference remoteURLString:@(remote_url) targetOID:[GTOID oidWithGitOid:oid] isMerge:(BOOL)is_merge];
NSString *remoteURL = @(remote_url);
NSCAssert(remote_url, @"remoteURL is nil");

GTReference *reference = [repository lookUpReferenceWithName:refName error:NULL];

GTFetchHeadEntry *entry = [[GTFetchHeadEntry alloc] initWithReference:reference remoteURLString:remoteURL targetOID:[GTOID oidWithGitOid:oid] isMerge:(BOOL)is_merge];

BOOL stop = NO;

42 changes: 42 additions & 0 deletions ObjectiveGit/GTRepository+Worktree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// GTRepository+GTRepository_Worktree.h
// ObjectiveGitFramework
//
// Created by Etienne on 25/07/2017.
// Copyright © 2017 GitHub, Inc. All rights reserved.
//

#import <ObjectiveGit/ObjectiveGit.h>

@class GTWorktree;

NS_ASSUME_NONNULL_BEGIN

@interface GTRepository (Worktree)

/// Is this the worktree of another repository ?
@property (nonatomic, readonly, getter = isWorktree) BOOL worktree;

/// The URL for the underlying repository's git directory.
/// Returns the same as -gitDirectoryURL if this is not a worktree.
@property (nonatomic, readonly, strong) NSURL *commonGitDirectoryURL;

+ (instancetype _Nullable)repositoryWithWorktree:(GTWorktree *)worktree error:(NSError **)error;

- (instancetype _Nullable)initWithWorktree:(GTWorktree *)worktree error:(NSError **)error;

- (GTReference * _Nullable)HEADReferenceInWorktreeWithName:(NSString *)name error:(NSError **)error;

- (BOOL)isHEADDetached:(BOOL *)detached inWorktreeWithName:(NSString *)name error:(NSError **)error;

- (BOOL)setWorkingDirectoryURL:(NSURL *)URL updateGitLink:(BOOL)update error:(NSError **)error;

- (NSArray <NSString *> * _Nullable)worktreeNamesWithError:(NSError **)error;

- (GTWorktree * _Nullable)lookupWorktreeWithName:(NSString *)name error:(NSError **)error;

- (GTWorktree * _Nullable)openWorktree:(NSError **)error;

@end

NS_ASSUME_NONNULL_END
117 changes: 117 additions & 0 deletions ObjectiveGit/GTRepository+Worktree.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//
// GTRepository+Worktree.m
// ObjectiveGitFramework
//
// Created by Etienne on 25/07/2017.
// Copyright © 2017 GitHub, Inc. All rights reserved.
//

#import "GTRepository+Worktree.h"

@implementation GTRepository (Worktree)

+ (instancetype)repositoryWithWorktree:(GTWorktree *)worktree error:(NSError **)error {
return [[self alloc] initWithWorktree:worktree error:error];
}

- (instancetype)initWithWorktree:(GTWorktree *)worktree error:(NSError **)error {
NSParameterAssert(worktree != nil);

git_repository *repo;
int gitError = git_repository_open_from_worktree(&repo, worktree.git_worktree);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to open worktree"];
return nil;
}
return [self initWithGitRepository:repo];
}

- (BOOL)isWorktree {
return (BOOL)git_repository_is_worktree(self.git_repository);
}

- (NSURL *)commonGitDirectoryURL {
const char *cPath = git_repository_commondir(self.git_repository);
NSAssert(cPath, @"commondir is nil");

NSString *path = @(cPath);
NSAssert(path, @"commondir is nil");
return [NSURL fileURLWithPath:path isDirectory:YES];
}

- (GTReference *)HEADReferenceInWorktreeWithName:(NSString *)name error:(NSError **)error {
NSParameterAssert(name != nil);

git_reference *ref;
int gitError = git_repository_head_for_worktree(&ref, self.git_repository, name.UTF8String);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to resolve HEAD in worktree"];
return nil;
}

return [[GTReference alloc] initWithGitReference:ref repository:self];
}

- (BOOL)isHEADDetached:(BOOL *)detached inWorktreeWithName:(NSString *)name error:(NSError **)error {
NSParameterAssert(detached != nil);
NSParameterAssert(name != nil);

int gitError = git_repository_head_detached_for_worktree(self.git_repository, name.UTF8String);
if (gitError < 0) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to resolve HEAD in worktree"];
return NO;
}

*detached = (gitError == 1);

return YES;
}

- (BOOL)setWorkingDirectoryURL:(NSURL *)URL updateGitLink:(BOOL)update error:(NSError **)error {
NSParameterAssert(URL != nil);

int gitError = git_repository_set_workdir(self.git_repository, URL.fileSystemRepresentation, update);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to set workdir"];
return NO;
}

return YES;
}

- (NSArray<NSString *> *)worktreeNamesWithError:(NSError **)error {
git_strarray names;
int gitError = git_worktree_list(&names, self.git_repository);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to load worktree names"];
return nil;
}

return [NSArray git_arrayWithStrarray:names];
}

- (GTWorktree *)lookupWorktreeWithName:(NSString *)name error:(NSError **)error {
NSParameterAssert(name != nil);

git_worktree *worktree;
int gitError = git_worktree_lookup(&worktree, self.git_repository, name.UTF8String);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to lookup worktree"];
return nil;
}

return [[GTWorktree alloc] initWithGitWorktree:worktree];
}

- (GTWorktree *)openWorktree:(NSError **)error {
git_worktree *worktree;
int gitError = git_worktree_open_from_repository(&worktree, self.git_repository);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to open worktree"];
return nil;
}

return [[GTWorktree alloc] initWithGitWorktree:worktree];
}

@end
2 changes: 1 addition & 1 deletion ObjectiveGit/GTRepository.h
Original file line number Diff line number Diff line change
@@ -156,7 +156,7 @@ typedef NS_ENUM(NSInteger, GTRepositoryStateType) {
/// Returns nil for a bare repository.
@property (nonatomic, readonly, strong) NSURL * _Nullable fileURL;
/// The file URL for the repository's .git directory.
@property (nonatomic, readonly, strong) NSURL * _Nullable gitDirectoryURL;
@property (nonatomic, readonly, strong) NSURL *gitDirectoryURL;

/// Is this a bare repository (one without a working directory)?
@property (nonatomic, readonly, getter = isBare) BOOL bare;
20 changes: 13 additions & 7 deletions ObjectiveGit/GTRepository.m
Original file line number Diff line number Diff line change
@@ -637,18 +637,22 @@ - (NSArray *)referenceNamesWithError:(NSError **)error {
}

- (NSURL *)fileURL {
const char *path = git_repository_workdir(self.git_repository);
const char *cPath = git_repository_workdir(self.git_repository);
// bare repository, you may be looking for gitDirectoryURL
if (path == NULL) return nil;
if (cPath == NULL) return nil;

return [NSURL fileURLWithPath:@(path) isDirectory:YES];
NSString *path = @(cPath);
NSAssert(path, @"workdir is nil");
return [NSURL fileURLWithPath:path isDirectory:YES];
}

- (NSURL *)gitDirectoryURL {
const char *path = git_repository_path(self.git_repository);
if (path == NULL) return nil;
const char *cPath = git_repository_path(self.git_repository);
NSAssert(cPath, @"gitdirectory is nil");

return [NSURL fileURLWithPath:@(path) isDirectory:YES];
NSString *path = @(cPath);
NSAssert(path, @"gitdirectory is nil");
return [NSURL fileURLWithPath:path isDirectory:YES];
}

- (BOOL)isBare {
@@ -737,7 +741,9 @@ static int submoduleEnumerationCallback(git_submodule *git_submodule, const char

NSError *error;
// Use -submoduleWithName:error: so that we get a git_submodule that we own.
GTSubmodule *submodule = [info->parentRepository submoduleWithName:@(name) error:&error];
NSString *submoduleName = @(name);
NSCAssert(submoduleName, @"submodule name is nil");
GTSubmodule *submodule = [info->parentRepository submoduleWithName:submoduleName error:&error];

BOOL stop = NO;
info->block(submodule, error, &stop);
15 changes: 12 additions & 3 deletions ObjectiveGit/GTSubmodule.m
Original file line number Diff line number Diff line change
@@ -62,21 +62,30 @@ - (NSString *)name {
const char *cName = git_submodule_name(self.git_submodule);
NSAssert(cName != NULL, @"Unexpected nil submodule name");

return @(cName);
NSString *name = @(cName);
NSAssert(name, @"name is nil");

return name;
}

- (NSString *)path {
const char *cPath = git_submodule_path(self.git_submodule);
NSAssert(cPath != NULL, @"Unexpected nil submodule path");

return @(cPath);
NSString *path = @(cPath);
NSAssert(path, @"message is nil");

return path;
}

- (NSString *)URLString {
const char *cURL = git_submodule_url(self.git_submodule);
NSAssert(cURL != NULL, @"Unexpected nil submodule URL");

return @(cURL);
NSString *URL = @(cURL);
NSAssert(URL, @"URL is nil");

return URL;
}

#pragma mark Lifecycle
8 changes: 6 additions & 2 deletions ObjectiveGit/GTTag.m
Original file line number Diff line number Diff line change
@@ -47,11 +47,15 @@ - (NSString *)description {
#pragma mark API

- (NSString *)message {
return @(git_tag_message(self.git_tag));
NSString *message = @(git_tag_message(self.git_tag));
NSAssert(message, @"message is nil");
return message;
}

- (NSString *)name {
return @(git_tag_name(self.git_tag));
NSString *name = @(git_tag_name(self.git_tag));
NSAssert(name, @"message is nil");
return name;
}

- (GTObject *)target {
4 changes: 3 additions & 1 deletion ObjectiveGit/GTTreeEntry.m
Original file line number Diff line number Diff line change
@@ -94,7 +94,9 @@ + (instancetype)entryWithEntry:(const git_tree_entry *)theEntry parentTree:(GTTr
}

- (NSString *)name {
return @(git_tree_entry_name(self.git_tree_entry));
NSString *name = @(git_tree_entry_name(self.git_tree_entry));
NSAssert(name, @"name was nil");
return name;
}

- (NSInteger)attributes {
75 changes: 75 additions & 0 deletions ObjectiveGit/GTWorktree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// GTWorktree.h
// ObjectiveGitFramework
//
// Created by Etienne on 25/07/2017.
// Copyright © 2017 GitHub, Inc. All rights reserved.
//

#import <Foundation/Foundation.h>

#import "GTRepository.h"

#import "git2/worktree.h"

NS_ASSUME_NONNULL_BEGIN

/// Add a worktree and keep it locked
/// A boolean, defaults to NO.
extern NSString *GTWorktreeAddOptionsLocked;

@interface GTWorktree : NSObject

/// Add a new worktree to a repository.
///
/// @param name The name of the worktree.
/// @param worktreeURL The location of the worktree.
/// @param repository The repository the worktree should be added to.
/// @param options The options to use when adding the worktree.
///
/// @return the newly created worktree object.
+ (instancetype _Nullable)addWorktreeWithName:(NSString *)name URL:(NSURL *)worktreeURL forRepository:(GTRepository *)repository options:(NSDictionary * _Nullable)options error:(NSError **)error;

/// Initialize a worktree from a git_worktree.
- (instancetype _Nullable)initWithGitWorktree:(git_worktree *)worktree;

/// The underlying `git_worktree` object.
- (git_worktree *)git_worktree __attribute__((objc_returns_inner_pointer));

/// Check the worktree validity
///
/// @param error An explanation if the worktree is not valid. nil otherwise
///
/// @return YES if the worktree is valid, NO otherwise (and error will be set).
- (BOOL)isValid:(NSError **)error;

/// Lock the worktree.
///
/// This will prevent the worktree from being prunable.
///
/// @param reason An optional reason for the lock.
/// @param error The error if the worktree couldn't be locked.
///
/// @return YES if the lock was successful, NO otherwise (and error will be set).
- (BOOL)lockWithReason:(NSString * _Nullable)reason error:(NSError **)error;

/// Unlock a worktree.
///
/// @param wasLocked On return, NO if the worktree wasn't locked, YES otherwise.
/// @param error The error if the worktree couldn't be unlocked.
///
/// @return YES if the unlock succeeded, NO otherwise (and error will be set).
- (BOOL)unlock:(BOOL * _Nullable)wasLocked error:(NSError **)error;

/// Check a worktree's lock state.
///
/// @param locked On return, YES if the worktree is locked, NO otherwise.
/// @param reason On return, the lock reason, if the worktree is locked. nil otherwise.
/// @param error The error if the lock state couldn't be determined.
///
/// @return YES if the check succeeded, NO otherwise (and error will be set).
- (BOOL)isLocked:(BOOL * _Nullable)locked reason:(NSString * _Nullable __autoreleasing * _Nullable)reason error:(NSError **)error;

@end

NS_ASSUME_NONNULL_END
110 changes: 110 additions & 0 deletions ObjectiveGit/GTWorktree.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// GTWorktree.m
// ObjectiveGitFramework
//
// Created by Etienne on 25/07/2017.
// Copyright © 2017 GitHub, Inc. All rights reserved.
//

#import "NSError+Git.h"
#import "GTWorktree.h"
#import "NSData+Git.h"

#import "git2/errors.h"
#import "git2/buffer.h"

NSString *GTWorktreeAddOptionsLocked = @"GTWorktreeAddOptionsLocked";

@interface GTWorktree ()
@property (nonatomic, assign, readonly) git_worktree *git_worktree;
@end

@implementation GTWorktree

+ (instancetype)addWorktreeWithName:(NSString *)name URL:(NSURL *)worktreeURL forRepository:(GTRepository *)repository options:(NSDictionary *)options error:(NSError **)error {
git_worktree *worktree;
git_worktree_add_options git_options = GIT_WORKTREE_ADD_OPTIONS_INIT;

if (options) {
git_options.lock = [options[GTWorktreeAddOptionsLocked] boolValue];
}

int gitError = git_worktree_add(&worktree, repository.git_repository, name.UTF8String, worktreeURL.fileSystemRepresentation, &git_options);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to add worktree"];
return nil;
}

return [[self alloc] initWithGitWorktree:worktree];
}

- (instancetype)initWithGitWorktree:(git_worktree *)worktree {
self = [super init];
if (!self) return nil;

_git_worktree = worktree;

return self;
}

- (void)dealloc {
git_worktree_free(_git_worktree);
}

- (BOOL)isValid:(NSError **)error {
int gitError = git_worktree_validate(self.git_worktree);
if (gitError < 0) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to validate worktree"];
return NO;
}

return YES;
}

- (BOOL)lockWithReason:(NSString *)reason error:(NSError **)error {
int gitError = git_worktree_lock(self.git_worktree, reason.UTF8String);
if (gitError != GIT_OK) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to lock worktree"];
return NO;
}

return YES;
}

- (BOOL)unlock:(BOOL *)wasLocked error:(NSError **)error {
int gitError = git_worktree_unlock(self.git_worktree);
if (gitError < 0) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to unlock worktree"];
return NO;
}

if (wasLocked) {
// unlock returns 1 if there was no lock.
*wasLocked = (gitError == 0);
}

return YES;
}

- (BOOL)isLocked:(BOOL *)locked reason:(NSString **)reason error:(NSError **)error {
git_buf reasonBuf = GIT_BUF_INIT_CONST("", 0);
int gitError = git_worktree_is_locked(&reasonBuf, self.git_worktree);
if (gitError < 0) {
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to check lock state of worktree"];
return NO;
}

if (locked) *locked = (gitError > 0);
if (reason) {
if (gitError > 0 && reasonBuf.size > 0) {
*reason = [[NSString alloc] initWithData:[NSData git_dataWithBuffer:&reasonBuf]
encoding:NSUTF8StringEncoding];
} else {
*reason = nil;
}
}

return YES;
}

@end
3 changes: 3 additions & 0 deletions ObjectiveGit/ObjectiveGit.h
Original file line number Diff line number Diff line change
@@ -42,6 +42,8 @@ FOUNDATION_EXPORT const unsigned char ObjectiveGitVersionString[];
#import <ObjectiveGit/GTRepository+Reset.h>
#import <ObjectiveGit/GTRepository+Pull.h>
#import <ObjectiveGit/GTRepository+Merging.h>
#import <ObjectiveGit/GTRepository+Worktree.h>
#import <ObjectiveGit/GTWorktree.h>
#import <ObjectiveGit/GTEnumerator.h>
#import <ObjectiveGit/GTCommit.h>
#import <ObjectiveGit/GTCredential.h>
@@ -72,6 +74,7 @@ FOUNDATION_EXPORT const unsigned char ObjectiveGitVersionString[];
#import <ObjectiveGit/GTFetchHeadEntry.h>
#import <ObjectiveGit/GTNote.h>
#import <ObjectiveGit/GTCheckoutOptions.h>
#import <ObjectiveGit/GTWorktree.h>

#import <ObjectiveGit/GTObjectDatabase.h>
#import <ObjectiveGit/GTOdbObject.h>
34 changes: 34 additions & 0 deletions ObjectiveGitFramework.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -89,15 +89,26 @@
30FDC08116835A8100654BF0 /* GTDiffLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 30FDC07E16835A8100654BF0 /* GTDiffLine.m */; };
4D123240178E009E0048F785 /* GTRepositoryCommittingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */; };
4D1C40D8182C006D00BE2960 /* GTBlobSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D1C40D7182C006D00BE2960 /* GTBlobSpec.m */; };
4D22E16B2127922F003CD3CE /* GTWorktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC372101F27D6D3003CD3CE /* GTWorktree.m */; };
4D22E16C21279314003CD3CE /* GTRepository+Worktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */; };
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 */; };
4D962C402126243A003CD3CE /* GTWorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */; };
4D962C412126243A003CD3CE /* GTWorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */; };
4D962C43212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */; };
4D962C44212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */; };
4D9BCD24206D84AD003CD3CE /* libgit2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D9BCD23206D84AD003CD3CE /* libgit2.a */; };
4D9BCD25206D84B2003CD3CE /* libgit2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D9BCD23206D84AD003CD3CE /* libgit2.a */; };
4DBA4A3217DA73CE006CD5F5 /* GTRemoteSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */; };
4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */; };
4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC372101F27D6D3003CD3CE /* GTWorktree.m */; };
4DC55AE51AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
4DC55AE61AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; };
4DC55AE81AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; };
4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
4DFA918F207D0B87003CD3CE /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8D63865207ACCAA00D1FD32 /* Nimble.framework */; };
4DFFB15B183AA8D600D1565E /* GTRepository+RemoteOperations.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DFFB159183AA8D600D1565E /* GTRepository+RemoteOperations.h */; settings = {ATTRIBUTES = (Public, ); }; };
4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DFFB15A183AA8D600D1565E /* GTRepository+RemoteOperations.m */; };
@@ -491,8 +502,14 @@
4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCredential.h; sourceTree = "<group>"; };
4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCredential.m; sourceTree = "<group>"; };
4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTCredential+Private.h"; sourceTree = "<group>"; };
4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTWorktreeSpec.m; sourceTree = "<group>"; };
4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+WorktreeSpec.m"; sourceTree = "<group>"; };
4D9BCD23206D84AD003CD3CE /* libgit2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgit2.a; path = External/build/lib/libgit2.a; sourceTree = "<group>"; };
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRemoteSpec.m; sourceTree = "<group>"; };
4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Worktree.h"; sourceTree = "<group>"; };
4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+Worktree.m"; sourceTree = "<group>"; };
4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTWorktree.h; sourceTree = "<group>"; };
4DC372101F27D6D3003CD3CE /* GTWorktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTWorktree.m; sourceTree = "<group>"; };
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCheckoutOptions.h; sourceTree = "<group>"; };
4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCheckoutOptions.m; sourceTree = "<group>"; };
4DE864341794A37E00371A65 /* GTRepository+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Private.h"; sourceTree = "<group>"; };
@@ -846,6 +863,7 @@
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */,
F8EFA0361B405020000FF7D0 /* GTRepository+PullSpec.m */,
30A269AC17B4878C000FE64E /* GTRepository+StatusSpec.m */,
4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */,
88E353051982EA6B0051001F /* GTRepositoryAttributesSpec.m */,
4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */,
88234B2518F2FE260039972E /* GTRepositoryResetSpec.m */,
@@ -857,6 +875,7 @@
30B1E7FF1703871900D0814D /* GTTimeAdditionsSpec.m */,
5BE612921745EEBC00266D8C /* GTTreeBuilderSpec.m */,
88328127173D8A64006D7DCF /* GTTreeSpec.m */,
4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */,
F879D82F1B4B77F4002D5C07 /* Libgit2FeaturesSpec.m */,
307623AA17C6C8BD00E2CDF1 /* NSArray+StringArraySpec.m */,
D01EFD9F195DEF2200838D24 /* NSDataGitSpec.m */,
@@ -907,6 +926,8 @@
88B2131B1B20E785005CF2C5 /* GTRepository+References.m */,
23F39FAB1C86DB1C00849F3C /* GTRepository+Merging.h */,
23F39FAC1C86DB1C00849F3C /* GTRepository+Merging.m */,
4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */,
4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */,
BDD8AE6D13131B8800CB5D40 /* GTEnumerator.h */,
BDD8AE6E13131B8800CB5D40 /* GTEnumerator.m */,
BD6C22A71314625800992935 /* GTObject.h */,
@@ -976,6 +997,8 @@
6EEB51A0199D62B9001D72C0 /* GTFetchHeadEntry.m */,
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */,
4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */,
4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */,
4DC372101F27D6D3003CD3CE /* GTWorktree.m */,
);
path = ObjectiveGit;
sourceTree = "<group>";
@@ -1083,6 +1106,7 @@
BDD627991318391200DE34D1 /* GTBlob.h in Headers */,
886E622A18AEBF75000611A0 /* GTFilterSource.h in Headers */,
BDD62924131C03D600DE34D1 /* GTTag.h in Headers */,
4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */,
88BC0E5018EF4F3600C7D0E6 /* GTRepository+Reset.h in Headers */,
BDFAF9C3131C1845000508BC /* GTIndex.h in Headers */,
BDFAF9C9131C1868000508BC /* GTIndexEntry.h in Headers */,
@@ -1110,6 +1134,7 @@
55C8057E13875C1B004DCB0F /* NSString+Git.h in Headers */,
30A3D6541667F11C00C49A39 /* GTDiff.h in Headers */,
3011D86B1668E48500CE3409 /* GTDiffFile.h in Headers */,
4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */,
3011D8711668E78500CE3409 /* GTDiffHunk.h in Headers */,
880EE66118AE700500B82455 /* GTFilter.h in Headers */,
30FDC07F16835A8100654BF0 /* GTDiffLine.h in Headers */,
@@ -1145,6 +1170,7 @@
D01B6F4D19F82F8700D411BC /* GTRemote.h in Headers */,
D01B6F1519F82F7B00D411BC /* NSData+Git.h in Headers */,
D01B6F6119F82FA600D411BC /* GTFilterSource.h in Headers */,
4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */,
D0E0171519F9AD820019930C /* ObjectiveGit.h in Headers */,
88B2131D1B20E785005CF2C5 /* GTRepository+References.h in Headers */,
D01B6F4919F82F8700D411BC /* GTOdbObject.h in Headers */,
@@ -1447,6 +1473,7 @@
D0F4E28A17C7F24200BBDE30 /* NSErrorGitSpec.m in Sources */,
F879D8311B4B788F002D5C07 /* Libgit2FeaturesSpec.m in Sources */,
88328128173D8A64006D7DCF /* GTTreeSpec.m in Sources */,
4D962C402126243A003CD3CE /* GTWorktreeSpec.m in Sources */,
88E353061982EA6B0051001F /* GTRepositoryAttributesSpec.m in Sources */,
88234B2618F2FE260039972E /* GTRepositoryResetSpec.m in Sources */,
5BE612931745EEBC00266D8C /* GTTreeBuilderSpec.m in Sources */,
@@ -1457,6 +1484,7 @@
D040AF70177B9779001AD9EB /* GTOIDSpec.m in Sources */,
D040AF78177B9A9E001AD9EB /* GTSignatureSpec.m in Sources */,
4DBA4A3217DA73CE006CD5F5 /* GTRemoteSpec.m in Sources */,
4D962C43212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */,
4D123240178E009E0048F785 /* GTRepositoryCommittingSpec.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1475,6 +1503,7 @@
30DCBA6517B45A78009B0EBD /* GTRepository+Status.m in Sources */,
BD6C235413146E6A00992935 /* GTObject.m in Sources */,
4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */,
4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */,
BD6C254613148DD300992935 /* GTSignature.m in Sources */,
BD6B0412131496B8001909D0 /* GTTree.m in Sources */,
BD6B0418131496CC001909D0 /* GTTreeEntry.m in Sources */,
@@ -1496,6 +1525,7 @@
88EB7E4E14AEBA600046FEA4 /* GTConfiguration.m in Sources */,
883CD6AC1600EBC600F57354 /* GTRemote.m in Sources */,
30DCBA7317B4791A009B0EBD /* NSArray+StringArray.m in Sources */,
4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */,
4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */,
88F05AC61601209A00B7AD1D /* ObjectiveGit.m in Sources */,
30A3D6561667F11C00C49A39 /* GTDiff.m in Sources */,
@@ -1540,11 +1570,13 @@
D01B6F3819F82F8700D411BC /* GTTree.m in Sources */,
D01B6F6C19F82FB300D411BC /* GTDiff.m in Sources */,
884C8A3A19FF4B890017E98D /* EXTScope.m in Sources */,
4D22E16B2127922F003CD3CE /* GTWorktree.m in Sources */,
D01B6F4E19F82F8700D411BC /* GTRemote.m in Sources */,
D01B6F3019F82F8700D411BC /* GTObject.m in Sources */,
F8D1BDF11B31FE7C00CDEC90 /* GTRepository+Pull.m in Sources */,
D01B6F4619F82F8700D411BC /* GTBranch.m in Sources */,
D01B6F3A19F82F8700D411BC /* GTTreeEntry.m in Sources */,
4D22E16C21279314003CD3CE /* GTRepository+Worktree.m in Sources */,
D01B6F2419F82F8700D411BC /* GTRepository+Reset.m in Sources */,
D01B6F5219F82FA600D411BC /* GTBlame.m in Sources */,
D01B6F5A19F82FA600D411BC /* GTOID.m in Sources */,
@@ -1590,6 +1622,7 @@
buildActionMask = 2147483647;
files = (
F8D007931B4FA03B009A8DAF /* GTObjectSpec.m in Sources */,
4D962C412126243A003CD3CE /* GTWorktreeSpec.m in Sources */,
F8D0078D1B4FA03B009A8DAF /* GTBranchSpec.m in Sources */,
F8D007761B4F7D10009A8DAF /* GTTimeAdditionsSpec.m in Sources */,
F8D007921B4FA03B009A8DAF /* GTIndexSpec.m in Sources */,
@@ -1622,6 +1655,7 @@
F879D8441B4B80C7002D5C07 /* Libgit2FeaturesSpec.m in Sources */,
F8D0078C1B4FA03B009A8DAF /* GTBlobSpec.m in Sources */,
F8D007991B4FA03B009A8DAF /* GTRepositorySpec.m in Sources */,
4D962C44212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */,
F8D0079F1B4FA03B009A8DAF /* GTObjectDatabaseSpec.m in Sources */,
F8D0079E1B4FA03B009A8DAF /* GTTreeSpec.m in Sources */,
F8D007A01B4FA03B009A8DAF /* GTRepository+StatusSpec.m in Sources */,
124 changes: 124 additions & 0 deletions ObjectiveGitTests/GTRepository+WorktreeSpec.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// GTRepository+WorktreeSpec.m
// ObjectiveGitFramework
//
// Created by Etienne Samson on 2018-08-16.
// Copyright (c) 2018 GitHub, Inc. All rights reserved.
//

@import ObjectiveGit;
@import Nimble;
@import Quick;

#import "QuickSpec+GTFixtures.h"

QuickSpecBegin(GTRepositoryWorktreeSpec)

__block GTRepository *repo;

__block GTWorktree *worktree;
beforeEach(^{
repo = self.bareFixtureRepository;
expect(repo).notTo(beNil());

NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];

NSError *error = nil;
worktree = [GTWorktree addWorktreeWithName:@"test" URL:worktreeURL forRepository:repo options:nil error:&error];
expect(worktree).notTo(beNil());
expect(error).to(beNil());
});

describe(@"-isWorktree", ^{
expect(repo.isWorktree).to(beFalse());
});

describe(@"-worktreeNamesWithError:", ^{
it(@"returns the list of worktrees", ^{
NSError *error = nil;
NSArray *worktreeNames = [repo worktreeNamesWithError:&error];
expect(worktreeNames).to(contain(@"test"));
});
});

describe(@"-lookupWorktreeWithName:", ^{
it(@"returns an existing worktree", ^{
NSError *error = nil;

GTWorktree *worktree2 = [repo lookupWorktreeWithName:@"test" error:&error];
expect(worktree2).notTo(beNil());
expect(error).to(beNil());
});

it(@"fails on non-existent worktrees", ^{
NSError *error = nil;

GTWorktree *worktree2 = [repo lookupWorktreeWithName:@"blob" error:&error];
expect(worktree2).to(beNil());
expect(error).notTo(beNil());
expect(error.code).to(equal(-1));
expect(error.localizedDescription).to(equal(@"Failed to lookup worktree"));
});
});

describe(@"+openWorktree", ^{
it(@"won't return a worktree from a real repository", ^{
NSError *error = nil;

GTWorktree *repoWorktree = [repo openWorktree:&error];
expect(repoWorktree).to(beNil());
expect(error).notTo(beNil());
expect(error.code).to(equal(-1));
expect(error.localizedDescription).to(equal(@"Failed to open worktree"));
});

it(@"can return a worktree from a worktree's repository", ^{
NSError *error = nil;

GTRepository *repoWt = [GTRepository repositoryWithWorktree:worktree error:&error];
expect(repoWt).notTo(beNil());
expect(repoWt.isWorktree).to(beTrue());

GTWorktree *worktreeRepoWt = [repoWt openWorktree:&error];
expect(worktreeRepoWt).notTo(beNil());
expect(error).to(beNil());

});
});

describe(@"+repositoryWithWorktree:", ^{
it(@"can open a repository from a worktree", ^{
NSError *error = nil;

GTRepository *repo2 = [GTRepository repositoryWithWorktree:worktree error:&error];
expect(repo2).notTo(beNil());
expect(error).to(beNil());

expect(repo.isWorktree).to(beFalse());
});
});

fdescribe(@"with a worktree repository", ^{
__block GTRepository *worktreeRepo;
beforeEach(^{
NSError *error = nil;

worktreeRepo = [GTRepository repositoryWithWorktree:worktree error:&error];
expect(worktreeRepo).notTo(beNil());
expect(error).to(beNil());
});

describe(@"-fileURL", ^{
it(@"returns an absolute url", ^{
NSURL *url = worktreeRepo.fileURL;
expect(url).notTo(beNil());
expect([url.path substringToIndex:1]).to(equal(@"/"));
});
});
});

afterEach(^{
[self tearDown];
});

QuickSpecEnd
149 changes: 149 additions & 0 deletions ObjectiveGitTests/GTWorktreeSpec.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//
// GTWorktreeSpec.m
// ObjectiveGitFramework
//
// Created by Etienne Samson on 2018-08-16.
// Copyright (c) 2018 GitHub, Inc. All rights reserved.
//

@import ObjectiveGit;
@import Nimble;
@import Quick;

#import "QuickSpec+GTFixtures.h"

QuickSpecBegin(GTWorktreeSpec)

__block GTRepository *repo;

beforeEach(^{
repo = self.bareFixtureRepository;
expect(repo).notTo(beNil());
});

describe(@"GTWorktree", ^{
describe(@"with no existing worktree", ^{
describe(@"+addWorktreeWithName:", ^{
it(@"can add a worktree to a repository", ^{
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];

NSError *error = nil;
GTWorktree *worktree = [GTWorktree addWorktreeWithName:@"test" URL:worktreeURL forRepository:repo options:nil error:&error];
expect(worktree).notTo(beNil());
expect(error).to(beNil());

BOOL locked;
NSString *reason;
BOOL success = [worktree isLocked:&locked reason:&reason error:&error];
expect(success).to(beTrue());
expect(locked).to(beFalse());
expect(reason).to(beNil());
expect(error).to(beNil());
});

it(@"can add a worktree to a repository, keeping it locked", ^{
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];

NSError *error = nil;
GTWorktree *worktree = [GTWorktree addWorktreeWithName:@"test"
URL:worktreeURL
forRepository:repo
options:@{
GTWorktreeAddOptionsLocked: @(YES),
}
error:&error];
expect(worktree).notTo(beNil());
expect(error).to(beNil());

BOOL locked;
NSString *reason;
BOOL success = [worktree isLocked:&locked reason:&reason error:&error];
expect(success).to(beTrue());
expect(locked).to(beTrue());
expect(reason).to(beNil());
expect(error).to(beNil());
});
});
});

describe(@"with an existing worktree", ^{
__block GTWorktree *worktree;
beforeEach(^{
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];

NSError *error = nil;
worktree = [GTWorktree addWorktreeWithName:@"test" URL:worktreeURL forRepository:repo options:nil error:&error];
expect(worktree).notTo(beNil());
expect(error).to(beNil());
});

describe(@"-lockWithReason:", ^{
afterEach(^{
[worktree unlock:NULL error:NULL];
});

it(@"can lock with no reason", ^{
NSError *error = nil;

BOOL success = [worktree lockWithReason:nil error:&error];
expect(success).to(beTrue());
expect(error).to(beNil());

BOOL isLocked;
NSString *reason;
success = [worktree isLocked:&isLocked reason:&reason error:&error];
expect(success).to(beTrue());
expect(isLocked).to(beTrue());
expect(reason).to(beNil());
expect(error).to(beNil());
});

it(@"can lock with a reason", ^{
NSError *error = nil;

BOOL success = [worktree lockWithReason:@"a bad reason" error:&error];
expect(success).to(beTrue());
expect(error).to(beNil());

BOOL isLocked;
NSString *reason;
success = [worktree isLocked:&isLocked reason:&reason error:&error];
expect(success).to(beTrue());
expect(isLocked).to(beTrue());
expect(reason).to(equal(@"a bad reason"));
expect(error).to(beNil());
});
});

describe(@"-unlock:", ^{
it(@"knows about non-locked worktrees", ^{
NSError *error = nil;

BOOL wasLocked = NO;
BOOL success = [worktree unlock:&wasLocked error:&error];
expect(success).to(beTrue());
expect(wasLocked).to(beTrue()); // https://github.com/libgit2/libgit2/pull/4769
expect(error).to(beNil());
});

it(@"can unlock locked worktrees", ^{
NSError *error = nil;

BOOL success = [worktree lockWithReason:NULL error:NULL];
expect(success).to(beTrue());

BOOL wasLocked = NO;
success = [worktree unlock:&wasLocked error:&error];
expect(success).to(beTrue());
expect(wasLocked).to(beTrue());
expect(error).to(beNil());
});
});
});
});

afterEach(^{
[self tearDown];
});

QuickSpecEnd