Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a5bd7b8

Browse files
committedAug 16, 2018
Add worktree support via GTWorktree
1 parent 167d13f commit a5bd7b8

File tree

8 files changed

+629
-0
lines changed

8 files changed

+629
-0
lines changed
 

‎ObjectiveGit/GTRepository+Worktree.h

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// GTRepository+GTRepository_Worktree.h
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Etienne on 25/07/2017.
6+
// Copyright © 2017 GitHub, Inc. All rights reserved.
7+
//
8+
9+
#import <ObjectiveGit/ObjectiveGit.h>
10+
11+
@class GTWorktree;
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
@interface GTRepository (Worktree)
16+
17+
/// Is this the worktree of another repository ?
18+
@property (nonatomic, readonly, getter = isWorktree) BOOL worktree;
19+
20+
/// The URL for the underlying repository's git directory.
21+
/// Returns the same as -gitDirectoryURL if this is not a worktree.
22+
@property (nonatomic, readonly, strong) NSURL *commonGitDirectoryURL;
23+
24+
+ (instancetype _Nullable)repositoryWithWorktree:(GTWorktree *)worktree error:(NSError **)error;
25+
26+
- (instancetype _Nullable)initWithWorktree:(GTWorktree *)worktree error:(NSError **)error;
27+
28+
- (GTReference * _Nullable)HEADReferenceInWorktreeWithName:(NSString *)name error:(NSError **)error;
29+
30+
- (BOOL)isHEADDetached:(BOOL *)detached inWorktreeWithName:(NSString *)name error:(NSError **)error;
31+
32+
- (BOOL)setWorkingDirectoryURL:(NSURL *)URL updateGitLink:(BOOL)update error:(NSError **)error;
33+
34+
- (NSArray <NSString *> * _Nullable)worktreeNamesWithError:(NSError **)error;
35+
36+
- (GTWorktree * _Nullable)lookupWorktreeWithName:(NSString *)name error:(NSError **)error;
37+
38+
- (GTWorktree * _Nullable)openWorktree:(NSError **)error;
39+
40+
@end
41+
42+
NS_ASSUME_NONNULL_END

‎ObjectiveGit/GTRepository+Worktree.m

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// GTRepository+Worktree.m
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Etienne on 25/07/2017.
6+
// Copyright © 2017 GitHub, Inc. All rights reserved.
7+
//
8+
9+
#import "GTRepository+Worktree.h"
10+
11+
@implementation GTRepository (Worktree)
12+
13+
+ (instancetype)repositoryWithWorktree:(GTWorktree *)worktree error:(NSError **)error {
14+
return [[self alloc] initWithWorktree:worktree error:error];
15+
}
16+
17+
- (instancetype)initWithWorktree:(GTWorktree *)worktree error:(NSError **)error {
18+
NSParameterAssert(worktree != nil);
19+
20+
git_repository *repo;
21+
int gitError = git_repository_open_from_worktree(&repo, worktree.git_worktree);
22+
if (gitError != GIT_OK) {
23+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to open worktree"];
24+
return nil;
25+
}
26+
return [self initWithGitRepository:repo];
27+
}
28+
29+
- (BOOL)isWorktree {
30+
return (BOOL)git_repository_is_worktree(self.git_repository);
31+
}
32+
33+
- (NSURL *)commonGitDirectoryURL {
34+
return [NSURL fileURLWithPath:@(git_repository_commondir(self.git_repository)) isDirectory:YES];
35+
}
36+
37+
- (GTReference *)HEADReferenceInWorktreeWithName:(NSString *)name error:(NSError **)error {
38+
NSParameterAssert(name != nil);
39+
40+
git_reference *ref;
41+
int gitError = git_repository_head_for_worktree(&ref, self.git_repository, name.UTF8String);
42+
if (gitError != GIT_OK) {
43+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to resolve HEAD in worktree"];
44+
return nil;
45+
}
46+
47+
return [[GTReference alloc] initWithGitReference:ref repository:self];
48+
}
49+
50+
- (BOOL)isHEADDetached:(BOOL *)detached inWorktreeWithName:(NSString *)name error:(NSError **)error {
51+
NSParameterAssert(detached != nil);
52+
NSParameterAssert(name != nil);
53+
54+
int gitError = git_repository_head_detached_for_worktree(self.git_repository, name.UTF8String);
55+
if (gitError < 0) {
56+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to resolve HEAD in worktree"];
57+
return NO;
58+
}
59+
60+
*detached = (gitError == 1);
61+
62+
return YES;
63+
}
64+
65+
- (BOOL)setWorkingDirectoryURL:(NSURL *)URL updateGitLink:(BOOL)update error:(NSError **)error {
66+
NSParameterAssert(URL != nil);
67+
68+
int gitError = git_repository_set_workdir(self.git_repository, URL.fileSystemRepresentation, update);
69+
if (gitError != GIT_OK) {
70+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to set workdir"];
71+
return NO;
72+
}
73+
74+
return YES;
75+
}
76+
77+
- (NSArray<NSString *> *)worktreeNamesWithError:(NSError **)error {
78+
git_strarray names;
79+
int gitError = git_worktree_list(&names, self.git_repository);
80+
if (gitError != GIT_OK) {
81+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to load worktree names"];
82+
return nil;
83+
}
84+
85+
return [NSArray git_arrayWithStrarray:names];
86+
}
87+
88+
- (GTWorktree *)lookupWorktreeWithName:(NSString *)name error:(NSError **)error {
89+
NSParameterAssert(name != nil);
90+
91+
git_worktree *worktree;
92+
int gitError = git_worktree_lookup(&worktree, self.git_repository, name.UTF8String);
93+
if (gitError != GIT_OK) {
94+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to lookup worktree"];
95+
return nil;
96+
}
97+
98+
return [[GTWorktree alloc] initWithGitWorktree:worktree];
99+
}
100+
101+
- (GTWorktree *)openWorktree:(NSError **)error {
102+
git_worktree *worktree;
103+
int gitError = git_worktree_open_from_repository(&worktree, self.git_repository);
104+
if (gitError != GIT_OK) {
105+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to open worktree"];
106+
return nil;
107+
}
108+
109+
return [[GTWorktree alloc] initWithGitWorktree:worktree];
110+
}
111+
112+
@end

‎ObjectiveGit/GTWorktree.h

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// GTWorktree.h
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Etienne on 25/07/2017.
6+
// Copyright © 2017 GitHub, Inc. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
#import "GTRepository.h"
12+
13+
#import "git2/worktree.h"
14+
15+
NS_ASSUME_NONNULL_BEGIN
16+
17+
/// Add a worktree and keep it locked
18+
/// A boolean, defaults to NO.
19+
extern NSString *GTWorktreeAddOptionsLocked;
20+
21+
@interface GTWorktree : NSObject
22+
23+
/// Add a new worktree to a repository.
24+
///
25+
/// @param name The name of the worktree.
26+
/// @param worktreeURL The location of the worktree.
27+
/// @param repository The repository the worktree should be added to.
28+
/// @param options The options to use when adding the worktree.
29+
///
30+
/// @return the newly created worktree object.
31+
+ (instancetype _Nullable)addWorktreeWithName:(NSString *)name URL:(NSURL *)worktreeURL forRepository:(GTRepository *)repository options:(NSDictionary * _Nullable)options error:(NSError **)error;
32+
33+
/// Initialize a worktree from a git_worktree.
34+
- (instancetype _Nullable)initWithGitWorktree:(git_worktree *)worktree;
35+
36+
/// The underlying `git_worktree` object.
37+
- (git_worktree *)git_worktree __attribute__((objc_returns_inner_pointer));
38+
39+
/// Check the worktree validity
40+
///
41+
/// @param error An explanation if the worktree is not valid. nil otherwise
42+
///
43+
/// @return YES if the worktree is valid, NO otherwise (and error will be set).
44+
- (BOOL)isValid:(NSError **)error;
45+
46+
/// Lock the worktree.
47+
///
48+
/// This will prevent the worktree from being prunable.
49+
///
50+
/// @param reason An optional reason for the lock.
51+
/// @param error The error if the worktree couldn't be locked.
52+
///
53+
/// @return YES if the lock was successful, NO otherwise (and error will be set).
54+
- (BOOL)lockWithReason:(NSString * _Nullable)reason error:(NSError **)error;
55+
56+
/// Unlock a worktree.
57+
///
58+
/// @param wasLocked On return, NO if the worktree wasn't locked, YES otherwise.
59+
/// @param error The error if the worktree couldn't be unlocked.
60+
///
61+
/// @return YES if the unlock succeeded, NO otherwise (and error will be set).
62+
- (BOOL)unlock:(BOOL *)wasLocked error:(NSError **)error;
63+
64+
/// Check a worktree's lock state.
65+
///
66+
/// @param locked On return, YES if the worktree is locked, NO otherwise.
67+
/// @param reason On return, the lock reason, if the worktree is locked. nil otherwise.
68+
/// @param error The error if the lock state couldn't be determined.
69+
///
70+
/// @return YES if the check succeeded, NO otherwise (and error will be set).
71+
- (BOOL)isLocked:(BOOL *)locked reason:(NSString ** _Nullable)reason error:(NSError **)error;
72+
73+
@end
74+
75+
NS_ASSUME_NONNULL_END

‎ObjectiveGit/GTWorktree.m

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//
2+
// GTWorktree.m
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Etienne on 25/07/2017.
6+
// Copyright © 2017 GitHub, Inc. All rights reserved.
7+
//
8+
9+
#import "NSError+Git.h"
10+
#import "GTWorktree.h"
11+
#import "NSData+Git.h"
12+
13+
#import "git2/errors.h"
14+
#import "git2/buffer.h"
15+
16+
NSString *GTWorktreeAddOptionsLocked = @"GTWorktreeAddOptionsLocked";
17+
18+
@interface GTWorktree ()
19+
@property (nonatomic, assign, readonly) git_worktree *git_worktree;
20+
@end
21+
22+
@implementation GTWorktree
23+
24+
+ (instancetype)addWorktreeWithName:(NSString *)name URL:(NSURL *)worktreeURL forRepository:(GTRepository *)repository options:(NSDictionary *)options error:(NSError **)error {
25+
git_worktree *worktree;
26+
git_worktree_add_options git_options = GIT_WORKTREE_ADD_OPTIONS_INIT;
27+
28+
if (options) {
29+
git_options.lock = [options[GTWorktreeAddOptionsLocked] boolValue];
30+
}
31+
32+
int gitError = git_worktree_add(&worktree, repository.git_repository, name.UTF8String, worktreeURL.fileSystemRepresentation, &git_options);
33+
if (gitError != GIT_OK) {
34+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to add worktree"];
35+
return nil;
36+
}
37+
38+
return [[self alloc] initWithGitWorktree:worktree];
39+
}
40+
41+
- (instancetype)initWithGitWorktree:(git_worktree *)worktree {
42+
self = [super init];
43+
if (!self) return nil;
44+
45+
_git_worktree = worktree;
46+
47+
return self;
48+
}
49+
50+
- (void)dealloc {
51+
git_worktree_free(_git_worktree);
52+
}
53+
54+
- (BOOL)isValid:(NSError **)error {
55+
int gitError = git_worktree_validate(self.git_worktree);
56+
if (gitError < 0) {
57+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to validate worktree"];
58+
return NO;
59+
}
60+
61+
return YES;
62+
}
63+
64+
- (BOOL)lockWithReason:(NSString *)reason error:(NSError **)error {
65+
int gitError = git_worktree_lock(self.git_worktree, reason.UTF8String);
66+
if (gitError != GIT_OK) {
67+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to lock worktree"];
68+
return NO;
69+
}
70+
71+
return YES;
72+
}
73+
74+
- (BOOL)unlock:(BOOL *)wasLocked error:(NSError **)error {
75+
int gitError = git_worktree_unlock(self.git_worktree);
76+
if (gitError < 0) {
77+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to unlock worktree"];
78+
return NO;
79+
}
80+
81+
if (wasLocked) {
82+
// unlock returns 1 if there was no lock.
83+
*wasLocked = (gitError == 0);
84+
}
85+
86+
return YES;
87+
}
88+
89+
- (BOOL)isLocked:(BOOL *)locked reason:(NSString **)reason error:(NSError **)error {
90+
git_buf reasonBuf = GIT_BUF_INIT_CONST("", 0);
91+
int gitError = git_worktree_is_locked(&reasonBuf, self.git_worktree);
92+
if (gitError < 0) {
93+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to check lock state of worktree"];
94+
return NO;
95+
}
96+
97+
if (locked) *locked = (gitError > 0);
98+
if (reason) {
99+
if (gitError > 0 && reasonBuf.size > 0) {
100+
*reason = [[NSString alloc] initWithData:[NSData git_dataWithBuffer:&reasonBuf]
101+
encoding:NSUTF8StringEncoding];
102+
} else {
103+
*reason = nil;
104+
}
105+
}
106+
107+
return YES;
108+
}
109+
110+
@end

‎ObjectiveGit/ObjectiveGit.h

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ FOUNDATION_EXPORT const unsigned char ObjectiveGitVersionString[];
4242
#import <ObjectiveGit/GTRepository+Reset.h>
4343
#import <ObjectiveGit/GTRepository+Pull.h>
4444
#import <ObjectiveGit/GTRepository+Merging.h>
45+
#import <ObjectiveGit/GTRepository+Worktree.h>
46+
#import <ObjectiveGit/GTWorktree.h>
4547
#import <ObjectiveGit/GTEnumerator.h>
4648
#import <ObjectiveGit/GTCommit.h>
4749
#import <ObjectiveGit/GTCredential.h>

‎ObjectiveGitFramework.xcodeproj/project.pbxproj

+30
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,22 @@
9191
4D1C40D8182C006D00BE2960 /* GTBlobSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D1C40D7182C006D00BE2960 /* GTBlobSpec.m */; };
9292
4D79C0EE17DF9F4D00997DE4 /* GTCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */; settings = {ATTRIBUTES = (Public, ); }; };
9393
4D79C0EF17DF9F4D00997DE4 /* GTCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */; };
94+
4D962C402126243A003CD3CE /* GTWorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */; };
95+
4D962C412126243A003CD3CE /* GTWorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */; };
96+
4D962C43212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */; };
97+
4D962C44212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */; };
9498
4D9BCD24206D84AD003CD3CE /* libgit2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D9BCD23206D84AD003CD3CE /* libgit2.a */; };
9599
4D9BCD25206D84B2003CD3CE /* libgit2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D9BCD23206D84AD003CD3CE /* libgit2.a */; };
96100
4DBA4A3217DA73CE006CD5F5 /* GTRemoteSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */; };
101+
4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
102+
4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */; };
103+
4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
104+
4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC372101F27D6D3003CD3CE /* GTWorktree.m */; };
97105
4DC55AE51AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
98106
4DC55AE61AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
99107
4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; };
100108
4DC55AE81AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; };
109+
4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
101110
4DFA918F207D0B87003CD3CE /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8D63865207ACCAA00D1FD32 /* Nimble.framework */; };
102111
4DFFB15B183AA8D600D1565E /* GTRepository+RemoteOperations.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DFFB159183AA8D600D1565E /* GTRepository+RemoteOperations.h */; settings = {ATTRIBUTES = (Public, ); }; };
103112
4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DFFB15A183AA8D600D1565E /* GTRepository+RemoteOperations.m */; };
@@ -492,8 +501,14 @@
492501
4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCredential.h; sourceTree = "<group>"; };
493502
4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCredential.m; sourceTree = "<group>"; };
494503
4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTCredential+Private.h"; sourceTree = "<group>"; };
504+
4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTWorktreeSpec.m; sourceTree = "<group>"; };
505+
4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+WorktreeSpec.m"; sourceTree = "<group>"; };
495506
4D9BCD23206D84AD003CD3CE /* libgit2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgit2.a; path = External/build/lib/libgit2.a; sourceTree = "<group>"; };
496507
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRemoteSpec.m; sourceTree = "<group>"; };
508+
4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Worktree.h"; sourceTree = "<group>"; };
509+
4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+Worktree.m"; sourceTree = "<group>"; };
510+
4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTWorktree.h; sourceTree = "<group>"; };
511+
4DC372101F27D6D3003CD3CE /* GTWorktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTWorktree.m; sourceTree = "<group>"; };
497512
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCheckoutOptions.h; sourceTree = "<group>"; };
498513
4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCheckoutOptions.m; sourceTree = "<group>"; };
499514
4DE864341794A37E00371A65 /* GTRepository+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Private.h"; sourceTree = "<group>"; };
@@ -847,6 +862,7 @@
847862
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */,
848863
F8EFA0361B405020000FF7D0 /* GTRepository+PullSpec.m */,
849864
30A269AC17B4878C000FE64E /* GTRepository+StatusSpec.m */,
865+
4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */,
850866
88E353051982EA6B0051001F /* GTRepositoryAttributesSpec.m */,
851867
4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */,
852868
88234B2518F2FE260039972E /* GTRepositoryResetSpec.m */,
@@ -858,6 +874,7 @@
858874
30B1E7FF1703871900D0814D /* GTTimeAdditionsSpec.m */,
859875
5BE612921745EEBC00266D8C /* GTTreeBuilderSpec.m */,
860876
88328127173D8A64006D7DCF /* GTTreeSpec.m */,
877+
4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */,
861878
F879D82F1B4B77F4002D5C07 /* Libgit2FeaturesSpec.m */,
862879
307623AA17C6C8BD00E2CDF1 /* NSArray+StringArraySpec.m */,
863880
D01EFD9F195DEF2200838D24 /* NSDataGitSpec.m */,
@@ -908,6 +925,8 @@
908925
88B2131B1B20E785005CF2C5 /* GTRepository+References.m */,
909926
23F39FAB1C86DB1C00849F3C /* GTRepository+Merging.h */,
910927
23F39FAC1C86DB1C00849F3C /* GTRepository+Merging.m */,
928+
4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */,
929+
4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */,
911930
BDD8AE6D13131B8800CB5D40 /* GTEnumerator.h */,
912931
BDD8AE6E13131B8800CB5D40 /* GTEnumerator.m */,
913932
BD6C22A71314625800992935 /* GTObject.h */,
@@ -977,6 +996,8 @@
977996
6EEB51A0199D62B9001D72C0 /* GTFetchHeadEntry.m */,
978997
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */,
979998
4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */,
999+
4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */,
1000+
4DC372101F27D6D3003CD3CE /* GTWorktree.m */,
9801001
);
9811002
path = ObjectiveGit;
9821003
sourceTree = "<group>";
@@ -1084,6 +1105,7 @@
10841105
BDD627991318391200DE34D1 /* GTBlob.h in Headers */,
10851106
886E622A18AEBF75000611A0 /* GTFilterSource.h in Headers */,
10861107
BDD62924131C03D600DE34D1 /* GTTag.h in Headers */,
1108+
4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */,
10871109
88BC0E5018EF4F3600C7D0E6 /* GTRepository+Reset.h in Headers */,
10881110
BDFAF9C3131C1845000508BC /* GTIndex.h in Headers */,
10891111
BDFAF9C9131C1868000508BC /* GTIndexEntry.h in Headers */,
@@ -1111,6 +1133,7 @@
11111133
55C8057E13875C1B004DCB0F /* NSString+Git.h in Headers */,
11121134
30A3D6541667F11C00C49A39 /* GTDiff.h in Headers */,
11131135
3011D86B1668E48500CE3409 /* GTDiffFile.h in Headers */,
1136+
4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */,
11141137
3011D8711668E78500CE3409 /* GTDiffHunk.h in Headers */,
11151138
880EE66118AE700500B82455 /* GTFilter.h in Headers */,
11161139
30FDC07F16835A8100654BF0 /* GTDiffLine.h in Headers */,
@@ -1146,6 +1169,7 @@
11461169
D01B6F4D19F82F8700D411BC /* GTRemote.h in Headers */,
11471170
D01B6F1519F82F7B00D411BC /* NSData+Git.h in Headers */,
11481171
D01B6F6119F82FA600D411BC /* GTFilterSource.h in Headers */,
1172+
4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */,
11491173
D0E0171519F9AD820019930C /* ObjectiveGit.h in Headers */,
11501174
88B2131D1B20E785005CF2C5 /* GTRepository+References.h in Headers */,
11511175
D01B6F4919F82F8700D411BC /* GTOdbObject.h in Headers */,
@@ -1449,6 +1473,7 @@
14491473
D0F4E28A17C7F24200BBDE30 /* NSErrorGitSpec.m in Sources */,
14501474
F879D8311B4B788F002D5C07 /* Libgit2FeaturesSpec.m in Sources */,
14511475
88328128173D8A64006D7DCF /* GTTreeSpec.m in Sources */,
1476+
4D962C402126243A003CD3CE /* GTWorktreeSpec.m in Sources */,
14521477
88E353061982EA6B0051001F /* GTRepositoryAttributesSpec.m in Sources */,
14531478
88234B2618F2FE260039972E /* GTRepositoryResetSpec.m in Sources */,
14541479
5BE612931745EEBC00266D8C /* GTTreeBuilderSpec.m in Sources */,
@@ -1459,6 +1484,7 @@
14591484
D040AF70177B9779001AD9EB /* GTOIDSpec.m in Sources */,
14601485
D040AF78177B9A9E001AD9EB /* GTSignatureSpec.m in Sources */,
14611486
4DBA4A3217DA73CE006CD5F5 /* GTRemoteSpec.m in Sources */,
1487+
4D962C43212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */,
14621488
4D123240178E009E0048F785 /* GTRepositoryCommittingSpec.m in Sources */,
14631489
);
14641490
runOnlyForDeploymentPostprocessing = 0;
@@ -1477,6 +1503,7 @@
14771503
30DCBA6517B45A78009B0EBD /* GTRepository+Status.m in Sources */,
14781504
BD6C235413146E6A00992935 /* GTObject.m in Sources */,
14791505
4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */,
1506+
4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */,
14801507
BD6C254613148DD300992935 /* GTSignature.m in Sources */,
14811508
BD6B0412131496B8001909D0 /* GTTree.m in Sources */,
14821509
BD6B0418131496CC001909D0 /* GTTreeEntry.m in Sources */,
@@ -1498,6 +1525,7 @@
14981525
88EB7E4E14AEBA600046FEA4 /* GTConfiguration.m in Sources */,
14991526
883CD6AC1600EBC600F57354 /* GTRemote.m in Sources */,
15001527
30DCBA7317B4791A009B0EBD /* NSArray+StringArray.m in Sources */,
1528+
4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */,
15011529
4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */,
15021530
88F05AC61601209A00B7AD1D /* ObjectiveGit.m in Sources */,
15031531
30A3D6561667F11C00C49A39 /* GTDiff.m in Sources */,
@@ -1592,6 +1620,7 @@
15921620
buildActionMask = 2147483647;
15931621
files = (
15941622
F8D007931B4FA03B009A8DAF /* GTObjectSpec.m in Sources */,
1623+
4D962C412126243A003CD3CE /* GTWorktreeSpec.m in Sources */,
15951624
F8D0078D1B4FA03B009A8DAF /* GTBranchSpec.m in Sources */,
15961625
F8D007761B4F7D10009A8DAF /* GTTimeAdditionsSpec.m in Sources */,
15971626
F8D007921B4FA03B009A8DAF /* GTIndexSpec.m in Sources */,
@@ -1624,6 +1653,7 @@
16241653
F879D8441B4B80C7002D5C07 /* Libgit2FeaturesSpec.m in Sources */,
16251654
F8D0078C1B4FA03B009A8DAF /* GTBlobSpec.m in Sources */,
16261655
F8D007991B4FA03B009A8DAF /* GTRepositorySpec.m in Sources */,
1656+
4D962C44212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */,
16271657
F8D0079F1B4FA03B009A8DAF /* GTObjectDatabaseSpec.m in Sources */,
16281658
F8D0079E1B4FA03B009A8DAF /* GTTreeSpec.m in Sources */,
16291659
F8D007A01B4FA03B009A8DAF /* GTRepository+StatusSpec.m in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//
2+
// GTRepository+WorktreeSpec.m
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Etienne Samson on 2018-08-16.
6+
// Copyright (c) 2018 GitHub, Inc. All rights reserved.
7+
//
8+
9+
@import ObjectiveGit;
10+
@import Nimble;
11+
@import Quick;
12+
13+
#import "QuickSpec+GTFixtures.h"
14+
15+
QuickSpecBegin(GTRepositoryWorktreeSpec)
16+
17+
__block GTRepository *repo;
18+
19+
beforeEach(^{
20+
repo = self.bareFixtureRepository;
21+
expect(repo).notTo(beNil());
22+
});
23+
24+
describe(@"GTRepository", ^{
25+
__block GTWorktree *worktree;
26+
beforeEach(^{
27+
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];
28+
29+
NSError *error = nil;
30+
worktree = [GTWorktree addWorktreeWithName:@"test" URL:worktreeURL forRepository:repo options:nil error:&error];
31+
expect(worktree).notTo(beNil());
32+
expect(error).to(beNil());
33+
});
34+
35+
describe(@"-isWorktree", ^{
36+
expect(repo.isWorktree).to(beFalse());
37+
});
38+
39+
describe(@"-worktreeNamesWithError:", ^{
40+
it(@"returns the list of worktrees", ^{
41+
NSError *error = nil;
42+
NSArray *worktreeNames = [repo worktreeNamesWithError:&error];
43+
expect(worktreeNames).to(contain(@"test"));
44+
});
45+
});
46+
47+
describe(@"-lookupWorktreeWithName:", ^{
48+
it(@"returns an existing worktree", ^{
49+
NSError *error = nil;
50+
51+
GTWorktree *worktree2 = [repo lookupWorktreeWithName:@"test" error:&error];
52+
expect(worktree2).notTo(beNil());
53+
expect(error).to(beNil());
54+
});
55+
56+
it(@"fails on non-existent worktrees", ^{
57+
NSError *error = nil;
58+
59+
GTWorktree *worktree2 = [repo lookupWorktreeWithName:@"blob" error:&error];
60+
expect(worktree2).to(beNil());
61+
expect(error).notTo(beNil());
62+
expect(error.code).to(equal(-1));
63+
expect(error.localizedDescription).to(equal(@"Failed to lookup worktree"));
64+
});
65+
});
66+
67+
describe(@"+openWorktree", ^{
68+
it(@"won't return a worktree from a real repository", ^{
69+
NSError *error = nil;
70+
71+
GTWorktree *repoWorktree = [repo openWorktree:&error];
72+
expect(repoWorktree).to(beNil());
73+
expect(error).notTo(beNil());
74+
expect(error.code).to(equal(-1));
75+
expect(error.localizedDescription).to(equal(@"Failed to open worktree"));
76+
});
77+
78+
it(@"can return a worktree from a worktree's repository", ^{
79+
NSError *error = nil;
80+
81+
GTRepository *repoWt = [GTRepository repositoryWithWorktree:worktree error:&error];
82+
expect(repoWt).notTo(beNil());
83+
expect(repoWt.isWorktree).to(beTrue());
84+
85+
GTWorktree *worktreeRepoWt = [repoWt openWorktree:&error];
86+
expect(worktreeRepoWt).notTo(beNil());
87+
expect(error).to(beNil());
88+
89+
});
90+
});
91+
92+
describe(@"+repositoryWithWorktree:", ^{
93+
it(@"can open a repository from a worktree", ^{
94+
NSError *error = nil;
95+
96+
GTRepository *repo2 = [GTRepository repositoryWithWorktree:worktree error:&error];
97+
expect(repo2).notTo(beNil());
98+
expect(error).to(beNil());
99+
100+
expect(repo.isWorktree).to(beFalse());
101+
});
102+
});
103+
});
104+
105+
afterEach(^{
106+
[self tearDown];
107+
});
108+
109+
QuickSpecEnd

‎ObjectiveGitTests/GTWorktreeSpec.m

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//
2+
// GTWorktreeSpec.m
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Etienne Samson on 2018-08-16.
6+
// Copyright (c) 2018 GitHub, Inc. All rights reserved.
7+
//
8+
9+
@import ObjectiveGit;
10+
@import Nimble;
11+
@import Quick;
12+
13+
#import "QuickSpec+GTFixtures.h"
14+
15+
QuickSpecBegin(GTWorktreeSpec)
16+
17+
__block GTRepository *repo;
18+
19+
beforeEach(^{
20+
repo = self.bareFixtureRepository;
21+
expect(repo).notTo(beNil());
22+
});
23+
24+
describe(@"GTWorktree", ^{
25+
describe(@"with no existing worktree", ^{
26+
describe(@"+addWorktreeWithName:", ^{
27+
it(@"can add a worktree to a repository", ^{
28+
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];
29+
30+
NSError *error = nil;
31+
GTWorktree *worktree = [GTWorktree addWorktreeWithName:@"test" URL:worktreeURL forRepository:repo options:nil error:&error];
32+
expect(worktree).notTo(beNil());
33+
expect(error).to(beNil());
34+
35+
BOOL locked;
36+
NSString *reason;
37+
BOOL success = [worktree isLocked:&locked reason:&reason error:&error];
38+
expect(success).to(beTrue());
39+
expect(locked).to(beFalse());
40+
expect(reason).to(beNil());
41+
expect(error).to(beNil());
42+
});
43+
44+
it(@"can add a worktree to a repository, keeping it locked", ^{
45+
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];
46+
47+
NSError *error = nil;
48+
GTWorktree *worktree = [GTWorktree addWorktreeWithName:@"test"
49+
URL:worktreeURL
50+
forRepository:repo
51+
options:@{
52+
GTWorktreeAddOptionsLocked: @(YES),
53+
}
54+
error:&error];
55+
expect(worktree).notTo(beNil());
56+
expect(error).to(beNil());
57+
58+
BOOL locked;
59+
NSString *reason;
60+
BOOL success = [worktree isLocked:&locked reason:&reason error:&error];
61+
expect(success).to(beTrue());
62+
expect(locked).to(beTrue());
63+
expect(reason).to(beNil());
64+
expect(error).to(beNil());
65+
});
66+
});
67+
});
68+
69+
describe(@"with an existing worktree", ^{
70+
__block GTWorktree *worktree;
71+
beforeEach(^{
72+
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];
73+
74+
NSError *error = nil;
75+
worktree = [GTWorktree addWorktreeWithName:@"test" URL:worktreeURL forRepository:repo options:nil error:&error];
76+
expect(worktree).notTo(beNil());
77+
expect(error).to(beNil());
78+
});
79+
80+
describe(@"-lockWithReason:", ^{
81+
afterEach(^{
82+
[worktree unlock:NULL error:NULL];
83+
});
84+
85+
it(@"can lock with no reason", ^{
86+
NSError *error = nil;
87+
88+
BOOL success = [worktree lockWithReason:nil error:&error];
89+
expect(success).to(beTrue());
90+
expect(error).to(beNil());
91+
92+
BOOL isLocked;
93+
NSString *reason;
94+
success = [worktree isLocked:&isLocked reason:&reason error:&error];
95+
expect(success).to(beTrue());
96+
expect(isLocked).to(beTrue());
97+
expect(reason).to(beNil());
98+
expect(error).to(beNil());
99+
});
100+
101+
it(@"can lock with a reason", ^{
102+
NSError *error = nil;
103+
104+
BOOL success = [worktree lockWithReason:@"a bad reason" error:&error];
105+
expect(success).to(beTrue());
106+
expect(error).to(beNil());
107+
108+
BOOL isLocked;
109+
NSString *reason;
110+
success = [worktree isLocked:&isLocked reason:&reason error:&error];
111+
expect(success).to(beTrue());
112+
expect(isLocked).to(beTrue());
113+
expect(reason).to(equal(@"a bad reason"));
114+
expect(error).to(beNil());
115+
});
116+
});
117+
118+
describe(@"-unlock:", ^{
119+
it(@"knows about non-locked worktrees", ^{
120+
NSError *error = nil;
121+
122+
BOOL wasLocked = NO;
123+
BOOL success = [worktree unlock:&wasLocked error:&error];
124+
expect(success).to(beTrue());
125+
expect(wasLocked).to(beTrue()); // https://github.com/libgit2/libgit2/pull/4769
126+
expect(error).to(beNil());
127+
});
128+
129+
it(@"can unlock locked worktrees", ^{
130+
NSError *error = nil;
131+
132+
BOOL success = [worktree lockWithReason:NULL error:NULL];
133+
expect(success).to(beTrue());
134+
135+
BOOL wasLocked = NO;
136+
success = [worktree unlock:&wasLocked error:&error];
137+
expect(success).to(beTrue());
138+
expect(wasLocked).to(beTrue());
139+
expect(error).to(beNil());
140+
});
141+
});
142+
});
143+
});
144+
145+
afterEach(^{
146+
[self tearDown];
147+
});
148+
149+
QuickSpecEnd

0 commit comments

Comments
 (0)
Please sign in to comment.