Skip to content

Commit 00340e9

Browse files
committed
Rework how merges are made
This makes us perform only one merge operation, instead of first merging trees, then merging commits to get the merge conflicts in the workdir.
1 parent b3e13c1 commit 00340e9

File tree

2 files changed

+54
-41
lines changed

2 files changed

+54
-41
lines changed

ObjectiveGit/GTRepository+Merging.m

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,32 @@ - (BOOL)analyzeMerge:(GTMergeAnalysis *)analysis preference:(GTMergePreference *
103103
return YES;
104104
}
105105

106+
- (BOOL)mergeAnnotatedCommits:(NSArray <GTAnnotatedCommit *> *)annotatedCommits mergeOptions:(NSDictionary *)mergeOptions checkoutOptions:(GTCheckoutOptions *)checkoutOptions error:(NSError **)error {
107+
NSParameterAssert(annotatedCommits);
108+
109+
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
110+
111+
const git_annotated_commit **annotated_commits = NULL;
112+
if (annotatedCommits.count > 0) {
113+
annotated_commits = calloc(annotatedCommits.count, sizeof(git_annotated_commit *));
114+
for (NSUInteger i = 0; i < annotatedCommits.count; i++){
115+
annotated_commits[i] = [annotatedCommits[i] git_annotated_commit];
116+
}
117+
}
118+
@onExit {
119+
free(annotated_commits);
120+
};
121+
122+
int gitError = git_merge(self.git_repository, annotated_commits, annotatedCommits.count, &merge_opts, checkoutOptions.git_checkoutOptions);
123+
if (gitError != GIT_OK) {
124+
if (error != NULL) {
125+
*error = [NSError git_errorFor:gitError description:@"Merge failed"];
126+
}
127+
return NO;
128+
}
129+
return YES;
130+
}
131+
106132
- (BOOL)mergeBranchIntoCurrentBranch:(GTBranch *)branch withError:(NSError **)error {
107133
// Check if merge is necessary
108134
GTBranch *localBranch = [self currentBranchWithError:error];
@@ -156,54 +182,51 @@ - (BOOL)mergeBranchIntoCurrentBranch:(GTBranch *)branch withError:(NSError **)er
156182
}
157183

158184
// Do normal merge
159-
GTTree *localTree = localCommit.tree;
160-
GTTree *remoteTree = remoteCommit.tree;
161-
162-
// TODO: Find common ancestor
163-
GTTree *ancestorTree = nil;
164-
165-
// Merge
166-
GTIndex *index = [localTree merge:remoteTree ancestor:ancestorTree error:error];
167-
if (!index) {
185+
GTIndex *index = [self indexWithError:error];
186+
if (index == nil) {
168187
return NO;
169188
}
170189

171-
// Check for conflict
172-
if (index.hasConflicts) {
173-
NSMutableArray <NSString *>*files = [NSMutableArray array];
174-
[index enumerateConflictedFilesWithError:error usingBlock:^(GTIndexEntry * _Nonnull ancestor, GTIndexEntry * _Nonnull ours, GTIndexEntry * _Nonnull theirs, BOOL * _Nonnull stop) {
175-
[files addObject:ours.path];
176-
}];
190+
NSError *mergeError = nil;
191+
GTCheckoutOptions *checkoutOptions = [GTCheckoutOptions checkoutOptionsWithStrategy:GTCheckoutStrategySafe|GTCheckoutStrategyAllowConflicts];
177192

193+
success = [self mergeAnnotatedCommits:@[remoteAnnotatedCommit]
194+
mergeOptions:nil
195+
checkoutOptions:checkoutOptions
196+
error:&mergeError];
197+
if (!success) {
178198
if (error != NULL) {
179-
NSDictionary *userInfo = @{GTPullMergeConflictedFiles: files};
180-
*error = [NSError git_errorFor:GIT_ECONFLICT description:@"Merge conflict" userInfo:userInfo failureReason:nil];
199+
*error = mergeError;
181200
}
201+
return NO;
202+
}
182203

183-
// Write conflicts
184-
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
185-
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
186-
checkout_opts.checkout_strategy = (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS);
187-
188-
git_annotated_commit *annotatedCommit;
189-
[self annotatedCommit:&annotatedCommit fromCommit:remoteCommit error:error];
190-
191-
git_merge(self.git_repository, (const git_annotated_commit **)&annotatedCommit, 1, &merge_opts, &checkout_opts);
204+
if (![index refresh:error]) {
205+
return NO;
206+
}
192207

208+
if (index.hasConflicts) {
209+
if (error) {
210+
NSMutableArray <NSString *> *files = [NSMutableArray array];
211+
[index enumerateConflictedFilesWithError:error usingBlock:^(GTIndexEntry * _Nonnull ancestor, GTIndexEntry * _Nonnull ours, GTIndexEntry * _Nonnull theirs, BOOL * _Nonnull stop) {
212+
[files addObject:ours.path];
213+
}];
214+
NSDictionary *userInfo = @{GTPullMergeConflictedFiles: files};
215+
*error = [NSError git_errorFor:GIT_EMERGECONFLICT description:@"Merge conflict" userInfo:userInfo failureReason:nil];
216+
}
193217
return NO;
194218
}
195219

196-
GTTree *newTree = [index writeTreeToRepository:self error:error];
197-
if (!newTree) {
220+
GTTree *mergedTree = [index writeTree:error];
221+
if (mergedTree == nil) {
198222
return NO;
199223
}
200224

201225
// Create merge commit
202226
NSString *message = [NSString stringWithFormat:@"Merge branch '%@'", localBranch.shortName];
203227
NSArray *parents = @[ localCommit, remoteCommit ];
204228

205-
// FIXME: This is stepping on the local tree
206-
GTCommit *mergeCommit = [self createCommitWithTree:newTree message:message parents:parents updatingReferenceNamed:localBranch.reference.name error:error];
229+
GTCommit *mergeCommit = [self createCommitWithTree:mergedTree message:message parents:parents updatingReferenceNamed:localBranch.reference.name error:error];
207230
if (!mergeCommit) {
208231
return NO;
209232
}
@@ -287,16 +310,6 @@ - (NSString * _Nullable)contentsOfDiffWithAncestor:(GTIndexEntry *)ancestor ourS
287310
return mergedContent;
288311
}
289312

290-
- (BOOL)annotatedCommit:(git_annotated_commit **)annotatedCommit fromCommit:(GTCommit *)fromCommit error:(NSError **)error {
291-
int gitError = git_annotated_commit_lookup(annotatedCommit, self.git_repository, fromCommit.OID.git_oid);
292-
if (gitError != GIT_OK) {
293-
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to lookup annotated commit for %@", fromCommit];
294-
return NO;
295-
}
296-
297-
return YES;
298-
}
299-
300313
- (BOOL)analyzeMerge:(GTMergeAnalysis *)analysis fromBranch:(GTBranch *)fromBranch error:(NSError **)error {
301314
NSParameterAssert(analysis != NULL);
302315
NSParameterAssert(fromBranch != nil);

ObjectiveGitTests/GTRepository+PullSpec.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@
260260
expect(error.userInfo[GTPullMergeConflictedFiles]).to(equal(@[@"test.txt"]));
261261
expect(fileContents).notTo(equal(@"TestLocal"));
262262
expect([localRepo mergeHeadEntriesWithError:nil]).to(equal(@[remoteCommit.OID]));
263-
expect([localRepo preparedMessageWithError:nil]).to(beginWith(@"Merge commit"));
263+
expect([localRepo preparedMessageWithError:nil]).to(beginWith(@"Merge remote-tracking branch"));
264264
expect(error.localizedDescription).to(equal(@"Merge conflict"));
265265
expect(@(transferProgressed)).to(beTruthy());
266266
});

0 commit comments

Comments
 (0)