diff --git a/GitUp/Application/DocumentController.m b/GitUp/Application/DocumentController.m index 10d21c15..7578f4c4 100644 --- a/GitUp/Application/DocumentController.m +++ b/GitUp/Application/DocumentController.m @@ -31,6 +31,18 @@ - (void)beginOpenPanel:(NSOpenPanel*)openPanel forTypes:(NSArray*)inTypes comple [super beginOpenPanel:openPanel forTypes:inTypes completionHandler:completionHandler]; } +- (void)openDocumentWithContentsOfURL:(NSURL *)url display:(BOOL)displayDocument completionHandler:(void (^)(NSDocument *_Nullable, BOOL, NSError *_Nullable))completionHandler { + NSError *error; + NSURL *repositoryURL = [GCRepository repositoryURLContainingURL:url error:&error]; + + if (repositoryURL) { + [super openDocumentWithContentsOfURL:repositoryURL display:displayDocument completionHandler:completionHandler]; + } else { + // Document already handles the URL not being a repository so just reuse that failure handling. + [super openDocumentWithContentsOfURL:url display:displayDocument completionHandler:completionHandler]; + } +} + - (NSError*)willPresentError:(NSError*)error { NSError* underlyingError = [error.userInfo objectForKey:NSUnderlyingErrorKey]; if ([underlyingError.domain isEqualToString:GCErrorDomain]) { diff --git a/GitUpKit/Core/GCRepository-Tests.m b/GitUpKit/Core/GCRepository-Tests.m index 76a709b1..e2b823b1 100644 --- a/GitUpKit/Core/GCRepository-Tests.m +++ b/GitUpKit/Core/GCRepository-Tests.m @@ -55,6 +55,34 @@ - (void)testOpen { XCTAssertTrue([[NSFileManager defaultManager] removeItemAtPath:path error:NULL]); } +- (void)testRepositoryURLContainingURL { + NSString* parentPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + XCTAssertTrue([[NSFileManager defaultManager] createDirectoryAtPath:parentPath withIntermediateDirectories:NO attributes:nil error:NULL]); + + NSString* repositoryPath = [parentPath stringByAppendingPathComponent:@"repository"]; + GCRepository* repository = [self createLocalRepositoryAtPath:repositoryPath bare:NO]; + NSURL* expectedURL = [NSURL fileURLWithPath:repository.workingDirectoryPath]; + + NSError* error = nil; + NSURL* sameDirectoryURL = [GCRepository repositoryURLContainingURL:expectedURL error:&error]; + XCTAssertEqualObjects(sameDirectoryURL.path.stringByStandardizingPath, expectedURL.path.stringByStandardizingPath); + XCTAssertNil(error); + + NSString* subdirectoryPath = [repository.workingDirectoryPath stringByAppendingPathComponent:@"subdirectory"]; + XCTAssertTrue([[NSFileManager defaultManager] createDirectoryAtPath:subdirectoryPath withIntermediateDirectories:NO attributes:nil error:NULL]); + error = nil; + NSURL* subdirectoryURL = [GCRepository repositoryURLContainingURL:[NSURL fileURLWithPath:subdirectoryPath] error:&error]; + XCTAssertEqualObjects(subdirectoryURL.path.stringByStandardizingPath, expectedURL.path.stringByStandardizingPath); + XCTAssertNil(error); + + error = nil; + XCTAssertNil([GCRepository repositoryURLContainingURL:[NSURL fileURLWithPath:parentPath] error:&error]); + XCTAssertEqual(error.code, kGCErrorCode_NotFound); + + [self destroyLocalRepository:repository]; + XCTAssertTrue([[NSFileManager defaultManager] removeItemAtPath:parentPath error:NULL]); +} + @end @implementation GCEmptyRepositoryTests (GCRepository) diff --git a/GitUpKit/Core/GCRepository.h b/GitUpKit/Core/GCRepository.h index 244ed7e9..cf7240f0 100644 --- a/GitUpKit/Core/GCRepository.h +++ b/GitUpKit/Core/GCRepository.h @@ -52,6 +52,8 @@ typedef NS_ENUM(NSUInteger, GCFileMode) { @end @interface GCRepository : NSObject +/// Walks up the file system to find the repository the given URL is contained in. Returns nil and sets the error if no repository is found. ++ (NSURL *)repositoryURLContainingURL:(NSURL *)url error:(NSError **)error; @property(nonatomic, weak) id delegate; @property(nonatomic, readonly) NSString* repositoryPath; @property(nonatomic, readonly) NSString* workingDirectoryPath; // nil for a bare repository diff --git a/GitUpKit/Core/GCRepository.m b/GitUpKit/Core/GCRepository.m index 79e6ac2f..43f9ac8a 100644 --- a/GitUpKit/Core/GCRepository.m +++ b/GitUpKit/Core/GCRepository.m @@ -133,6 +133,35 @@ + (void)load { #endif } ++ (NSURL *)repositoryURLContainingURL:(NSURL *)url error:(NSError *__autoreleasing *)error { + NSParameterAssert(url.isFileURL); + + git_buf repoBuffer = {0}; + + int result = git_repository_discover(&repoBuffer, GCGitPathFromFileSystemPath(url.path), false, NULL); + if (result == GIT_OK) { + NSString* repoPath = [[NSString alloc] initWithBytes:repoBuffer.ptr length:repoBuffer.size encoding:NSUTF8StringEncoding]; + NSURL* repoURL = [NSURL fileURLWithPath:repoPath]; + git_buf_dispose(&repoBuffer); + + // Need to trim the .git from the path, otherwise it works but the document title is .git. + + if ([repoURL.lastPathComponent isEqualToString:@".git"]) { + return [repoURL URLByDeletingLastPathComponent]; + } else { + XLOG_WARNING(@"Discovered repository URL not ending in .git: “%@”", repoURL.path); + return repoURL; + } + } + + if (result == GIT_ENOTFOUND) { + GC_SET_ERROR(kGCErrorCode_NotFound, @"No repository found"); + } else { + GC_SET_GENERIC_ERROR(@"Error %d", result); + } + return nil; +} + - (instancetype)initWithRepository:(git_repository*)repository error:(NSError**)error { if ((self = [super init])) { _private = repository;