Skip to content

Commit f9f6310

Browse files
committed
Add worktree support via GTWorktree
1 parent 2e60264 commit f9f6310

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>

0 commit comments

Comments
 (0)