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 1c3a46d

Browse files
committedNov 18, 2018
Add worktree support via GTWorktree
1 parent c8c7bd1 commit 1c3a46d

File tree

8 files changed

+654
-0
lines changed

8 files changed

+654
-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

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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+
const char *cPath = git_repository_commondir(self.git_repository);
35+
NSAssert(cPath, @"commondir is nil");
36+
37+
NSString *path = @(cPath);
38+
NSAssert(path, @"commondir is nil");
39+
return [NSURL fileURLWithPath:path isDirectory:YES];
40+
}
41+
42+
- (GTReference *)HEADReferenceInWorktreeWithName:(NSString *)name error:(NSError **)error {
43+
NSParameterAssert(name != nil);
44+
45+
git_reference *ref;
46+
int gitError = git_repository_head_for_worktree(&ref, self.git_repository, name.UTF8String);
47+
if (gitError != GIT_OK) {
48+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to resolve HEAD in worktree"];
49+
return nil;
50+
}
51+
52+
return [[GTReference alloc] initWithGitReference:ref repository:self];
53+
}
54+
55+
- (BOOL)isHEADDetached:(BOOL *)detached inWorktreeWithName:(NSString *)name error:(NSError **)error {
56+
NSParameterAssert(detached != nil);
57+
NSParameterAssert(name != nil);
58+
59+
int gitError = git_repository_head_detached_for_worktree(self.git_repository, name.UTF8String);
60+
if (gitError < 0) {
61+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to resolve HEAD in worktree"];
62+
return NO;
63+
}
64+
65+
*detached = (gitError == 1);
66+
67+
return YES;
68+
}
69+
70+
- (BOOL)setWorkingDirectoryURL:(NSURL *)URL updateGitLink:(BOOL)update error:(NSError **)error {
71+
NSParameterAssert(URL != nil);
72+
73+
int gitError = git_repository_set_workdir(self.git_repository, URL.fileSystemRepresentation, update);
74+
if (gitError != GIT_OK) {
75+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to set workdir"];
76+
return NO;
77+
}
78+
79+
return YES;
80+
}
81+
82+
- (NSArray<NSString *> *)worktreeNamesWithError:(NSError **)error {
83+
git_strarray names;
84+
int gitError = git_worktree_list(&names, self.git_repository);
85+
if (gitError != GIT_OK) {
86+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to load worktree names"];
87+
return nil;
88+
}
89+
90+
return [NSArray git_arrayWithStrarray:names];
91+
}
92+
93+
- (GTWorktree *)lookupWorktreeWithName:(NSString *)name error:(NSError **)error {
94+
NSParameterAssert(name != nil);
95+
96+
git_worktree *worktree;
97+
int gitError = git_worktree_lookup(&worktree, self.git_repository, name.UTF8String);
98+
if (gitError != GIT_OK) {
99+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to lookup worktree"];
100+
return nil;
101+
}
102+
103+
return [[GTWorktree alloc] initWithGitWorktree:worktree];
104+
}
105+
106+
- (GTWorktree *)openWorktree:(NSError **)error {
107+
git_worktree *worktree;
108+
int gitError = git_worktree_open_from_repository(&worktree, self.git_repository);
109+
if (gitError != GIT_OK) {
110+
if (error) *error = [NSError git_errorFor:gitError description:@"Failed to open worktree"];
111+
return nil;
112+
}
113+
114+
return [[GTWorktree alloc] initWithGitWorktree:worktree];
115+
}
116+
117+
@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 * _Nullable)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 * _Nullable)locked reason:(NSString * _Nullable __autoreleasing * _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

+3
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>
@@ -72,6 +74,7 @@ FOUNDATION_EXPORT const unsigned char ObjectiveGitVersionString[];
7274
#import <ObjectiveGit/GTFetchHeadEntry.h>
7375
#import <ObjectiveGit/GTNote.h>
7476
#import <ObjectiveGit/GTCheckoutOptions.h>
77+
#import <ObjectiveGit/GTWorktree.h>
7578

7679
#import <ObjectiveGit/GTObjectDatabase.h>
7780
#import <ObjectiveGit/GTOdbObject.h>

‎ObjectiveGitFramework.xcodeproj/project.pbxproj

+34
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,26 @@
8989
30FDC08116835A8100654BF0 /* GTDiffLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 30FDC07E16835A8100654BF0 /* GTDiffLine.m */; };
9090
4D123240178E009E0048F785 /* GTRepositoryCommittingSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */; };
9191
4D1C40D8182C006D00BE2960 /* GTBlobSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D1C40D7182C006D00BE2960 /* GTBlobSpec.m */; };
92+
4D22E16B2127922F003CD3CE /* GTWorktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC372101F27D6D3003CD3CE /* GTWorktree.m */; };
93+
4D22E16C21279314003CD3CE /* GTRepository+Worktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */; };
9294
4D79C0EE17DF9F4D00997DE4 /* GTCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */; settings = {ATTRIBUTES = (Public, ); }; };
9395
4D79C0EF17DF9F4D00997DE4 /* GTCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */; };
96+
4D962C402126243A003CD3CE /* GTWorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */; };
97+
4D962C412126243A003CD3CE /* GTWorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */; };
98+
4D962C43212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */; };
99+
4D962C44212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */; };
94100
4D9BCD24206D84AD003CD3CE /* libgit2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D9BCD23206D84AD003CD3CE /* libgit2.a */; };
95101
4D9BCD25206D84B2003CD3CE /* libgit2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D9BCD23206D84AD003CD3CE /* libgit2.a */; };
96102
4DBA4A3217DA73CE006CD5F5 /* GTRemoteSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */; };
103+
4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
104+
4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */; };
105+
4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
106+
4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC372101F27D6D3003CD3CE /* GTWorktree.m */; };
97107
4DC55AE51AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
98108
4DC55AE61AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
99109
4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; };
100110
4DC55AE81AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; };
111+
4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; };
101112
4DFA918F207D0B87003CD3CE /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8D63865207ACCAA00D1FD32 /* Nimble.framework */; };
102113
4DFFB15B183AA8D600D1565E /* GTRepository+RemoteOperations.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DFFB159183AA8D600D1565E /* GTRepository+RemoteOperations.h */; settings = {ATTRIBUTES = (Public, ); }; };
103114
4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DFFB15A183AA8D600D1565E /* GTRepository+RemoteOperations.m */; };
@@ -491,8 +502,14 @@
491502
4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCredential.h; sourceTree = "<group>"; };
492503
4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCredential.m; sourceTree = "<group>"; };
493504
4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTCredential+Private.h"; sourceTree = "<group>"; };
505+
4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTWorktreeSpec.m; sourceTree = "<group>"; };
506+
4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+WorktreeSpec.m"; sourceTree = "<group>"; };
494507
4D9BCD23206D84AD003CD3CE /* libgit2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgit2.a; path = External/build/lib/libgit2.a; sourceTree = "<group>"; };
495508
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRemoteSpec.m; sourceTree = "<group>"; };
509+
4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Worktree.h"; sourceTree = "<group>"; };
510+
4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+Worktree.m"; sourceTree = "<group>"; };
511+
4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTWorktree.h; sourceTree = "<group>"; };
512+
4DC372101F27D6D3003CD3CE /* GTWorktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTWorktree.m; sourceTree = "<group>"; };
496513
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCheckoutOptions.h; sourceTree = "<group>"; };
497514
4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCheckoutOptions.m; sourceTree = "<group>"; };
498515
4DE864341794A37E00371A65 /* GTRepository+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Private.h"; sourceTree = "<group>"; };
@@ -846,6 +863,7 @@
846863
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */,
847864
F8EFA0361B405020000FF7D0 /* GTRepository+PullSpec.m */,
848865
30A269AC17B4878C000FE64E /* GTRepository+StatusSpec.m */,
866+
4D962C42212631D3003CD3CE /* GTRepository+WorktreeSpec.m */,
849867
88E353051982EA6B0051001F /* GTRepositoryAttributesSpec.m */,
850868
4D12323F178E009E0048F785 /* GTRepositoryCommittingSpec.m */,
851869
88234B2518F2FE260039972E /* GTRepositoryResetSpec.m */,
@@ -857,6 +875,7 @@
857875
30B1E7FF1703871900D0814D /* GTTimeAdditionsSpec.m */,
858876
5BE612921745EEBC00266D8C /* GTTreeBuilderSpec.m */,
859877
88328127173D8A64006D7DCF /* GTTreeSpec.m */,
878+
4D962C3F21262439003CD3CE /* GTWorktreeSpec.m */,
860879
F879D82F1B4B77F4002D5C07 /* Libgit2FeaturesSpec.m */,
861880
307623AA17C6C8BD00E2CDF1 /* NSArray+StringArraySpec.m */,
862881
D01EFD9F195DEF2200838D24 /* NSDataGitSpec.m */,
@@ -907,6 +926,8 @@
907926
88B2131B1B20E785005CF2C5 /* GTRepository+References.m */,
908927
23F39FAB1C86DB1C00849F3C /* GTRepository+Merging.h */,
909928
23F39FAC1C86DB1C00849F3C /* GTRepository+Merging.m */,
929+
4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */,
930+
4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */,
910931
BDD8AE6D13131B8800CB5D40 /* GTEnumerator.h */,
911932
BDD8AE6E13131B8800CB5D40 /* GTEnumerator.m */,
912933
BD6C22A71314625800992935 /* GTObject.h */,
@@ -976,6 +997,8 @@
976997
6EEB51A0199D62B9001D72C0 /* GTFetchHeadEntry.m */,
977998
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */,
978999
4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */,
1000+
4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */,
1001+
4DC372101F27D6D3003CD3CE /* GTWorktree.m */,
9791002
);
9801003
path = ObjectiveGit;
9811004
sourceTree = "<group>";
@@ -1083,6 +1106,7 @@
10831106
BDD627991318391200DE34D1 /* GTBlob.h in Headers */,
10841107
886E622A18AEBF75000611A0 /* GTFilterSource.h in Headers */,
10851108
BDD62924131C03D600DE34D1 /* GTTag.h in Headers */,
1109+
4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */,
10861110
88BC0E5018EF4F3600C7D0E6 /* GTRepository+Reset.h in Headers */,
10871111
BDFAF9C3131C1845000508BC /* GTIndex.h in Headers */,
10881112
BDFAF9C9131C1868000508BC /* GTIndexEntry.h in Headers */,
@@ -1110,6 +1134,7 @@
11101134
55C8057E13875C1B004DCB0F /* NSString+Git.h in Headers */,
11111135
30A3D6541667F11C00C49A39 /* GTDiff.h in Headers */,
11121136
3011D86B1668E48500CE3409 /* GTDiffFile.h in Headers */,
1137+
4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */,
11131138
3011D8711668E78500CE3409 /* GTDiffHunk.h in Headers */,
11141139
880EE66118AE700500B82455 /* GTFilter.h in Headers */,
11151140
30FDC07F16835A8100654BF0 /* GTDiffLine.h in Headers */,
@@ -1145,6 +1170,7 @@
11451170
D01B6F4D19F82F8700D411BC /* GTRemote.h in Headers */,
11461171
D01B6F1519F82F7B00D411BC /* NSData+Git.h in Headers */,
11471172
D01B6F6119F82FA600D411BC /* GTFilterSource.h in Headers */,
1173+
4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */,
11481174
D0E0171519F9AD820019930C /* ObjectiveGit.h in Headers */,
11491175
88B2131D1B20E785005CF2C5 /* GTRepository+References.h in Headers */,
11501176
D01B6F4919F82F8700D411BC /* GTOdbObject.h in Headers */,
@@ -1447,6 +1473,7 @@
14471473
D0F4E28A17C7F24200BBDE30 /* NSErrorGitSpec.m in Sources */,
14481474
F879D8311B4B788F002D5C07 /* Libgit2FeaturesSpec.m in Sources */,
14491475
88328128173D8A64006D7DCF /* GTTreeSpec.m in Sources */,
1476+
4D962C402126243A003CD3CE /* GTWorktreeSpec.m in Sources */,
14501477
88E353061982EA6B0051001F /* GTRepositoryAttributesSpec.m in Sources */,
14511478
88234B2618F2FE260039972E /* GTRepositoryResetSpec.m in Sources */,
14521479
5BE612931745EEBC00266D8C /* GTTreeBuilderSpec.m in Sources */,
@@ -1457,6 +1484,7 @@
14571484
D040AF70177B9779001AD9EB /* GTOIDSpec.m in Sources */,
14581485
D040AF78177B9A9E001AD9EB /* GTSignatureSpec.m in Sources */,
14591486
4DBA4A3217DA73CE006CD5F5 /* GTRemoteSpec.m in Sources */,
1487+
4D962C43212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */,
14601488
4D123240178E009E0048F785 /* GTRepositoryCommittingSpec.m in Sources */,
14611489
);
14621490
runOnlyForDeploymentPostprocessing = 0;
@@ -1475,6 +1503,7 @@
14751503
30DCBA6517B45A78009B0EBD /* GTRepository+Status.m in Sources */,
14761504
BD6C235413146E6A00992935 /* GTObject.m in Sources */,
14771505
4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */,
1506+
4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */,
14781507
BD6C254613148DD300992935 /* GTSignature.m in Sources */,
14791508
BD6B0412131496B8001909D0 /* GTTree.m in Sources */,
14801509
BD6B0418131496CC001909D0 /* GTTreeEntry.m in Sources */,
@@ -1496,6 +1525,7 @@
14961525
88EB7E4E14AEBA600046FEA4 /* GTConfiguration.m in Sources */,
14971526
883CD6AC1600EBC600F57354 /* GTRemote.m in Sources */,
14981527
30DCBA7317B4791A009B0EBD /* NSArray+StringArray.m in Sources */,
1528+
4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */,
14991529
4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */,
15001530
88F05AC61601209A00B7AD1D /* ObjectiveGit.m in Sources */,
15011531
30A3D6561667F11C00C49A39 /* GTDiff.m in Sources */,
@@ -1540,11 +1570,13 @@
15401570
D01B6F3819F82F8700D411BC /* GTTree.m in Sources */,
15411571
D01B6F6C19F82FB300D411BC /* GTDiff.m in Sources */,
15421572
884C8A3A19FF4B890017E98D /* EXTScope.m in Sources */,
1573+
4D22E16B2127922F003CD3CE /* GTWorktree.m in Sources */,
15431574
D01B6F4E19F82F8700D411BC /* GTRemote.m in Sources */,
15441575
D01B6F3019F82F8700D411BC /* GTObject.m in Sources */,
15451576
F8D1BDF11B31FE7C00CDEC90 /* GTRepository+Pull.m in Sources */,
15461577
D01B6F4619F82F8700D411BC /* GTBranch.m in Sources */,
15471578
D01B6F3A19F82F8700D411BC /* GTTreeEntry.m in Sources */,
1579+
4D22E16C21279314003CD3CE /* GTRepository+Worktree.m in Sources */,
15481580
D01B6F2419F82F8700D411BC /* GTRepository+Reset.m in Sources */,
15491581
D01B6F5219F82FA600D411BC /* GTBlame.m in Sources */,
15501582
D01B6F5A19F82FA600D411BC /* GTOID.m in Sources */,
@@ -1590,6 +1622,7 @@
15901622
buildActionMask = 2147483647;
15911623
files = (
15921624
F8D007931B4FA03B009A8DAF /* GTObjectSpec.m in Sources */,
1625+
4D962C412126243A003CD3CE /* GTWorktreeSpec.m in Sources */,
15931626
F8D0078D1B4FA03B009A8DAF /* GTBranchSpec.m in Sources */,
15941627
F8D007761B4F7D10009A8DAF /* GTTimeAdditionsSpec.m in Sources */,
15951628
F8D007921B4FA03B009A8DAF /* GTIndexSpec.m in Sources */,
@@ -1622,6 +1655,7 @@
16221655
F879D8441B4B80C7002D5C07 /* Libgit2FeaturesSpec.m in Sources */,
16231656
F8D0078C1B4FA03B009A8DAF /* GTBlobSpec.m in Sources */,
16241657
F8D007991B4FA03B009A8DAF /* GTRepositorySpec.m in Sources */,
1658+
4D962C44212631D4003CD3CE /* GTRepository+WorktreeSpec.m in Sources */,
16251659
F8D0079F1B4FA03B009A8DAF /* GTObjectDatabaseSpec.m in Sources */,
16261660
F8D0079E1B4FA03B009A8DAF /* GTTreeSpec.m in Sources */,
16271661
F8D007A01B4FA03B009A8DAF /* GTRepository+StatusSpec.m in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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+
__block GTWorktree *worktree;
20+
beforeEach(^{
21+
repo = self.bareFixtureRepository;
22+
expect(repo).notTo(beNil());
23+
24+
NSURL *worktreeURL = [self.tempDirectoryFileURL URLByAppendingPathComponent:@"test-worktree"];
25+
26+
NSError *error = nil;
27+
worktree = [GTWorktree addWorktreeWithName:@"test" URL:worktreeURL forRepository:repo options:nil error:&error];
28+
expect(worktree).notTo(beNil());
29+
expect(error).to(beNil());
30+
});
31+
32+
describe(@"-isWorktree", ^{
33+
expect(repo.isWorktree).to(beFalse());
34+
});
35+
36+
describe(@"-worktreeNamesWithError:", ^{
37+
it(@"returns the list of worktrees", ^{
38+
NSError *error = nil;
39+
NSArray *worktreeNames = [repo worktreeNamesWithError:&error];
40+
expect(worktreeNames).to(contain(@"test"));
41+
});
42+
});
43+
44+
describe(@"-lookupWorktreeWithName:", ^{
45+
it(@"returns an existing worktree", ^{
46+
NSError *error = nil;
47+
48+
GTWorktree *worktree2 = [repo lookupWorktreeWithName:@"test" error:&error];
49+
expect(worktree2).notTo(beNil());
50+
expect(error).to(beNil());
51+
});
52+
53+
it(@"fails on non-existent worktrees", ^{
54+
NSError *error = nil;
55+
56+
GTWorktree *worktree2 = [repo lookupWorktreeWithName:@"blob" error:&error];
57+
expect(worktree2).to(beNil());
58+
expect(error).notTo(beNil());
59+
expect(error.code).to(equal(-1));
60+
expect(error.localizedDescription).to(equal(@"Failed to lookup worktree"));
61+
});
62+
});
63+
64+
describe(@"+openWorktree", ^{
65+
it(@"won't return a worktree from a real repository", ^{
66+
NSError *error = nil;
67+
68+
GTWorktree *repoWorktree = [repo openWorktree:&error];
69+
expect(repoWorktree).to(beNil());
70+
expect(error).notTo(beNil());
71+
expect(error.code).to(equal(-1));
72+
expect(error.localizedDescription).to(equal(@"Failed to open worktree"));
73+
});
74+
75+
it(@"can return a worktree from a worktree's repository", ^{
76+
NSError *error = nil;
77+
78+
GTRepository *repoWt = [GTRepository repositoryWithWorktree:worktree error:&error];
79+
expect(repoWt).notTo(beNil());
80+
expect(repoWt.isWorktree).to(beTrue());
81+
82+
GTWorktree *worktreeRepoWt = [repoWt openWorktree:&error];
83+
expect(worktreeRepoWt).notTo(beNil());
84+
expect(error).to(beNil());
85+
86+
});
87+
});
88+
89+
describe(@"+repositoryWithWorktree:", ^{
90+
it(@"can open a repository from a worktree", ^{
91+
NSError *error = nil;
92+
93+
GTRepository *repo2 = [GTRepository repositoryWithWorktree:worktree error:&error];
94+
expect(repo2).notTo(beNil());
95+
expect(error).to(beNil());
96+
97+
expect(repo.isWorktree).to(beFalse());
98+
});
99+
});
100+
101+
fdescribe(@"with a worktree repository", ^{
102+
__block GTRepository *worktreeRepo;
103+
beforeEach(^{
104+
NSError *error = nil;
105+
106+
worktreeRepo = [GTRepository repositoryWithWorktree:worktree error:&error];
107+
expect(worktreeRepo).notTo(beNil());
108+
expect(error).to(beNil());
109+
});
110+
111+
describe(@"-fileURL", ^{
112+
it(@"returns an absolute url", ^{
113+
NSURL *url = worktreeRepo.fileURL;
114+
expect(url).notTo(beNil());
115+
expect([url.path substringToIndex:1]).to(equal(@"/"));
116+
});
117+
});
118+
});
119+
120+
afterEach(^{
121+
[self tearDown];
122+
});
123+
124+
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.