Skip to content

Commit efb15d1

Browse files
committed
Move merging to public method
1 parent 07d8c3f commit efb15d1

File tree

3 files changed

+129
-115
lines changed

3 files changed

+129
-115
lines changed

ObjectiveGit/GTRepository+Merging.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ typedef NS_OPTIONS(NSInteger, GTMergeAnalysis) {
4343
/// Retruns a (possibly empty) array with GTCommit objects. Will not be nil.
4444
- (NSArray <GTCommit *>*)mergeHeadEntriesWithError:(NSError **)error;
4545

46+
/// Merge Branch into current branch
47+
///
48+
/// analysis - The resulting analysis.
49+
/// fromBranch - The branch to merge from.
50+
/// error - The error if one occurred. Can be NULL.
51+
///
52+
/// Returns YES if the nerge was successful, NO otherwise (and `error`, if provided,
53+
/// will point to an error describing what happened).
54+
- (BOOL)mergeBranchIntoCurrentBranch:(GTBranch *)fromBranch withError:(NSError **)error;
55+
4656
/// Analyze which merge to perform.
4757
///
4858
/// analysis - The resulting analysis.

ObjectiveGit/GTRepository+Merging.m

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
#import "GTOID.h"
1111
#import "NSError+Git.h"
1212
#import "git2/errors.h"
13+
#import "GTCommit.h"
14+
#import "GTReference.h"
15+
#import "GTRepository+Committing.h"
16+
#import "GTRepository+Pull.h"
17+
#import "GTTree.h"
18+
#import "GTIndex.h"
19+
#import "GTIndexEntry.h"
1320

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

@@ -67,6 +74,116 @@ - (NSArray *)mergeHeadEntriesWithError:(NSError **)error {
6774
return entries;
6875
}
6976

77+
- (BOOL)mergeBranchIntoCurrentBranch:(GTBranch *)branch withError:(NSError **)error {
78+
// Check if merge is necessary
79+
GTBranch *localBranch = [self currentBranchWithError:error];
80+
if (!localBranch) {
81+
return NO;
82+
}
83+
84+
GTCommit *localCommit = [localBranch targetCommitWithError:error];
85+
if (!localCommit) {
86+
return NO;
87+
}
88+
89+
GTCommit *remoteCommit = [branch targetCommitWithError:error];
90+
if (!remoteCommit) {
91+
return NO;
92+
}
93+
94+
if ([localCommit.SHA isEqualToString:remoteCommit.SHA]) {
95+
// Local and remote tracking branch are already in sync
96+
return YES;
97+
}
98+
99+
GTMergeAnalysis analysis = GTMergeAnalysisNone;
100+
BOOL success = [self analyzeMerge:&analysis fromBranch:branch error:error];
101+
if (!success) {
102+
return NO;
103+
}
104+
105+
if (analysis & GTMergeAnalysisUpToDate) {
106+
// Nothing to do
107+
return YES;
108+
} else if (analysis & GTMergeAnalysisFastForward ||
109+
analysis & GTMergeAnalysisUnborn) {
110+
// Fast-forward branch
111+
NSString *message = [NSString stringWithFormat:@"merge %@: Fast-forward", branch.name];
112+
GTReference *reference = [localBranch.reference referenceByUpdatingTarget:remoteCommit.SHA message:message error:error];
113+
BOOL checkoutSuccess = [self checkoutReference:reference strategy:GTCheckoutStrategyForce error:error progressBlock:nil];
114+
115+
return checkoutSuccess;
116+
} else if (analysis & GTMergeAnalysisNormal) {
117+
// Do normal merge
118+
GTTree *localTree = localCommit.tree;
119+
GTTree *remoteTree = remoteCommit.tree;
120+
121+
// TODO: Find common ancestor
122+
GTTree *ancestorTree = nil;
123+
124+
// Merge
125+
GTIndex *index = [localTree merge:remoteTree ancestor:ancestorTree error:error];
126+
if (!index) {
127+
return NO;
128+
}
129+
130+
// Check for conflict
131+
if (index.hasConflicts) {
132+
NSMutableArray <NSString *>*files = [NSMutableArray array];
133+
[index enumerateConflictedFilesWithError:error usingBlock:^(GTIndexEntry * _Nonnull ancestor, GTIndexEntry * _Nonnull ours, GTIndexEntry * _Nonnull theirs, BOOL * _Nonnull stop) {
134+
[files addObject:ours.path];
135+
}];
136+
137+
if (error != NULL) {
138+
NSDictionary *userInfo = @{GTPullMergeConflictedFiles: files};
139+
*error = [NSError git_errorFor:GIT_ECONFLICT description:@"Merge conflict" userInfo:userInfo failureReason:nil];
140+
}
141+
142+
// Write conflicts
143+
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
144+
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
145+
checkout_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS;
146+
147+
git_annotated_commit *annotatedCommit;
148+
[self annotatedCommit:&annotatedCommit fromCommit:remoteCommit error:error];
149+
150+
git_merge(self.git_repository, (const git_annotated_commit **)&annotatedCommit, 1, &merge_opts, &checkout_opts);
151+
152+
return NO;
153+
}
154+
155+
GTTree *newTree = [index writeTreeToRepository:self error:error];
156+
if (!newTree) {
157+
return NO;
158+
}
159+
160+
// Create merge commit
161+
NSString *message = [NSString stringWithFormat:@"Merge branch '%@'", localBranch.shortName];
162+
NSArray *parents = @[ localCommit, remoteCommit ];
163+
164+
// FIXME: This is stepping on the local tree
165+
GTCommit *mergeCommit = [self createCommitWithTree:newTree message:message parents:parents updatingReferenceNamed:localBranch.name error:error];
166+
if (!mergeCommit) {
167+
return NO;
168+
}
169+
170+
BOOL success = [self checkoutReference:localBranch.reference strategy:GTCheckoutStrategyForce error:error progressBlock:nil];
171+
return success;
172+
}
173+
174+
return NO;
175+
}
176+
177+
- (BOOL)annotatedCommit:(git_annotated_commit **)annotatedCommit fromCommit:(GTCommit *)fromCommit error:(NSError **)error {
178+
int gitError = git_annotated_commit_lookup(annotatedCommit, self.git_repository, fromCommit.OID.git_oid);
179+
if (gitError != GIT_OK) {
180+
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to lookup annotated commit for %@", fromCommit];
181+
return NO;
182+
}
183+
184+
return YES;
185+
}
186+
70187
- (BOOL)analyzeMerge:(GTMergeAnalysis *)analysis fromBranch:(GTBranch *)fromBranch error:(NSError **)error {
71188
NSParameterAssert(analysis != NULL);
72189
NSParameterAssert(fromBranch != nil);

ObjectiveGit/GTRepository+Pull.m

Lines changed: 2 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,10 @@
99
#import "GTRepository+Pull.h"
1010

1111
#import "GTCommit.h"
12-
#import "GTIndex.h"
13-
#import "GTOID.h"
14-
#import "GTRemote.h"
15-
#import "GTReference.h"
16-
#import "GTRepository+Committing.h"
1712
#import "GTRepository+RemoteOperations.h"
18-
#import "GTTree.h"
1913
#import "NSError+Git.h"
20-
#import "GTIndexEntry.h"
2114
#import "git2/errors.h"
15+
#import "GTRepository+Merging.h"
2216

2317
NSString * const GTPullMergeConflictedFiles = @"GTPullMergeConflictedFiles";
2418

@@ -55,114 +49,7 @@ - (BOOL)pullBranch:(GTBranch *)branch fromRemote:(GTRemote *)remote withOptions:
5549
trackingBranch = branch;
5650
}
5751

58-
// Check if merge is necessary
59-
GTBranch *localBranch = [repo currentBranchWithError:error];
60-
if (!localBranch) {
61-
return NO;
62-
}
63-
64-
GTCommit *localCommit = [localBranch targetCommitWithError:error];
65-
if (!localCommit) {
66-
return NO;
67-
}
68-
69-
GTCommit *remoteCommit = [trackingBranch targetCommitWithError:error];
70-
if (!remoteCommit) {
71-
return NO;
72-
}
73-
74-
if ([localCommit.SHA isEqualToString:remoteCommit.SHA]) {
75-
// Local and remote tracking branch are already in sync
76-
return YES;
77-
}
78-
79-
GTMergeAnalysis analysis = GTMergeAnalysisNone;
80-
BOOL success = [self analyzeMerge:&analysis fromBranch:trackingBranch error:error];
81-
if (!success) {
82-
return NO;
83-
}
84-
85-
if (analysis & GTMergeAnalysisUpToDate) {
86-
// Nothing to do
87-
return YES;
88-
} else if (analysis & GTMergeAnalysisFastForward ||
89-
analysis & GTMergeAnalysisUnborn) {
90-
// Fast-forward branch
91-
NSString *message = [NSString stringWithFormat:@"merge %@/%@: Fast-forward", remote.name, trackingBranch.name];
92-
GTReference *reference = [localBranch.reference referenceByUpdatingTarget:remoteCommit.SHA message:message error:error];
93-
BOOL checkoutSuccess = [self checkoutReference:reference strategy:GTCheckoutStrategyForce error:error progressBlock:nil];
94-
95-
return checkoutSuccess;
96-
} else if (analysis & GTMergeAnalysisNormal) {
97-
// Do normal merge
98-
GTTree *localTree = localCommit.tree;
99-
GTTree *remoteTree = remoteCommit.tree;
100-
101-
// TODO: Find common ancestor
102-
GTTree *ancestorTree = nil;
103-
104-
// Merge
105-
GTIndex *index = [localTree merge:remoteTree ancestor:ancestorTree error:error];
106-
if (!index) {
107-
return NO;
108-
}
109-
110-
// Check for conflict
111-
if (index.hasConflicts) {
112-
NSMutableArray <NSString *>*files = [NSMutableArray array];
113-
[index enumerateConflictedFilesWithError:error usingBlock:^(GTIndexEntry * _Nonnull ancestor, GTIndexEntry * _Nonnull ours, GTIndexEntry * _Nonnull theirs, BOOL * _Nonnull stop) {
114-
[files addObject:ours.path];
115-
}];
116-
117-
if (error != NULL) {
118-
NSDictionary *userInfo = @{GTPullMergeConflictedFiles: files};
119-
*error = [NSError git_errorFor:GIT_ECONFLICT description:@"Merge conflict, Pull aborted." userInfo:userInfo failureReason:nil];
120-
}
121-
122-
// Write conflicts
123-
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
124-
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
125-
checkout_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS;
126-
127-
git_annotated_commit *annotatedCommit;
128-
[self annotatedCommit:&annotatedCommit fromCommit:remoteCommit error:error];
129-
130-
git_merge(repo.git_repository, (const git_annotated_commit **)&annotatedCommit, 1, &merge_opts, &checkout_opts);
131-
132-
return NO;
133-
}
134-
135-
GTTree *newTree = [index writeTreeToRepository:repo error:error];
136-
if (!newTree) {
137-
return NO;
138-
}
139-
140-
// Create merge commit
141-
NSString *message = [NSString stringWithFormat:@"Merge branch '%@'", localBranch.shortName];
142-
NSArray *parents = @[ localCommit, remoteCommit ];
143-
144-
// FIXME: This is stepping on the local tree
145-
GTCommit *mergeCommit = [repo createCommitWithTree:newTree message:message parents:parents updatingReferenceNamed:localBranch.name error:error];
146-
if (!mergeCommit) {
147-
return NO;
148-
}
149-
150-
BOOL success = [self checkoutReference:localBranch.reference strategy:GTCheckoutStrategyForce error:error progressBlock:nil];
151-
return success;
152-
}
153-
154-
return NO;
155-
}
156-
157-
- (BOOL)annotatedCommit:(git_annotated_commit **)annotatedCommit fromCommit:(GTCommit *)fromCommit error:(NSError **)error {
158-
int gitError = git_annotated_commit_lookup(annotatedCommit, self.git_repository, fromCommit.OID.git_oid);
159-
if (gitError != GIT_OK) {
160-
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to lookup annotated commit for %@", fromCommit];
161-
return NO;
162-
}
163-
164-
return YES;
165-
}
52+
return [repo mergeBranchIntoCurrentBranch:trackingBranch withError:error];
16653
}
16754

16855
@end

0 commit comments

Comments
 (0)