diff --git a/Classes/GTDiff.h b/Classes/GTDiff.h index 01b5defd7..8145ba288 100644 --- a/Classes/GTDiff.h +++ b/Classes/GTDiff.h @@ -46,6 +46,17 @@ extern NSString *const GTDiffOptionsNewPrefixKey; // Defaults to 512MB. extern NSString *const GTDiffOptionsMaxSizeKey; +// An `NSArray` of `NSStrings`s to limit the diff to specific paths inside the +// repository. The entries in the array represent either single paths or +// filename patterns with wildcard matching a la standard shell glob (see +// http://linux.die.net/man/7/glob for wildcard matching rules). +// +// The diff will only contain the files or patterns included in this options +// array. +// +// Defaults to including all files. +extern NSString *const GTDiffOptionsPathSpecArrayKey; + // Enum for use as documented in the options dictionary with the // `GTDiffOptionsFlagsKey` key. // diff --git a/Classes/GTDiff.m b/Classes/GTDiff.m index fa6cd2bc1..63c37153f 100644 --- a/Classes/GTDiff.m +++ b/Classes/GTDiff.m @@ -20,6 +20,7 @@ NSString *const GTDiffOptionsOldPrefixKey = @"GTDiffOptionsOldPrefixKey"; NSString *const GTDiffOptionsNewPrefixKey = @"GTDiffOptionsNewPrefixKey"; NSString *const GTDiffOptionsMaxSizeKey = @"GTDiffOptionsMaxSizeKey"; +NSString *const GTDiffOptionsPathSpecArrayKey = @"GTDiffOptionsPathSpecArrayKey"; NSString *const GTDiffFindOptionsFlagsKey = @"GTDiffFindOptionsFlagsKey"; NSString *const GTDiffFindOptionsRenameThresholdKey = @"GTDiffFindOptionsRenameThresholdKey"; @@ -30,40 +31,62 @@ @implementation GTDiff -+ (BOOL)optionsStructFromDictionary:(NSDictionary *)dictionary optionsStruct:(git_diff_options *)newOptions { - if (dictionary == nil || dictionary.count < 1) return NO; ++ (git_diff_options *)optionsStructFromDictionary:(NSDictionary *)dictionary { + if (dictionary == nil || dictionary.count < 1) return nil; + + git_diff_options newOptions = GIT_DIFF_OPTIONS_INIT; NSNumber *flagsNumber = dictionary[GTDiffOptionsFlagsKey]; - if (flagsNumber != nil) newOptions->flags = (uint32_t)flagsNumber.unsignedIntegerValue; + if (flagsNumber != nil) newOptions.flags = (uint32_t)flagsNumber.unsignedIntegerValue; NSNumber *contextLinesNumber = dictionary[GTDiffOptionsContextLinesKey]; - if (contextLinesNumber != nil) newOptions->context_lines = (uint16_t)contextLinesNumber.unsignedIntegerValue; + if (contextLinesNumber != nil) newOptions.context_lines = (uint16_t)contextLinesNumber.unsignedIntegerValue; NSNumber *interHunkLinesNumber = dictionary[GTDiffOptionsInterHunkLinesKey]; - if (interHunkLinesNumber != nil) newOptions->interhunk_lines = (uint16_t)interHunkLinesNumber.unsignedIntegerValue; + if (interHunkLinesNumber != nil) newOptions.interhunk_lines = (uint16_t)interHunkLinesNumber.unsignedIntegerValue; // We cast to char* below to work around a current bug in libgit2, which is // fixed in https://github.com/libgit2/libgit2/pull/1118 NSString *oldPrefix = dictionary[GTDiffOptionsOldPrefixKey]; - if (oldPrefix != nil) newOptions->old_prefix = (char *)oldPrefix.UTF8String; + if (oldPrefix != nil) newOptions.old_prefix = (char *)oldPrefix.UTF8String; NSString *newPrefix = dictionary[GTDiffOptionsNewPrefixKey]; - if (newPrefix != nil) newOptions->new_prefix = (char *)newPrefix.UTF8String; + if (newPrefix != nil) newOptions.new_prefix = (char *)newPrefix.UTF8String; NSNumber *maxSizeNumber = dictionary[GTDiffOptionsMaxSizeKey]; - if (maxSizeNumber != nil) newOptions->max_size = (uint16_t)maxSizeNumber.unsignedIntegerValue; + if (maxSizeNumber != nil) newOptions.max_size = (uint16_t)maxSizeNumber.unsignedIntegerValue; + + NSArray *pathSpec = dictionary[GTDiffOptionsPathSpecArrayKey]; + if (pathSpec != nil) { + char **cStrings = malloc(sizeof(*cStrings) * pathSpec.count); + for (NSUInteger idx = 0; idx < pathSpec.count; idx ++) { + cStrings[idx] = (char *)[pathSpec[idx] cStringUsingEncoding:NSUTF8StringEncoding]; + } + + git_strarray optionsPathSpec = {.strings = cStrings, .count = pathSpec.count}; + newOptions.pathspec = optionsPathSpec; + } - return YES; + git_diff_options *returnOptions = malloc(sizeof(*returnOptions)); + memcpy(returnOptions, &newOptions, sizeof(*returnOptions)); + + return returnOptions; +} + ++ (void)freeOptionsStruct:(git_diff_options *)options { + if (options == NULL) return; + free(options->pathspec.strings); + free(options); } + (GTDiff *)diffOldTree:(GTTree *)oldTree withNewTree:(GTTree *)newTree options:(NSDictionary *)options error:(NSError **)error { NSParameterAssert([oldTree.repository isEqual:newTree.repository]); - git_diff_options optionsStruct = GIT_DIFF_OPTIONS_INIT; - BOOL optionsStructCreated = [self optionsStructFromDictionary:options optionsStruct:&optionsStruct]; + git_diff_options *optionsStruct = [self optionsStructFromDictionary:options]; git_diff_list *diffList; - int returnValue = git_diff_tree_to_tree(&diffList, oldTree.repository.git_repository, oldTree.git_tree, newTree.git_tree, (optionsStructCreated ? &optionsStruct : NULL)); + int returnValue = git_diff_tree_to_tree(&diffList, oldTree.repository.git_repository, oldTree.git_tree, newTree.git_tree, optionsStruct); + [self freeOptionsStruct:optionsStruct]; if (returnValue != GIT_OK) { if (error != NULL) *error = [NSError git_errorFor:returnValue withAdditionalDescription:@"Failed to create diff."]; return nil; @@ -76,10 +99,10 @@ + (GTDiff *)diffOldTree:(GTTree *)oldTree withNewTree:(GTTree *)newTree options: + (GTDiff *)diffIndexFromTree:(GTTree *)tree options:(NSDictionary *)options error:(NSError **)error { NSParameterAssert(tree != nil); - git_diff_options optionsStruct = GIT_DIFF_OPTIONS_INIT; - BOOL optionsStructCreated = [self optionsStructFromDictionary:options optionsStruct:&optionsStruct]; + git_diff_options *optionsStruct = [self optionsStructFromDictionary:options]; git_diff_list *diffList; - int returnValue = git_diff_tree_to_index(&diffList, tree.repository.git_repository, tree.git_tree, NULL, (optionsStructCreated ? &optionsStruct : NULL)); + int returnValue = git_diff_tree_to_index(&diffList, tree.repository.git_repository, tree.git_tree, NULL, optionsStruct); + [self freeOptionsStruct:optionsStruct]; if (returnValue != GIT_OK) { if (error != NULL) *error = [NSError git_errorFor:returnValue withAdditionalDescription:@"Failed to create diff."]; return nil; @@ -92,10 +115,10 @@ + (GTDiff *)diffIndexFromTree:(GTTree *)tree options:(NSDictionary *)options err + (GTDiff *)diffIndexToWorkingDirectoryInRepository:(GTRepository *)repository options:(NSDictionary *)options error:(NSError **)error { NSParameterAssert(repository != nil); - git_diff_options optionsStruct = GIT_DIFF_OPTIONS_INIT; - BOOL optionsStructCreated = [self optionsStructFromDictionary:options optionsStruct:&optionsStruct]; + git_diff_options *optionsStruct = [self optionsStructFromDictionary:options]; git_diff_list *diffList; - int returnValue = git_diff_index_to_workdir(&diffList, repository.git_repository, NULL, (optionsStructCreated ? &optionsStruct : NULL)); + int returnValue = git_diff_index_to_workdir(&diffList, repository.git_repository, NULL, optionsStruct); + [self freeOptionsStruct:optionsStruct]; if (returnValue != GIT_OK) { if (error != NULL) *error = [NSError git_errorFor:returnValue withAdditionalDescription:@"Failed to create diff."]; return nil; @@ -108,10 +131,10 @@ + (GTDiff *)diffIndexToWorkingDirectoryInRepository:(GTRepository *)repository o + (GTDiff *)diffWorkingDirectoryFromTree:(GTTree *)tree options:(NSDictionary *)options error:(NSError **)error { NSParameterAssert(tree != nil); - git_diff_options optionsStruct = GIT_DIFF_OPTIONS_INIT; - BOOL optionsStructCreated = [self optionsStructFromDictionary:options optionsStruct:&optionsStruct]; + git_diff_options *optionsStruct = [self optionsStructFromDictionary:options]; git_diff_list *diffList; - int returnValue = git_diff_tree_to_workdir(&diffList, tree.repository.git_repository, tree.git_tree, (optionsStructCreated ? &optionsStruct : NULL)); + int returnValue = git_diff_tree_to_workdir(&diffList, tree.repository.git_repository, tree.git_tree, optionsStruct); + [self freeOptionsStruct:optionsStruct]; if (returnValue != GIT_OK) { if (error != NULL) *error = [NSError git_errorFor:returnValue withAdditionalDescription:@"Failed to create diff."]; return nil; diff --git a/Classes/GTRepository.m b/Classes/GTRepository.m index 7a23400b8..36d5efbb5 100644 --- a/Classes/GTRepository.m +++ b/Classes/GTRepository.m @@ -172,24 +172,16 @@ + (id)cloneFromURL:(NSURL *)originURL toWorkingDirectory:(NSURL *)workdirURL bar checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE; checkoutOptions.progress_cb = checkoutProgressCallback; checkoutOptions.progress_payload = (__bridge void *)checkoutProgressBlock; - cloneOptions.checkout_opts = &checkoutOptions; + cloneOptions.checkout_opts = checkoutOptions; } cloneOptions.fetch_progress_cb = transferProgressCallback; cloneOptions.fetch_progress_payload = (__bridge void *)transferProgressBlock; - git_remote *remote; const char *remoteURL = originURL.absoluteString.UTF8String; - int gitError = git_remote_new(&remote, NULL, "origin", remoteURL, GIT_REMOTE_DEFAULT_FETCH); - if (gitError != GIT_OK) { - if (error != NULL) *error = [NSError git_errorFor:gitError withAdditionalDescription:@"Failed to create remote to clone repository."]; - return nil; - } - const char *workingDirectoryPath = workdirURL.path.UTF8String; git_repository *repository; - gitError = git_clone(&repository, remote, workingDirectoryPath, &cloneOptions); - git_remote_free(remote); + int gitError = git_clone(&repository, remoteURL, workingDirectoryPath, &cloneOptions); if (gitError < GIT_OK) { if (error != NULL) *error = [NSError git_errorFor:gitError withAdditionalDescription:@"Failed to clone repository."]; return nil; diff --git a/ObjectiveGitTests/GTDiffSpec.m b/ObjectiveGitTests/GTDiffSpec.m index 964b93623..a2899ac92 100644 --- a/ObjectiveGitTests/GTDiffSpec.m +++ b/ObjectiveGitTests/GTDiffSpec.m @@ -172,6 +172,16 @@ }]; }); + it(@"should correctly limit itself to a given pathspec", ^{ + NSDictionary *options = @{ GTDiffOptionsPathSpecArrayKey: @[ @"ladflbahjgdf" ] }; + setupDiffFromCommitSHAsAndOptions(@"be0f001ff517a00b5b8e3c29ee6561e70f994e17", @"fe89ea0a8e70961b8a6344d9660c326d3f2eb0fe", options); + expect(diff.deltaCount).to.equal(0); + + options = @{ GTDiffOptionsPathSpecArrayKey: @[ @"TestAppWindowController.h" ] }; + setupDiffFromCommitSHAsAndOptions(@"be0f001ff517a00b5b8e3c29ee6561e70f994e17", @"fe89ea0a8e70961b8a6344d9660c326d3f2eb0fe", options); + expect(diff.deltaCount).to.equal(1); + }); + it(@"should correctly recognise binary and text files", ^{ setupDiffFromCommitSHAsAndOptions(@"6b0c1c8b8816416089c534e474f4c692a76ac14f", @"a4bca6b67a5483169963572ee3da563da33712f7", nil); expect(diff.deltaCount).to.equal(3); @@ -198,6 +208,18 @@ *stop = YES; }]; }); + + it(@"should correctly find untracked files if asked", ^{ + diff = [GTDiff diffIndexToWorkingDirectoryInRepository:repository options:@{ GTDiffOptionsFlagsKey: @(GTDiffOptionsFlagsIncludeUntracked) } error:NULL]; + __block BOOL foundImage = NO; + [diff enumerateDeltasUsingBlock:^(GTDiffDelta *delta, BOOL *stop) { + if (![delta.newFile.path isEqualToString:@"UntrackedImage.png"]) return; + foundImage = YES; + *stop = YES; + }]; + + expect(foundImage).to.beTruthy(); + }); }); SpecEnd diff --git a/ObjectiveGitTests/fixtures/Fixtures.zip b/ObjectiveGitTests/fixtures/Fixtures.zip index a27d3f41a..892acaec2 100644 Binary files a/ObjectiveGitTests/fixtures/Fixtures.zip and b/ObjectiveGitTests/fixtures/Fixtures.zip differ diff --git a/libgit2 b/libgit2 index e62171e2f..160e4fb79 160000 --- a/libgit2 +++ b/libgit2 @@ -1 +1 @@ -Subproject commit e62171e2fc1b101512a7e86f6d990a38b78ed12b +Subproject commit 160e4fb792b070e14c7094893e390c53d788648c