Skip to content

Commit 6e5a9d9

Browse files
committed
Merge pull request #501 from libgit2/stash_apply
Add stash apply & pop support
2 parents 0dc6c60 + dcc44af commit 6e5a9d9

File tree

3 files changed

+157
-1
lines changed

3 files changed

+157
-1
lines changed

ObjectiveGit/GTRepository+Stashing.h

+39
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@ typedef NS_OPTIONS(NSInteger, GTRepositoryStashFlag) {
1919
GTRepositoryStashFlagIncludeIgnored = GIT_STASH_INCLUDE_IGNORED
2020
};
2121

22+
/// Flags for -applyStashAtIndex:flags:error: and
23+
/// -popStashAtIndex:flags:error.
24+
/// Those can be ORed together. See git_stash_apply_flags for additional information.
25+
typedef NS_OPTIONS(NSInteger, GTRepositoryStashApplyFlag) {
26+
GTRepositoryStashApplyFlagDefault = GIT_STASH_APPLY_DEFAULT,
27+
GTRepositoryStashApplyFlagReinstateIndex = GIT_STASH_APPLY_REINSTATE_INDEX,
28+
};
29+
30+
/// Enum representing the current state of a stash apply/pop operation.
31+
/// See git_stash_apply_progress_t for additional information.
32+
typedef NS_ENUM(NSInteger, GTRepositoryStashApplyProgress) {
33+
GTRepositoryStashApplyProgressNone = GIT_STASH_APPLY_PROGRESS_NONE,
34+
GTRepositoryStashApplyProgressLoadingStash = GIT_STASH_APPLY_PROGRESS_LOADING_STASH,
35+
GTRepositoryStashApplyProgressAnalyzeIndex = GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX,
36+
GTRepositoryStashApplyProgressAnalyzeModified = GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED,
37+
GTRepositoryStashApplyProgressAnalyzeUntracked = GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED,
38+
GTRepositoryStashApplyProgressGheckoutUntracked = GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED,
39+
GTRepositoryStashApplyProgressCheckoutModified = GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED,
40+
GTRepositoryStashApplyProgressDone = GIT_STASH_APPLY_PROGRESS_DONE,
41+
};
42+
2243
NS_ASSUME_NONNULL_BEGIN
2344

2445
@interface GTRepository (Stashing)
@@ -41,6 +62,24 @@ NS_ASSUME_NONNULL_BEGIN
4162
/// will cause enumeration to stop after the block returns. Must not be nil.
4263
- (void)enumerateStashesUsingBlock:(void (^)(NSUInteger index, NSString * __nullable message, GTOID * __nullable oid, BOOL *stop))block;
4364

65+
/// Apply stashed changes.
66+
///
67+
/// index - The index of the stash to apply. 0 is the latest one.
68+
/// flags - The flags to use when applying the stash.
69+
/// error - If not NULL, set to any error that occurred.
70+
///
71+
/// Returns YES if the requested stash was successfully applied, NO otherwise.
72+
- (BOOL)applyStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock;
73+
74+
/// Pop stashed changes.
75+
///
76+
/// index - The index of the stash to apply. 0 is the most recent stash.
77+
/// flags - The flags to use when applying the stash.
78+
/// error - If not NULL, set to any error that occurred.
79+
///
80+
/// Returns YES if the requested stash was successfully applied, NO otherwise.
81+
- (BOOL)popStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock;
82+
4483
/// Drop a stash from the repository's list of stashes.
4584
///
4685
/// index - The index of the stash to drop, where 0 is the most recent stash.

ObjectiveGit/GTRepository+Stashing.m

+44-1
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,53 @@ - (void)enumerateStashesUsingBlock:(GTRepositoryStashEnumerationBlock)block {
5050
git_stash_foreach(self.git_repository, &stashEnumerationCallback, (__bridge void *)block);
5151
}
5252

53+
static int stashApplyProgressCallback(git_stash_apply_progress_t progress, void *payload) {
54+
void (^block)(GTRepositoryStashApplyProgress, BOOL *) = (__bridge id)payload;
55+
56+
BOOL stop = NO;
57+
block((GTRepositoryStashApplyProgress)progress, &stop);
58+
59+
return (stop ? GIT_EUSER : 0);
60+
}
61+
62+
- (BOOL)applyStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock {
63+
git_stash_apply_options stash_options = GIT_STASH_APPLY_OPTIONS_INIT;
64+
65+
stash_options.flags = (git_stash_apply_flags)flags;
66+
if (progressBlock != nil) {
67+
stash_options.progress_cb = stashApplyProgressCallback;
68+
stash_options.progress_payload = (__bridge void *)progressBlock;
69+
}
70+
71+
int gitError = git_stash_apply(self.git_repository, index, &stash_options);
72+
if (gitError != GIT_OK) {
73+
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Stash apply failed" failureReason:@"The stash at index %ld couldn't be applied.", (unsigned long)index];
74+
return NO;
75+
}
76+
return YES;
77+
}
78+
79+
- (BOOL)popStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock {
80+
git_stash_apply_options stash_options = GIT_STASH_APPLY_OPTIONS_INIT;
81+
82+
stash_options.flags = (git_stash_apply_flags)flags;
83+
if (progressBlock != nil) {
84+
stash_options.progress_cb = stashApplyProgressCallback;
85+
stash_options.progress_payload = (__bridge void *)progressBlock;
86+
}
87+
88+
int gitError = git_stash_pop(self.git_repository, index, &stash_options);
89+
if (gitError != GIT_OK) {
90+
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Stash pop failed" failureReason:@"The stash at index %ld couldn't be applied.", (unsigned long)index];
91+
return NO;
92+
}
93+
return YES;
94+
}
95+
5396
- (BOOL)dropStashAtIndex:(NSUInteger)index error:(NSError **)error {
5497
int gitError = git_stash_drop(self.git_repository, index);
5598
if (gitError != GIT_OK) {
56-
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to drop stash."];
99+
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Stash drop failed" failureReason:@"The stash at index %ld couldn't be dropped", (unsigned long)index];
57100
return NO;
58101
}
59102

ObjectiveGitTests/GTRepositoryStashingSpec.m

+74
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,80 @@
120120
expect(@(lastIndex)).to(equal(@2));
121121
});
122122

123+
it(@"should apply stashes", ^{
124+
expect(@([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());
125+
126+
NSError *error = nil;
127+
GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagIncludeUntracked error:&error];
128+
expect(stash).notTo(beNil());
129+
expect(error).to(beNil());
130+
131+
__block BOOL progressCalled = NO;
132+
BOOL success = [repository applyStashAtIndex:0 flags:GTRepositoryStashApplyFlagDefault error:&error progressBlock:^void(GTRepositoryStashApplyProgress step, BOOL *stop) {
133+
progressCalled = YES;
134+
}];
135+
expect(@(success)).to(beTruthy());
136+
expect(@(progressCalled)).to(beTruthy());
137+
expect(error).to(beNil());
138+
139+
expect([NSString stringWithContentsOfURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] encoding:NSUTF8StringEncoding error:NULL]).to(equal(@"foobar"));
140+
});
141+
142+
143+
it(@"should drop stashes", ^{
144+
expect(@([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());
145+
146+
NSError *error = nil;
147+
GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagIncludeUntracked error:&error];
148+
expect(stash).notTo(beNil());
149+
expect(error).to(beNil());
150+
151+
BOOL success = [repository dropStashAtIndex:0 error:&error];
152+
expect(@(success)).to(beTruthy());
153+
expect(error).to(beNil());
154+
});
155+
156+
it(@"should fail to apply/drop unknown stashes", ^{
157+
NSError *error = nil;
158+
BOOL success = NO;
159+
160+
__block NSUInteger lastStashIndex = 0;
161+
[repository enumerateStashesUsingBlock:^(NSUInteger index, NSString * __nullable message, GTOID * __nullable oid, BOOL * __nonnull stop) {
162+
lastStashIndex = index;
163+
}];
164+
165+
success = [repository applyStashAtIndex:(lastStashIndex + 1) flags:GTRepositoryStashApplyFlagDefault error:&error progressBlock:nil];
166+
expect(@(success)).to(beFalsy());
167+
expect(error).notTo(beNil());
168+
expect(error.domain).to(equal(GTGitErrorDomain));
169+
expect(@(error.code)).to(equal(@(GIT_ENOTFOUND)));
170+
171+
success = [repository dropStashAtIndex:(lastStashIndex + 1) error:&error];
172+
expect(@(success)).to(beFalsy());
173+
expect(error).notTo(beNil());
174+
expect(error.domain).to(equal(GTGitErrorDomain));
175+
expect(@(error.code)).to(equal(@(GIT_ENOTFOUND)));
176+
});
177+
178+
it(@"should fail to apply conflicting stashes", ^{
179+
expect(@([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());
180+
181+
NSError *error = nil;
182+
GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagIncludeUntracked error:&error];
183+
expect(stash).notTo(beNil());
184+
expect(error).to(beNil());
185+
186+
187+
expect(@([@"barfoo" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());
188+
189+
BOOL success = [repository applyStashAtIndex:0 flags:GTRepositoryStashApplyFlagDefault error:&error progressBlock:nil];
190+
expect(@(success)).to(beFalsy());
191+
expect(error).notTo(beNil());
192+
193+
expect(error.domain).to(equal(GTGitErrorDomain));
194+
expect(@(error.code)).to(equal(@(GIT_ECONFLICT)));
195+
});
196+
123197
afterEach(^{
124198
[self tearDown];
125199
});

0 commit comments

Comments
 (0)