Skip to content

Commit

Permalink
Move the merge-handling logic for entries to GTIndex
Browse files Browse the repository at this point in the history
  • Loading branch information
tiennou committed Oct 27, 2018
1 parent 1c7498e commit e0e103f
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 129 deletions.
18 changes: 18 additions & 0 deletions ObjectiveGit/GTIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
@class GTIndexEntry;
@class GTRepository;
@class GTTree;
@class GTMergeResult;

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -234,6 +235,23 @@ NS_ASSUME_NONNULL_BEGIN
- (GTIndexEntry * _Nullable)entryWithName:(NSString *)name error:(NSError **)error __deprecated_msg("use entryWithPath:error: instead.");


@end

@interface GTIndex (FileMerging)

/// Gets the result of a merge with the given file entries
///
/// The parameters taked are the ones received from `enumerateConflictedFiles`.
///
/// ancestor - The ancestor entry
/// ours - The index entry of our side
/// theirs - The index entry of their side
/// options - The merge options to use. Can be nil.
/// error - The error if one occurred. Can be NULL.
///
/// Returns The results of the merge or nil on error
- (GTMergeResult * _Nullable)resultOfMergingAncestorEntry:(const GTIndexEntry *)ancestor ourEntry:(const GTIndexEntry *)ours theirEntry:(const GTIndexEntry *)theirs options:(NSDictionary * _Nullable)options error:(NSError * _Nullable __autoreleasing *)error;

@end

NS_ASSUME_NONNULL_END
27 changes: 27 additions & 0 deletions ObjectiveGit/GTIndex.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@
#import "GTBlob.h"
#import "NSArray+StringArray.h"
#import "NSError+Git.h"
#import "GTMerge+Private.h"

#import "git2/errors.h"
#import "git2/merge.h"

// The block synonymous with libgit2's `git_index_matched_path_cb` callback.
typedef BOOL (^GTIndexPathspecMatchedBlock)(NSString *matchedPathspec, NSString *path, BOOL *stop);
Expand Down Expand Up @@ -406,4 +408,29 @@ - (GTIndexEntry *)entryWithName:(NSString *)name {
- (GTIndexEntry *)entryWithName:(NSString *)name error:(NSError **)error {
return [self entryWithPath:name error:error];
}

@end

@implementation GTIndex (FileMerging)

- (GTMergeResult *)resultOfMergingAncestorEntry:(const GTIndexEntry *)ancestorEntry ourEntry:(const GTIndexEntry *)ourEntry theirEntry:(const GTIndexEntry *)theirEntry options:(NSDictionary *)options error:(NSError * _Nullable __autoreleasing *)error {
NSParameterAssert(ourEntry);
NSParameterAssert(theirEntry);
NSParameterAssert(ancestorEntry);

git_merge_file_result gitResult;
git_merge_file_options opts;

BOOL success = [GTMergeFile handleMergeFileOptions:&opts optionsDict:options error:error];
if (!success) return nil;

int gitError = git_merge_file_from_index(&gitResult, self.repository.git_repository, ancestorEntry.git_index_entry, ourEntry.git_index_entry, theirEntry.git_index_entry, &opts);
if (gitError != 0) {
if (error) *error = [NSError git_errorFor:gitError description:@"Merging entries failed"];
return nil;
}

return [[GTMergeResult alloc] initWithGitMergeFileResult:&gitResult];
}

@end
15 changes: 15 additions & 0 deletions ObjectiveGit/GTMerge+Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// GTMerge+Private.h
// ObjectiveGitFramework
//
// Created by Etienne on 27/10/2018.
// Copyright © 2018 GitHub, Inc. All rights reserved.
//

#import "GTMerge.h"

@interface GTMergeFile (Private)

+ (BOOL)handleMergeFileOptions:(git_merge_file_options *)opts optionsDict:(NSDictionary *)dict error:(NSError **)error;

@end
12 changes: 0 additions & 12 deletions ObjectiveGit/GTRepository+Merging.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,6 @@ typedef NS_OPTIONS(NSInteger, GTMergeAnalysis) {
/// will point to an error describing what happened).
- (BOOL)mergeBranchIntoCurrentBranch:(GTBranch *)fromBranch withError:(NSError **)error;

/// Gets the file content with conflict markers for the given file
///
/// The parameters taked are the ones received from `enumerateConflictedFiles`.
///
/// ancestor - The ancestor entry
/// ours - The index entry of our side
/// theirs - The index entry of their side
/// error - The error if one occurred. Can be NULL.
///
/// Returns The file content annotated with conflict markers or null on error
- (NSString * _Nullable)contentsOfDiffWithAncestor:(GTIndexEntry *)ancestor ourSide:(GTIndexEntry *)ourSide theirSide:(GTIndexEntry *)theirSide error:(NSError **)error;

/// Analyze which merge to perform.
///
/// analysis - The resulting analysis.
Expand Down
75 changes: 0 additions & 75 deletions ObjectiveGit/GTRepository+Merging.m
Original file line number Diff line number Diff line change
Expand Up @@ -172,81 +172,6 @@ - (BOOL)mergeBranchIntoCurrentBranch:(GTBranch *)branch withError:(NSError **)er
return NO;
}

- (NSString * _Nullable)contentsOfDiffWithAncestor:(GTIndexEntry *)ancestor ourSide:(GTIndexEntry *)ourSide theirSide:(GTIndexEntry *)theirSide error:(NSError **)error {

GTObjectDatabase *database = [self objectDatabaseWithError:error];
if (database == nil) {
return nil;
}

// initialize the ancestor's merge file input
git_merge_file_input ancestorInput;
int gitError = git_merge_file_init_input(&ancestorInput, GIT_MERGE_FILE_INPUT_VERSION);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to create merge file input for ancestor"];
return nil;
}

git_oid ancestorId = ancestor.git_index_entry->id;
GTOID *ancestorOID = [[GTOID alloc] initWithGitOid:&ancestorId];
NSData *ancestorData = [[database objectWithOID:ancestorOID error: error] data];
if (ancestorData == nil) {
return nil;
}
ancestorInput.ptr = ancestorData.bytes;
ancestorInput.size = ancestorData.length;


// initialize our merge file input
git_merge_file_input ourInput;
gitError = git_merge_file_init_input(&ourInput, GIT_MERGE_FILE_INPUT_VERSION);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to create merge file input for our side"];
return nil;
}

git_oid ourId = ourSide.git_index_entry->id;
GTOID *ourOID = [[GTOID alloc] initWithGitOid:&ourId];
NSData *ourData = [[database objectWithOID:ourOID error: error] data];
if (ourData == nil) {
return nil;
}
ourInput.ptr = ourData.bytes;
ourInput.size = ourData.length;


// initialize their merge file input
git_merge_file_input theirInput;
gitError = git_merge_file_init_input(&theirInput, GIT_MERGE_FILE_INPUT_VERSION);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to create merge file input other side"];
return nil;
}

git_oid theirId = theirSide.git_index_entry->id;
GTOID *theirOID = [[GTOID alloc] initWithGitOid:&theirId];
NSData *theirData = [[database objectWithOID:theirOID error: error] data];
if (theirData == nil) {
return nil;
}
theirInput.ptr = theirData.bytes;
theirInput.size = theirData.length;


git_merge_file_result result;
gitError = git_merge_file(&result, &ancestorInput, &ourInput, &theirInput, nil);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to create merge file"];
return nil;
}

NSString *mergedContent = [[NSString alloc] initWithBytes:result.ptr length:result.len encoding:NSUTF8StringEncoding];

git_merge_file_result_free(&result);

return mergedContent;
}

- (BOOL)annotatedCommit:(git_annotated_commit **)annotatedCommit fromCommit:(GTCommit *)fromCommit error:(NSError **)error {
int gitError = git_annotated_commit_lookup(annotatedCommit, self.git_repository, fromCommit.OID.git_oid);
if (gitError != GIT_OK) {
Expand Down
2 changes: 2 additions & 0 deletions ObjectiveGitFramework.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@
4D7BA1B82183C4C9003CD3CE /* GTMerge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMerge.h; sourceTree = "<group>"; };
4D7BA1B92183C4C9003CD3CE /* GTMerge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMerge.m; sourceTree = "<group>"; };
4D7BA1BF2183DD55003CD3CE /* GTMergeSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMergeSpec.m; sourceTree = "<group>"; };
4D7BA1BE2183D3EE003CD3CE /* GTMerge+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GTMerge+Private.h"; sourceTree = "<group>"; };
4D9BCD23206D84AD003CD3CE /* libgit2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgit2.a; path = External/build/lib/libgit2.a; sourceTree = "<group>"; };
4DBA4A3117DA73CE006CD5F5 /* GTRemoteSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTRemoteSpec.m; sourceTree = "<group>"; };
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCheckoutOptions.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -987,6 +988,7 @@
4DC55AE31AD859AD0032563C /* GTCheckoutOptions.h */,
4DC55AE41AD859AD0032563C /* GTCheckoutOptions.m */,
4D7BA1B82183C4C9003CD3CE /* GTMerge.h */,
4D7BA1BE2183D3EE003CD3CE /* GTMerge+Private.h */,
4D7BA1B92183C4C9003CD3CE /* GTMerge.m */,
);
path = ObjectiveGit;
Expand Down
69 changes: 69 additions & 0 deletions ObjectiveGitTests/GTIndexSpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,75 @@
});
});

describe(@"-resultOfMergingAncestorEntry:ourEntry:theirEntry:options:error:", ^{
it(@"should produce a nice merge conflict description", ^{
NSURL *mainURL = [repository.fileURL URLByAppendingPathComponent:@"main.m"];
NSData *mainData = [[NSFileManager defaultManager] contentsAtPath:mainURL.path];
expect(mainData).notTo(beNil());

NSString *mainString = [[NSString alloc] initWithData:mainData encoding:NSUTF8StringEncoding];
NSData *masterData = [[mainString stringByReplacingOccurrencesOfString:@"return" withString:@"//The meaning of life is 41\n return"] dataUsingEncoding:NSUTF8StringEncoding];
NSData *otherData = [[mainString stringByReplacingOccurrencesOfString:@"return" withString:@"//The meaning of life is 42\n return"] dataUsingEncoding:NSUTF8StringEncoding];

expect(@([[NSFileManager defaultManager] createFileAtPath:mainURL.path contents:masterData attributes:nil])).to(beTruthy());

GTIndex *index = [repository indexWithError:NULL];
expect(@([index addFile:mainURL.lastPathComponent error:NULL])).to(beTruthy());
GTReference *head = [repository headReferenceWithError:NULL];
GTCommit *parent = [repository lookUpObjectByOID:head.targetOID objectType:GTObjectTypeCommit error:NULL];
expect(parent).toNot(beNil());
GTTree *masterTree = [index writeTree:NULL];
expect(masterTree).toNot(beNil());

GTBranch *otherBranch = [repository lookUpBranchWithName:@"other-branch" type:GTBranchTypeLocal success:NULL error:NULL];
expect(otherBranch).toNot(beNil());
expect(@([repository checkoutReference:otherBranch.reference options:nil error:NULL])).to(beTruthy());

expect(@([[NSFileManager defaultManager] createFileAtPath:mainURL.path contents:otherData attributes:nil])).to(beTruthy());

index = [repository indexWithError:NULL];
expect(@([index addFile:mainURL.lastPathComponent error:NULL])).to(beTruthy());
GTTree *otherTree = [index writeTree:NULL];
expect(otherTree).toNot(beNil());

GTIndex *conflictIndex = [otherTree merge:masterTree ancestor:parent.tree error:NULL];
expect(@([conflictIndex hasConflicts])).to(beTruthy());

[conflictIndex enumerateConflictedFilesWithError:NULL usingBlock:^(GTIndexEntry * _Nonnull ancestor, GTIndexEntry * _Nonnull ours, GTIndexEntry * _Nonnull theirs, BOOL * _Nonnull stop) {

GTMergeResult *result = [conflictIndex resultOfMergingAncestorEntry:ancestor ourEntry:ours theirEntry:theirs options:nil error:NULL];
expect(result).notTo(beNil());

NSString *conflictString = [[NSString alloc] initWithData:result.data encoding:NSUTF8StringEncoding];
NSString *expectedString = @"//\n"
"// main.m\n"
"// Test\n"
"//\n"
"// Created by Joe Ricioppo on 9/28/10.\n"
"// Copyright 2010 __MyCompanyName__. All rights reserved.\n"
"//\n"
"\n"
"#import <Cocoa/Cocoa.h>\n"
"\n"
"int main(int argc, char *argv[])\n"
"{\n"
"<<<<<<< main.m\n"
" //The meaning of life is 42\n"
"=======\n"
" //The meaning of life is 41\n"
">>>>>>> main.m\n"
" return NSApplicationMain(argc, (const char **) argv);\n"
"}\n"
"123456789\n"
"123456789\n"
"123456789\n"
"123456789!blah!\n";

expect(conflictString).to(equal(expectedString));
}];
});
});

afterEach(^{
[self tearDown];
});
Expand Down
42 changes: 0 additions & 42 deletions ObjectiveGitTests/GTRepositorySpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -260,48 +260,6 @@
});
});

describe(@"-contentsOfDiffWithAncestor:ourSide:theirSide:error:", ^{
it(@"should produce a nice merge conflict description", ^{
NSURL *mainURL = [repository.fileURL URLByAppendingPathComponent:@"main.m"];
NSData *mainData = [[NSFileManager defaultManager] contentsAtPath:mainURL.path];
expect(mainData).notTo(beNil());

NSString *mainString = [[NSString alloc] initWithData:mainData encoding:NSUTF8StringEncoding];
NSData *masterData = [[mainString stringByReplacingOccurrencesOfString:@"return" withString:@"//The meaning of life is 41\n return"] dataUsingEncoding:NSUTF8StringEncoding];
NSData *otherData = [[mainString stringByReplacingOccurrencesOfString:@"return" withString:@"//The meaning of life is 42\n return"] dataUsingEncoding:NSUTF8StringEncoding];

expect(@([[NSFileManager defaultManager] createFileAtPath:mainURL.path contents:masterData attributes:nil])).to(beTruthy());

GTIndex *index = [repository indexWithError:NULL];
expect(@([index addFile:mainURL.lastPathComponent error:NULL])).to(beTruthy());
GTReference *head = [repository headReferenceWithError:NULL];
GTCommit *parent = [repository lookUpObjectByOID:head.targetOID objectType:GTObjectTypeCommit error:NULL];
expect(parent).toNot(beNil());
GTTree *masterTree = [index writeTree:NULL];
expect(masterTree).toNot(beNil());

GTBranch *otherBranch = [repository lookUpBranchWithName:@"other-branch" type:GTBranchTypeLocal success:NULL error:NULL];
expect(otherBranch).toNot(beNil());
expect(@([repository checkoutReference:otherBranch.reference options:nil error:NULL])).to(beTruthy());

expect(@([[NSFileManager defaultManager] createFileAtPath:mainURL.path contents:otherData attributes:nil])).to(beTruthy());

index = [repository indexWithError:NULL];
expect(@([index addFile:mainURL.lastPathComponent error:NULL])).to(beTruthy());
GTTree *otherTree = [index writeTree:NULL];
expect(otherTree).toNot(beNil());

GTIndex *conflictIndex = [otherTree merge:masterTree ancestor:parent.tree error:NULL];
expect(@([conflictIndex hasConflicts])).to(beTruthy());

[conflictIndex enumerateConflictedFilesWithError:NULL usingBlock:^(GTIndexEntry * _Nonnull ancestor, GTIndexEntry * _Nonnull ours, GTIndexEntry * _Nonnull theirs, BOOL * _Nonnull stop) {

NSString *conflictString = [repository contentsOfDiffWithAncestor:ancestor ourSide:ours theirSide:theirs error:NULL];
expect(conflictString).to(equal(@"//\n// main.m\n// Test\n//\n// Created by Joe Ricioppo on 9/28/10.\n// Copyright 2010 __MyCompanyName__. All rights reserved.\n//\n\n#import <Cocoa/Cocoa.h>\n\nint main(int argc, char *argv[])\n{\n<<<<<<< file.txt\n //The meaning of life is 42\n=======\n //The meaning of life is 41\n>>>>>>> file.txt\n return NSApplicationMain(argc, (const char **) argv);\n}\n123456789\n123456789\n123456789\n123456789!blah!\n"));
}];
});
});

describe(@"-mergeBaseBetweenFirstOID:secondOID:error:", ^{
it(@"should find the merge base between two branches", ^{
NSError *error = nil;
Expand Down

0 comments on commit e0e103f

Please sign in to comment.