Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions GitUpKit/Core/GCRepository-Tests.m
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,41 @@ - (void)testState {
XCTAssertEqual(self.repository.state, kGCRepositoryState_None);
}

// Regression test for issue #1053: hooks are shared from the repository's common
// directory, so they must be discoverable from a linked worktree. Previously
// -pathForHookWithName: looked up the default hooks directory in the per-worktree
// git directory, which never contains a hooks directory, so hooks were silently
// unavailable in worktrees.
- (void)testPathForHookWithName_FoundFromLinkedWorktree {
// Create a linked worktree checked out on the topic branch.
NSString* worktreePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
NSString* output = [self runGitCLTWithRepository:self.repository command:@"worktree", @"add", worktreePath, @"topic", nil];
XCTAssertNotNil(output); // nil means git exited non-zero, i.e. the worktree wasn't created

// Open the linked worktree as its own repository.
GCRepository* worktreeRepository = [[GCRepository alloc] initWithExistingLocalRepository:worktreePath error:NULL];
XCTAssertNotNil(worktreeRepository);

NSString* hookName = @"pre-commit";

// A linked worktree's git directory is separate from the common directory and
// must not contain the hook.
NSString* worktreeHookPath = [[worktreeRepository.repositoryPath stringByAppendingPathComponent:@"hooks"] stringByAppendingPathComponent:hookName];
XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:worktreeHookPath]);

// Install the hook in the main repository's common hooks directory, which is
// shared with all linked worktrees.
NSString* commonHookPath = [[self.repository.repositoryPath stringByAppendingPathComponent:@"hooks"] stringByAppendingPathComponent:hookName];
[[NSFileManager defaultManager] createDirectoryAtPath:[commonHookPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:NULL error:NULL];
XCTAssertTrue([[NSFileManager defaultManager] createFileAtPath:commonHookPath
contents:[@"#!/bin/sh\nexit 0\n" dataUsingEncoding:NSUTF8StringEncoding]
attributes:@{NSFilePosixPermissions : @0o755}]);

// The worktree must resolve the hook from the shared common directory.
XCTAssertEqualObjects([worktreeRepository pathForHookWithName:hookName], commonHookPath);

worktreeRepository = nil;
[[NSFileManager defaultManager] removeItemAtPath:worktreePath error:NULL];
}

@end
6 changes: 5 additions & 1 deletion GitUpKit/Core/GCRepository.m
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,11 @@ - (NSString*)pathForHookWithName:(NSString*)name {
hooksPath = [self.workingDirectoryPath stringByAppendingPathComponent:hooksPath];
}
} else {
hooksPath = [self.repositoryPath stringByAppendingPathComponent:@"hooks"];
// Hooks live in the repository's common directory, which is shared by all
// linked worktrees. self.repositoryPath is the per-worktree git directory,
// which for a linked worktree never contains a hooks directory, so looking
// it up there made hooks silently unavailable in worktrees (issue #1053).
hooksPath = [_MakeDirectoryPath(git_repository_commondir(_private)) stringByAppendingPathComponent:@"hooks"];
}
NSString* path = [hooksPath stringByAppendingPathComponent:name];
return [[NSFileManager defaultManager] isExecutableFileAtPath:path] ? path : nil;
Expand Down