Commit 4ea3eebf4b53274b885f749f543101f6633c665e

Edward Thomson 2015-05-01T18:34:38

stash_apply: provide progress callbacks

diff --git a/include/git2/stash.h b/include/git2/stash.h
index 1f773dc..526db0b 100644
--- a/include/git2/stash.h
+++ b/include/git2/stash.h
@@ -80,6 +80,40 @@ typedef enum {
 	GIT_STASH_APPLY_REINSTATE_INDEX = (1 << 0),
 } git_stash_apply_flags;
 
+typedef enum {
+	GIT_STASH_APPLY_PROGRESS_NONE = 0,
+
+	/** Loading the stashed data from the object database. */
+	GIT_STASH_APPLY_PROGRESS_LOADING_STASH,
+
+	/** The stored index is being analyzed. */
+	GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX,
+
+	/** The modified files are being analyzed. */
+	GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED,
+
+	/** The untracked and ignored files are being analyzed. */
+	GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED,
+
+	/** The untracked files are being written to disk. */
+	GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED,
+
+	/** The modified files are being written to disk. */
+	GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED,
+
+	/** The stash was applied successfully. */
+	GIT_STASH_APPLY_PROGRESS_DONE,
+} git_stash_apply_progress_t;
+
+/**
+ * Stash application progress notification function.
+ * Return 0 to continue processing, or a negative value to
+ * abort the stash application.
+ */
+typedef int (*git_stash_apply_progress_cb)(
+	git_stash_apply_progress_t progress,
+	void *payload);
+
 /** Stash application options structure.
  *
  * Initialize with the `GIT_STASH_APPLY_OPTIONS_INIT` macro to set
@@ -95,6 +129,10 @@ typedef struct git_stash_apply_options {
 
 	/** Options to use when writing files to the working directory. */
 	git_checkout_options checkout_options;
+
+	/** Optional callback to notify the consumer of application progress. */
+	git_stash_apply_progress_cb progress_cb;
+	void *progress_payload;
 } git_stash_apply_options;
 
 #define GIT_STASH_APPLY_OPTIONS_VERSION 1
diff --git a/src/stash.c b/src/stash.c
index dade06f..71ab7b9 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -701,6 +701,11 @@ int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int ver
 	return 0;
 }
 
+#define NOTIFY_PROGRESS(opts, progress_type) \
+	if ((opts).progress_cb && \
+		(error = (opts).progress_cb((progress_type), (opts).progress_payload))) \
+		return (error < 0) ? error : -1;
+
 int git_stash_apply(
 	git_repository *repo,
 	size_t index,
@@ -725,6 +730,8 @@ int git_stash_apply(
 	normalize_apply_options(&opts, given_opts);
 	checkout_strategy = opts.checkout_options.checkout_strategy;
 
+	NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH);
+
 	/* Retrieve commit corresponding to the given stash */
 	if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0)
 		goto cleanup;
@@ -739,6 +746,8 @@ int git_stash_apply(
 	if ((error = git_repository_index(&repo_index, repo)) < 0)
 		goto cleanup;
 
+	NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX);
+
 	/* Restore index if required */
 	if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) &&
 		git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) {
@@ -753,19 +762,26 @@ int git_stash_apply(
 		}
 	}
 
+	NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED);
+
 	/* Restore modified files in workdir */
 	if ((error = merge_index_and_tree(
 			&modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0)
 		goto cleanup;
 
 	/* If applicable, restore untracked / ignored files in workdir */
-	if (untracked_tree &&
-		(error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0)
-		goto cleanup;
+	if (untracked_tree) {
+		NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED);
+
+		if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0)
+			goto cleanup;
+	}
 
 	if (untracked_index) {
 		opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
 
+		NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED);
+
 		if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0)
 			goto cleanup;
 
@@ -787,6 +803,8 @@ int git_stash_apply(
 	 */
 	opts.checkout_options.baseline_index = repo_index;
 
+	NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED);
+
 	if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 0)
 		goto cleanup;
 
@@ -795,6 +813,8 @@ int git_stash_apply(
 			goto cleanup;
 	}
 
+	NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE);
+
 cleanup:
 	git_index_free(untracked_index);
 	git_index_free(modified_index);
diff --git a/tests/stash/apply.c b/tests/stash/apply.c
index de330e9..213945e 100644
--- a/tests/stash/apply.c
+++ b/tests/stash/apply.c
@@ -286,3 +286,48 @@ void test_stash_apply__executes_notify_cb(void)
 	cl_assert_equal_b(true, seen_paths.who);
 	cl_assert_equal_b(true, seen_paths.when);
 }
+
+int progress_cb(
+	git_stash_apply_progress_t progress,
+	void *payload)
+{
+	git_stash_apply_progress_t *p = (git_stash_apply_progress_t *)payload;
+
+	cl_assert_equal_i((*p)+1, progress);
+
+	*p = progress;
+
+	return 0;
+}
+
+void test_stash_apply__calls_progress_cb(void)
+{
+	git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
+	git_stash_apply_progress_t progress = GIT_STASH_APPLY_PROGRESS_NONE;
+
+	opts.progress_cb = progress_cb;
+	opts.progress_payload = &progress;
+
+	cl_git_pass(git_stash_apply(repo, 0, &opts));
+	cl_assert_equal_i(progress, GIT_STASH_APPLY_PROGRESS_DONE);
+}
+
+int aborting_progress_cb(
+	git_stash_apply_progress_t progress,
+	void *payload)
+{
+	if (progress == GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED)
+		return -44;
+
+	return 0;
+}
+
+void test_stash_apply__progress_cb_can_abort(void)
+{
+	git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
+	git_stash_apply_progress_t progress = GIT_STASH_APPLY_PROGRESS_NONE;
+
+	opts.progress_cb = aborting_progress_cb;
+
+	cl_git_fail_with(-44, git_stash_apply(repo, 0, &opts));
+}