Skip to content

Commit

Permalink
Add worktree support via GTWorktree
Browse files Browse the repository at this point in the history
  • Loading branch information
tiennou committed Aug 16, 2018
1 parent 167d13f commit a5bd7b8
Show file tree
Hide file tree
Showing 8 changed files with 629 additions and 0 deletions.
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
112 changes: 112 additions & 0 deletions ObjectiveGit/GTRepository+Worktree.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// 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 {
return [NSURL fileURLWithPath:@(git_repository_commondir(self.git_repository)) 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
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 *)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 *)locked reason:(NSString ** _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
2 changes: 2 additions & 0 deletions ObjectiveGit/ObjectiveGit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading

0 comments on commit a5bd7b8

Please sign in to comment.