Skip to content

Wrap git_merge_file #630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
13 changes: 13 additions & 0 deletions ObjectiveGit/GTRepository+Merging.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

#import "GTRepository.h"
#import "GTIndexEntry.h"
#import "git2/merge.h"

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -52,6 +53,18 @@ 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
83 changes: 83 additions & 0 deletions ObjectiveGit/GTRepository+Merging.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#import "GTTree.h"
#import "GTIndex.h"
#import "GTIndexEntry.h"
#import "GTOdbObject.h"
#import "GTObjectDatabase.h"

typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *stats, BOOL *stop);

Expand Down Expand Up @@ -170,6 +172,87 @@ - (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;
}

char *cString = malloc(result.len * sizeof(char *) + 1);
strncpy(cString, result.ptr, result.len);
cString[result.len] = '\0';

NSString *mergedContent = [[NSString alloc] initWithCString:cString encoding:NSUTF8StringEncoding];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe -initWithBytesNoCopy:length:encoding:freeWhenDone: ? That should allow you to just pass result.ptr/result.len, skipping the need for the malloc/strncpy step. You would have to result.ptr = NULL though, so git_merge_file_result_free doesn't break it by freeing before returning.


free(cString);

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
42 changes: 42 additions & 0 deletions ObjectiveGitTests/GTRepositorySpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,48 @@
});
});

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