Skip to content

Commit 90dacb6

Browse files
committed
Merge pull request #464 from libgit2/pull
Pull
2 parents 6e5a9d9 + dae0b10 commit 90dacb6

12 files changed

+629
-66
lines changed

ObjectiveGit/GTRemote.m

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ - (NSUInteger)hash {
9595
return self.name.hash ^ self.URLString.hash;
9696
}
9797

98+
- (NSString *)description {
99+
return [NSString stringWithFormat:@"<%@: %p> name: %@, URLString: %@", NSStringFromClass([self class]), self, self.name, self.URLString];
100+
}
101+
98102
#pragma mark API
99103

100104
+ (BOOL)isValidRemoteName:(NSString *)name {

ObjectiveGit/GTRepository+Pull.h

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// GTRepository+Pull.h
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Ben Chatelain on 6/17/15.
6+
// Copyright © 2015 GitHub, Inc. All rights reserved.
7+
//
8+
9+
#import "GTRepository.h"
10+
#import "git2/merge.h"
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
/// An enum describing the result of the merge analysis.
15+
/// See `git_merge_analysis_t`.
16+
typedef NS_ENUM(NSInteger, GTMergeAnalysis) {
17+
GTMergeAnalysisNone = GIT_MERGE_ANALYSIS_NONE,
18+
GTMergeAnalysisNormal = GIT_MERGE_ANALYSIS_NORMAL,
19+
GTMergeAnalysisUpToDate = GIT_MERGE_ANALYSIS_UP_TO_DATE,
20+
GTMergeAnalysisUnborn = GIT_MERGE_ANALYSIS_UNBORN,
21+
GTMergeAnalysisFastForward = GIT_MERGE_ANALYSIS_FASTFORWARD,
22+
};
23+
24+
typedef void (^GTRemoteFetchTransferProgressBlock)(const git_transfer_progress *progress, BOOL *stop);
25+
26+
@interface GTRepository (Pull)
27+
28+
#pragma mark - Pull
29+
30+
/// Pull a single branch from a remote.
31+
///
32+
/// branch - The branch to pull.
33+
/// remote - The remote to pull from.
34+
/// options - Options applied to the fetch operation.
35+
/// Recognized options are:
36+
/// `GTRepositoryRemoteOptionsCredentialProvider`
37+
/// error - The error if one occurred. Can be NULL.
38+
/// progressBlock - An optional callback for monitoring progress.
39+
///
40+
/// Returns YES if the pull was successful, NO otherwise (and `error`, if provided,
41+
/// will point to an error describing what happened).
42+
- (BOOL)pullBranch:(GTBranch *)branch fromRemote:(GTRemote *)remote withOptions:(nullable NSDictionary *)options error:(NSError **)error progress:(nullable GTRemoteFetchTransferProgressBlock)progressBlock;
43+
44+
/// Analyze which merge to perform.
45+
///
46+
/// analysis - The resulting analysis.
47+
/// fromBranch - The branch to merge from.
48+
/// error - The error if one occurred. Can be NULL.
49+
///
50+
/// Returns YES if the analysis was successful, NO otherwise (and `error`, if provided,
51+
/// will point to an error describing what happened).
52+
- (BOOL)analyzeMerge:(GTMergeAnalysis *)analysis fromBranch:(GTBranch *)fromBranch error:(NSError **)error;
53+
54+
@end
55+
56+
NS_ASSUME_NONNULL_END

ObjectiveGit/GTRepository+Pull.m

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
//
2+
// GTRepository+Pull.m
3+
// ObjectiveGitFramework
4+
//
5+
// Created by Ben Chatelain on 6/17/15.
6+
// Copyright © 2015 GitHub, Inc. All rights reserved.
7+
//
8+
9+
#import "GTRepository+Pull.h"
10+
11+
#import "GTCommit.h"
12+
#import "GTIndex.h"
13+
#import "GTOID.h"
14+
#import "GTRemote.h"
15+
#import "GTReference.h"
16+
#import "GTRepository+Committing.h"
17+
#import "GTRepository+RemoteOperations.h"
18+
#import "GTTree.h"
19+
#import "NSError+Git.h"
20+
#import "git2/errors.h"
21+
22+
@implementation GTRepository (Pull)
23+
24+
#pragma mark - Pull
25+
26+
- (BOOL)pullBranch:(GTBranch *)branch fromRemote:(GTRemote *)remote withOptions:(NSDictionary *)options error:(NSError **)error progress:(GTRemoteFetchTransferProgressBlock)progressBlock {
27+
NSParameterAssert(branch != nil);
28+
NSParameterAssert(remote != nil);
29+
30+
GTRepository *repo = branch.repository;
31+
32+
if (![self fetchRemote:remote withOptions:options error:error progress:progressBlock]) {
33+
return NO;
34+
}
35+
36+
// Get tracking branch after fetch so that it is up-to-date and doesn't need to be refreshed from disk
37+
GTBranch *trackingBranch;
38+
if (branch.branchType == GTBranchTypeLocal) {
39+
BOOL success = NO;
40+
trackingBranch = [branch trackingBranchWithError:error success:&success];
41+
if (!success) {
42+
if (error != NULL) *error = [NSError git_errorFor:GIT_ERROR description:@"Tracking branch not found for %@", branch.name];
43+
return NO;
44+
}
45+
else if (!trackingBranch) {
46+
// Error should already be provided by libgit2
47+
return NO;
48+
}
49+
}
50+
else {
51+
// When given a remote branch, use it as the tracking branch
52+
trackingBranch = branch;
53+
}
54+
55+
// Check if merge is necessary
56+
GTBranch *localBranch = [repo currentBranchWithError:error];
57+
if (!localBranch) {
58+
return NO;
59+
}
60+
61+
GTCommit *localCommit = [localBranch targetCommitWithError:error];
62+
if (!localCommit) {
63+
return NO;
64+
}
65+
66+
GTCommit *remoteCommit = [trackingBranch targetCommitWithError:error];
67+
if (!remoteCommit) {
68+
return NO;
69+
}
70+
71+
if ([localCommit.SHA isEqualToString:remoteCommit.SHA]) {
72+
// Local and remote tracking branch are already in sync
73+
return YES;
74+
}
75+
76+
GTMergeAnalysis analysis = GTMergeAnalysisNone;
77+
BOOL success = [self analyzeMerge:&analysis fromBranch:trackingBranch error:error];
78+
if (!success) {
79+
return NO;
80+
}
81+
82+
if (analysis & GTMergeAnalysisUpToDate) {
83+
// Nothing to do
84+
return YES;
85+
} else if (analysis & GTMergeAnalysisFastForward ||
86+
analysis & GTMergeAnalysisUnborn) {
87+
// Fast-forward branch
88+
NSString *message = [NSString stringWithFormat:@"merge %@/%@: Fast-forward", remote.name, trackingBranch.name];
89+
GTReference *reference = [localBranch.reference referenceByUpdatingTarget:remoteCommit.SHA message:message error:error];
90+
BOOL checkoutSuccess = [self checkoutReference:reference strategy:GTCheckoutStrategyForce error:error progressBlock:nil];
91+
92+
return checkoutSuccess;
93+
} else if (analysis & GTMergeAnalysisNormal) {
94+
// Do normal merge
95+
GTTree *localTree = localCommit.tree;
96+
GTTree *remoteTree = remoteCommit.tree;
97+
98+
// TODO: Find common ancestor
99+
GTTree *ancestorTree = nil;
100+
101+
// Merge
102+
GTIndex *index = [localTree merge:remoteTree ancestor:ancestorTree error:error];
103+
if (!index) {
104+
return NO;
105+
}
106+
107+
// Check for conflict
108+
if (index.hasConflicts) {
109+
if (error != NULL) *error = [NSError git_errorFor:GIT_ECONFLICT description:@"Merge conflict, pull aborted"];
110+
return NO;
111+
}
112+
113+
GTTree *newTree = [index writeTreeToRepository:repo error:error];
114+
if (!newTree) {
115+
return NO;
116+
}
117+
118+
// Create merge commit
119+
NSString *message = [NSString stringWithFormat:@"Merge branch '%@'", localBranch.shortName];
120+
NSArray *parents = @[ localCommit, remoteCommit ];
121+
122+
// FIXME: This is stepping on the local tree
123+
GTCommit *mergeCommit = [repo createCommitWithTree:newTree message:message parents:parents updatingReferenceNamed:localBranch.name error:error];
124+
if (!mergeCommit) {
125+
return NO;
126+
}
127+
128+
BOOL success = [self checkoutReference:localBranch.reference strategy:GTCheckoutStrategyForce error:error progressBlock:nil];
129+
return success;
130+
}
131+
132+
return NO;
133+
}
134+
135+
- (BOOL)analyzeMerge:(GTMergeAnalysis *)analysis fromBranch:(GTBranch *)fromBranch error:(NSError **)error {
136+
NSParameterAssert(analysis != NULL);
137+
NSParameterAssert(fromBranch != nil);
138+
139+
GTCommit *fromCommit = [fromBranch targetCommitWithError:error];
140+
if (!fromCommit) {
141+
return NO;
142+
}
143+
144+
git_annotated_commit *annotatedCommit;
145+
146+
int gitError = git_annotated_commit_lookup(&annotatedCommit, self.git_repository, fromCommit.OID.git_oid);
147+
if (gitError != GIT_OK) {
148+
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to lookup annotated comit for %@", fromCommit];
149+
return NO;
150+
}
151+
152+
// Allow fast-forward or normal merge
153+
git_merge_preference_t preference = GIT_MERGE_PREFERENCE_NONE;
154+
155+
// Merge analysis
156+
gitError = git_merge_analysis((git_merge_analysis_t *)analysis, &preference, self.git_repository, (const git_annotated_commit **) &annotatedCommit, 1);
157+
if (gitError != GIT_OK) {
158+
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to analyze merge"];
159+
return NO;
160+
}
161+
162+
// Cleanup
163+
git_annotated_commit_free(annotatedCommit);
164+
165+
return YES;
166+
}
167+
168+
@end

ObjectiveGit/GTRepository.m

+8-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ @interface GTRepository ()
9191
@implementation GTRepository
9292

9393
- (NSString *)description {
94+
if (self.isBare) {
95+
return [NSString stringWithFormat:@"<%@: %p> (bare) gitDirectoryURL: %@", self.class, self, self.gitDirectoryURL];
96+
}
9497
return [NSString stringWithFormat:@"<%@: %p> fileURL: %@", self.class, self, self.fileURL];
9598
}
9699

@@ -366,7 +369,11 @@ - (GTReference *)headReferenceWithError:(NSError **)error {
366369
git_reference *headRef;
367370
int gitError = git_repository_head(&headRef, self.git_repository);
368371
if (gitError != GIT_OK) {
369-
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to get HEAD"];
372+
NSString *unborn = @"";
373+
if (gitError == GIT_EUNBORNBRANCH) {
374+
unborn = @" (unborn)";
375+
}
376+
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to get HEAD%@", unborn];
370377
return nil;
371378
}
372379

ObjectiveGit/ObjectiveGit.h

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ FOUNDATION_EXPORT const unsigned char ObjectiveGitVersionString[];
4040
#import <ObjectiveGit/GTRepository+References.h>
4141
#import <ObjectiveGit/GTRepository+RemoteOperations.h>
4242
#import <ObjectiveGit/GTRepository+Reset.h>
43+
#import <ObjectiveGit/GTRepository+Pull.h>
4344
#import <ObjectiveGit/GTEnumerator.h>
4445
#import <ObjectiveGit/GTCommit.h>
4546
#import <ObjectiveGit/GTCredential.h>

0 commit comments

Comments
 (0)