From a4ceb97dd571be9ebc3e78010aa484c05c50ba8c Mon Sep 17 00:00:00 2001 From: Etienne Samson Date: Sun, 26 Nov 2017 12:50:24 +0100 Subject: [PATCH] WIP: worktree support --- ObjectiveGit/GTRepository+Worktree.h | 42 +++++++ ObjectiveGit/GTRepository+Worktree.m | 112 ++++++++++++++++++ ObjectiveGit/GTWorktree.h | 35 ++++++ ObjectiveGit/GTWorktree.m | 100 ++++++++++++++++ ObjectiveGit/ObjectiveGit.h | 2 + .../project.pbxproj | 18 +++ 6 files changed, 309 insertions(+) create mode 100644 ObjectiveGit/GTRepository+Worktree.h create mode 100644 ObjectiveGit/GTRepository+Worktree.m create mode 100644 ObjectiveGit/GTWorktree.h create mode 100644 ObjectiveGit/GTWorktree.m diff --git a/ObjectiveGit/GTRepository+Worktree.h b/ObjectiveGit/GTRepository+Worktree.h new file mode 100644 index 000000000..bb797b43d --- /dev/null +++ b/ObjectiveGit/GTRepository+Worktree.h @@ -0,0 +1,42 @@ +// +// GTRepository+GTRepository_Worktree.h +// ObjectiveGitFramework +// +// Created by Etienne on 25/07/2017. +// Copyright © 2017 GitHub, Inc. All rights reserved. +// + +#import + +@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 * _Nullable)worktreeNamesWithError:(NSError **)error; + +- (GTWorktree * _Nullable)lookupWorktreeWithName:(NSString *)name error:(NSError **)error; + +- (GTWorktree * _Nullable)openWorktree:(NSError **)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ObjectiveGit/GTRepository+Worktree.m b/ObjectiveGit/GTRepository+Worktree.m new file mode 100644 index 000000000..b0565e975 --- /dev/null +++ b/ObjectiveGit/GTRepository+Worktree.m @@ -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 *)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 diff --git a/ObjectiveGit/GTWorktree.h b/ObjectiveGit/GTWorktree.h new file mode 100644 index 000000000..500deb609 --- /dev/null +++ b/ObjectiveGit/GTWorktree.h @@ -0,0 +1,35 @@ +// +// GTWorktree.h +// ObjectiveGitFramework +// +// Created by Etienne on 25/07/2017. +// Copyright © 2017 GitHub, Inc. All rights reserved. +// + +#import + +#import "GTRepository.h" + +#import "git2/worktree.h" + +typedef struct { + BOOL lock; +} GTWorktreeAddOptions; + +@interface GTWorktree : NSObject + ++ (instancetype)addWorktreeWithName:(NSString *)name URL:(NSURL *)worktreeURL forRepository:(GTRepository *)repository options:(const GTWorktreeAddOptions *)options error:(NSError **)error; + +- (instancetype)initWithGitWorktree:(git_worktree *)worktree; + +/// The underlying `git_worktree` object. +- (git_worktree *)git_worktree __attribute__((objc_returns_inner_pointer)); + +- (BOOL)isValid:(NSError **)error; + +- (BOOL)lockWithReason:(NSString *)reason error:(NSError **)error; +- (BOOL)unlock:(BOOL *)wasLocked error:(NSError **)error; + +- (BOOL)isLocked:(BOOL *)locked reason:(NSString **)reason error:(NSError **)error; + +@end diff --git a/ObjectiveGit/GTWorktree.m b/ObjectiveGit/GTWorktree.m new file mode 100644 index 000000000..83a5a7002 --- /dev/null +++ b/ObjectiveGit/GTWorktree.m @@ -0,0 +1,100 @@ +// +// 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 "git2/errors.h" +#import "git2/buffer.h" + +@interface GTWorktree () +@property (nonatomic, assign, readonly) git_worktree *git_worktree; +@end + +@implementation GTWorktree + ++ (instancetype)addWorktreeWithName:(NSString *)name URL:(NSURL *)worktreeURL forRepository:(GTRepository *)repository options:(const GTWorktreeAddOptions *)options error:(NSError **)error { + git_worktree *worktree; + git_worktree_add_options git_options = GIT_WORKTREE_ADD_OPTIONS_INIT; + + if (options) { + git_options.lock = options->lock; + } + + 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, (char *)reason.UTF8String); /* WIP: I don't like that cast */ + 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) { + *wasLocked = (gitError == 0); + } + + return YES; +} + +- (BOOL)isLocked:(BOOL *)locked reason:(NSString **)reason error:(NSError **)error { + git_buf reasonBuf; + 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); + + /* WIP: reason */ + + return YES; +} + +@end diff --git a/ObjectiveGit/ObjectiveGit.h b/ObjectiveGit/ObjectiveGit.h index 5c8cba5bc..77eef7a56 100644 --- a/ObjectiveGit/ObjectiveGit.h +++ b/ObjectiveGit/ObjectiveGit.h @@ -42,6 +42,8 @@ FOUNDATION_EXPORT const unsigned char ObjectiveGitVersionString[]; #import #import #import +#import +#import #import #import #import diff --git a/ObjectiveGitFramework.xcodeproj/project.pbxproj b/ObjectiveGitFramework.xcodeproj/project.pbxproj index 81414dc2a..186e0c963 100644 --- a/ObjectiveGitFramework.xcodeproj/project.pbxproj +++ b/ObjectiveGitFramework.xcodeproj/project.pbxproj @@ -94,10 +94,15 @@ 4D79C0EE17DF9F4D00997DE4 /* GTCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4D79C0EF17DF9F4D00997DE4 /* GTCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */; }; 4DBA4A3217DA73CE006CD5F5 /* GTRemoteSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */; }; + 4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */; }; + 4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC372101F27D6D3003CD3CE /* GTWorktree.m */; }; 4DC55AE51AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4DC55AE61AD859AD0032563C /* GTCheckoutOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; }; 4DC55AE81AD859AD0032563C /* GTCheckoutOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */; }; + 4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4DFFB15B183AA8D600D1565E /* GTRepository+RemoteOperations.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DFFB159183AA8D600D1565E /* GTRepository+RemoteOperations.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DFFB15A183AA8D600D1565E /* GTRepository+RemoteOperations.m */; }; 55C8055013861FE7004DCB0F /* GTObjectDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C8054D13861F34004DCB0F /* GTObjectDatabase.m */; }; @@ -491,6 +496,10 @@ 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCredential.m; sourceTree = ""; }; 4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTCredential+Private.h"; sourceTree = ""; }; 4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRemoteSpec.m; sourceTree = ""; }; + 4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Worktree.h"; sourceTree = ""; }; + 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTRepository+Worktree.m"; sourceTree = ""; }; + 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTWorktree.h; sourceTree = ""; }; + 4DC372101F27D6D3003CD3CE /* GTWorktree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTWorktree.m; sourceTree = ""; }; 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCheckoutOptions.h; sourceTree = ""; }; 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCheckoutOptions.m; sourceTree = ""; }; 4DE864341794A37E00371A65 /* GTRepository+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Private.h"; sourceTree = ""; }; @@ -898,6 +907,8 @@ 88B2131B1B20E785005CF2C5 /* GTRepository+References.m */, 23F39FAB1C86DB1C00849F3C /* GTRepository+Merging.h */, 23F39FAC1C86DB1C00849F3C /* GTRepository+Merging.m */, + 4DC3720B1F27CD96003CD3CE /* GTRepository+Worktree.h */, + 4DC3720C1F27CD96003CD3CE /* GTRepository+Worktree.m */, BDD8AE6D13131B8800CB5D40 /* GTEnumerator.h */, BDD8AE6E13131B8800CB5D40 /* GTEnumerator.m */, BD6C22A71314625800992935 /* GTObject.h */, @@ -967,6 +978,8 @@ 6EEB51A0199D62B9001D72C0 /* GTFetchHeadEntry.m */, 4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */, 4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */, + 4DC3720F1F27D6D3003CD3CE /* GTWorktree.h */, + 4DC372101F27D6D3003CD3CE /* GTWorktree.m */, ); path = ObjectiveGit; sourceTree = ""; @@ -1081,6 +1094,7 @@ BDD627991318391200DE34D1 /* GTBlob.h in Headers */, 886E622A18AEBF75000611A0 /* GTFilterSource.h in Headers */, BDD62924131C03D600DE34D1 /* GTTag.h in Headers */, + 4DC372111F27D6D3003CD3CE /* GTWorktree.h in Headers */, 88BC0E5018EF4F3600C7D0E6 /* GTRepository+Reset.h in Headers */, BDFAF9C3131C1845000508BC /* GTIndex.h in Headers */, BDFAF9C9131C1868000508BC /* GTIndexEntry.h in Headers */, @@ -1108,6 +1122,7 @@ 55C8057E13875C1B004DCB0F /* NSString+Git.h in Headers */, 30A3D6541667F11C00C49A39 /* GTDiff.h in Headers */, 3011D86B1668E48500CE3409 /* GTDiffFile.h in Headers */, + 4DC3720D1F27CD96003CD3CE /* GTRepository+Worktree.h in Headers */, 3011D8711668E78500CE3409 /* GTDiffHunk.h in Headers */, 880EE66118AE700500B82455 /* GTFilter.h in Headers */, 30FDC07F16835A8100654BF0 /* GTDiffLine.h in Headers */, @@ -1143,6 +1158,7 @@ D01B6F4D19F82F8700D411BC /* GTRemote.h in Headers */, D01B6F1519F82F7B00D411BC /* NSData+Git.h in Headers */, D01B6F6119F82FA600D411BC /* GTFilterSource.h in Headers */, + 4DE935D21FCB0096003CD3CE /* GTWorktree.h in Headers */, D0E0171519F9AD820019930C /* ObjectiveGit.h in Headers */, 88B2131D1B20E785005CF2C5 /* GTRepository+References.h in Headers */, D01B6F4919F82F8700D411BC /* GTOdbObject.h in Headers */, @@ -1473,6 +1489,7 @@ 30DCBA6517B45A78009B0EBD /* GTRepository+Status.m in Sources */, BD6C235413146E6A00992935 /* GTObject.m in Sources */, 4DC55AE71AD859AD0032563C /* GTCheckoutOptions.m in Sources */, + 4DC3720E1F27CD96003CD3CE /* GTRepository+Worktree.m in Sources */, BD6C254613148DD300992935 /* GTSignature.m in Sources */, BD6B0412131496B8001909D0 /* GTTree.m in Sources */, BD6B0418131496CC001909D0 /* GTTreeEntry.m in Sources */, @@ -1494,6 +1511,7 @@ 88EB7E4E14AEBA600046FEA4 /* GTConfiguration.m in Sources */, 883CD6AC1600EBC600F57354 /* GTRemote.m in Sources */, 30DCBA7317B4791A009B0EBD /* NSArray+StringArray.m in Sources */, + 4DC372121F27D6D3003CD3CE /* GTWorktree.m in Sources */, 4DFFB15C183AA8D600D1565E /* GTRepository+RemoteOperations.m in Sources */, 88F05AC61601209A00B7AD1D /* ObjectiveGit.m in Sources */, 30A3D6561667F11C00C49A39 /* GTDiff.m in Sources */,