Skip to content
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

Add stash apply & pop support #501

Merged
merged 4 commits into from
Sep 8, 2015
Merged
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
39 changes: 39 additions & 0 deletions ObjectiveGit/GTRepository+Stashing.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ typedef NS_OPTIONS(NSInteger, GTRepositoryStashFlag) {
GTRepositoryStashFlagIncludeIgnored = GIT_STASH_INCLUDE_IGNORED
};

/// Flags for -applyStashAtIndex:flags:error: and
/// -popStashAtIndex:flags:error.
/// Those can be ORed together. See git_stash_apply_flags for additional information.
typedef NS_OPTIONS(NSInteger, GTRepositoryStashApplyFlag) {
GTRepositoryStashApplyFlagDefault = GIT_STASH_APPLY_DEFAULT,
GTRepositoryStashApplyFlagReinstateIndex = GIT_STASH_APPLY_REINSTATE_INDEX,
};

/// Enum representing the current state of a stash apply/pop operation.
/// See git_stash_apply_progress_t for additional information.
typedef NS_ENUM(NSInteger, GTRepositoryStashApplyProgress) {
GTRepositoryStashApplyProgressNone = GIT_STASH_APPLY_PROGRESS_NONE,
GTRepositoryStashApplyProgressLoadingStash = GIT_STASH_APPLY_PROGRESS_LOADING_STASH,
GTRepositoryStashApplyProgressAnalyzeIndex = GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX,
GTRepositoryStashApplyProgressAnalyzeModified = GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED,
GTRepositoryStashApplyProgressAnalyzeUntracked = GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED,
GTRepositoryStashApplyProgressGheckoutUntracked = GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED,
GTRepositoryStashApplyProgressCheckoutModified = GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED,
GTRepositoryStashApplyProgressDone = GIT_STASH_APPLY_PROGRESS_DONE,
};

NS_ASSUME_NONNULL_BEGIN

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

/// Apply stashed changes.
///
/// index - The index of the stash to apply. 0 is the latest one.
/// flags - The flags to use when applying the stash.
/// error - If not NULL, set to any error that occurred.
///
/// Returns YES if the requested stash was successfully applied, NO otherwise.
- (BOOL)applyStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock;

/// Pop stashed changes.
///
/// index - The index of the stash to apply. 0 is the most recent stash.
/// flags - The flags to use when applying the stash.
/// error - If not NULL, set to any error that occurred.
///
/// Returns YES if the requested stash was successfully applied, NO otherwise.
- (BOOL)popStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock;

/// Drop a stash from the repository's list of stashes.
///
/// index - The index of the stash to drop, where 0 is the most recent stash.
Expand Down
45 changes: 44 additions & 1 deletion ObjectiveGit/GTRepository+Stashing.m
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,53 @@ - (void)enumerateStashesUsingBlock:(GTRepositoryStashEnumerationBlock)block {
git_stash_foreach(self.git_repository, &stashEnumerationCallback, (__bridge void *)block);
}

static int stashApplyProgressCallback(git_stash_apply_progress_t progress, void *payload) {
void (^block)(GTRepositoryStashApplyProgress, BOOL *) = (__bridge id)payload;

BOOL stop = NO;
block((GTRepositoryStashApplyProgress)progress, &stop);

return (stop ? GIT_EUSER : 0);
}

- (BOOL)applyStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock {
git_stash_apply_options stash_options = GIT_STASH_APPLY_OPTIONS_INIT;

stash_options.flags = (git_stash_apply_flags)flags;
if (progressBlock != nil) {
stash_options.progress_cb = stashApplyProgressCallback;
stash_options.progress_payload = (__bridge void *)progressBlock;
}

int gitError = git_stash_apply(self.git_repository, index, &stash_options);
if (gitError != GIT_OK) {
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];
return NO;
}
return YES;
}

- (BOOL)popStashAtIndex:(NSUInteger)index flags:(GTRepositoryStashApplyFlag)flags error:(NSError **)error progressBlock:(nullable void (^)(GTRepositoryStashApplyProgress progress, BOOL *stop))progressBlock {
git_stash_apply_options stash_options = GIT_STASH_APPLY_OPTIONS_INIT;

stash_options.flags = (git_stash_apply_flags)flags;
if (progressBlock != nil) {
stash_options.progress_cb = stashApplyProgressCallback;
stash_options.progress_payload = (__bridge void *)progressBlock;
}

int gitError = git_stash_pop(self.git_repository, index, &stash_options);
if (gitError != GIT_OK) {
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];
return NO;
}
return YES;
}

- (BOOL)dropStashAtIndex:(NSUInteger)index error:(NSError **)error {
int gitError = git_stash_drop(self.git_repository, index);
if (gitError != GIT_OK) {
if (error != NULL) *error = [NSError git_errorFor:gitError description:@"Failed to drop stash."];
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];
return NO;
}

Expand Down
74 changes: 74 additions & 0 deletions ObjectiveGitTests/GTRepositoryStashingSpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,80 @@
expect(@(lastIndex)).to(equal(@2));
});

it(@"should apply stashes", ^{
expect(@([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());

NSError *error = nil;
GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagIncludeUntracked error:&error];
expect(stash).notTo(beNil());
expect(error).to(beNil());

__block BOOL progressCalled = NO;
BOOL success = [repository applyStashAtIndex:0 flags:GTRepositoryStashApplyFlagDefault error:&error progressBlock:^void(GTRepositoryStashApplyProgress step, BOOL *stop) {
progressCalled = YES;
}];
expect(@(success)).to(beTruthy());
expect(@(progressCalled)).to(beTruthy());
expect(error).to(beNil());

expect([NSString stringWithContentsOfURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] encoding:NSUTF8StringEncoding error:NULL]).to(equal(@"foobar"));
});


it(@"should drop stashes", ^{
expect(@([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());

NSError *error = nil;
GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagIncludeUntracked error:&error];
expect(stash).notTo(beNil());
expect(error).to(beNil());

BOOL success = [repository dropStashAtIndex:0 error:&error];
expect(@(success)).to(beTruthy());
expect(error).to(beNil());
});

it(@"should fail to apply/drop unknown stashes", ^{
NSError *error = nil;
BOOL success = NO;

__block NSUInteger lastStashIndex = 0;
[repository enumerateStashesUsingBlock:^(NSUInteger index, NSString * __nullable message, GTOID * __nullable oid, BOOL * __nonnull stop) {
lastStashIndex = index;
}];

success = [repository applyStashAtIndex:(lastStashIndex + 1) flags:GTRepositoryStashApplyFlagDefault error:&error progressBlock:nil];
expect(@(success)).to(beFalsy());
expect(error).notTo(beNil());
expect(error.domain).to(equal(GTGitErrorDomain));
expect(@(error.code)).to(equal(@(GIT_ENOTFOUND)));

success = [repository dropStashAtIndex:(lastStashIndex + 1) error:&error];
expect(@(success)).to(beFalsy());
expect(error).notTo(beNil());
expect(error.domain).to(equal(GTGitErrorDomain));
expect(@(error.code)).to(equal(@(GIT_ENOTFOUND)));
});

it(@"should fail to apply conflicting stashes", ^{
expect(@([@"foobar" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());

NSError *error = nil;
GTCommit *stash = [repository stashChangesWithMessage:nil flags:GTRepositoryStashFlagIncludeUntracked error:&error];
expect(stash).notTo(beNil());
expect(error).to(beNil());


expect(@([@"barfoo" writeToURL:[repository.fileURL URLByAppendingPathComponent:@"new-test-file"] atomically:YES encoding:NSUTF8StringEncoding error:NULL])).to(beTruthy());

BOOL success = [repository applyStashAtIndex:0 flags:GTRepositoryStashApplyFlagDefault error:&error progressBlock:nil];
expect(@(success)).to(beFalsy());
expect(error).notTo(beNil());

expect(error.domain).to(equal(GTGitErrorDomain));
expect(@(error.code)).to(equal(@(GIT_ECONFLICT)));
});

afterEach(^{
[self tearDown];
});
Expand Down