Commit d07c9f4d2a59dbd423c155b0e74fdeb3c88ea4b9

Edward Thomson 2015-11-02T16:36:28

Merge branch 'pr/3147'

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ade3e3..0b2e433 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,11 @@ v0.23 + 1
   the opportunity for concurrent operations and not committing any
   changes until the unlock.
 
+* `git_diff_options` added a new callback `progress_cb` to report on the
+  progress of the diff as files are being compared. The documentation of
+  the existing callback `notify_cb` was updated to reflect that it only
+  gets called when new deltas are added to the diff.
+
 ### API removals
 
 ### Breaking API changes
@@ -36,6 +41,9 @@ v0.23 + 1
   it existed in the index.  This does not affect the higher-level
   `git_index_add_bypath` or `git_index_add_frombuffer` functions.
 
+* The `notify_payload` field of `git_diff_options` was renamed to `payload`
+  to reflect that it's also the payload for the new progress callback.
+
 v0.23
 ------
 
diff --git a/include/git2/diff.h b/include/git2/diff.h
index a0f6db3..cbffdb4 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -351,6 +351,22 @@ typedef int (*git_diff_notify_cb)(
 	void *payload);
 
 /**
+ * Diff progress callback.
+ *
+ * Called before each file comparison.
+ *
+ * @param diff_so_far The diff being generated.
+ * @param old_path The path to the old file or NULL.
+ * @param new_path The path to the new file or NULL.
+ * @return Non-zero to abort the diff.
+ */
+typedef int (*git_diff_progress_cb)(
+	const git_diff *diff_so_far,
+	const char *old_path,
+	const char *new_path,
+	void *payload);
+
+/**
  * Structure describing options about how the diff should be executed.
  *
  * Setting all values of the structure to zero will yield the default
@@ -370,8 +386,10 @@ typedef int (*git_diff_notify_cb)(
  * - `max_size` is a file size (in bytes) above which a blob will be marked
  *   as binary automatically; pass a negative value to disable.
  * - `notify_cb` is an optional callback function, notifying the consumer of
- *   which files are being examined as the diff is generated
- * - `notify_payload` is the payload data to pass to the `notify_cb` function
+ *   changes to the diff as new deltas are added.
+ * - `progress_cb` is an optional callback function, notifying the consumer of
+ *   which files are being examined as the diff is generated.
+ * - `payload` is the payload to pass to the callback functions.
  * - `ignore_submodules` overrides the submodule ignore setting for all
  *   submodules in the diff.
  */
@@ -383,8 +401,9 @@ typedef struct {
 
 	git_submodule_ignore_t ignore_submodules; /**< submodule ignore rule */
 	git_strarray       pathspec;     /**< defaults to include all paths */
-	git_diff_notify_cb notify_cb;
-	void              *notify_payload;
+	git_diff_notify_cb   notify_cb;
+	git_diff_progress_cb progress_cb;
+	void                *payload;
 
 	/* options controlling how to diff text is generated */
 
@@ -403,7 +422,7 @@ typedef struct {
  * `git_diff_options_init` programmatic initialization.
  */
 #define GIT_DIFF_OPTIONS_INIT \
-	{GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_UNSPECIFIED, {NULL,0}, NULL, NULL, 3}
+	{GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_UNSPECIFIED, {NULL,0}, NULL, NULL, NULL, 3}
 
 /**
  * Initializes a `git_diff_options` with default values. Equivalent to
diff --git a/src/diff.c b/src/diff.c
index b5e9b6c..c236235 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -56,7 +56,7 @@ static int diff_insert_delta(
 
 	if (diff->opts.notify_cb) {
 		error = diff->opts.notify_cb(
-			diff, delta, matched_pathspec, diff->opts.notify_payload);
+			diff, delta, matched_pathspec, diff->opts.payload);
 
 		if (error) {
 			git__free(delta);
@@ -1260,7 +1260,18 @@ int git_diff__from_iterators(
 
 	/* run iterators building diffs */
 	while (!error && (info.oitem || info.nitem)) {
-		int cmp = info.oitem ?
+		int cmp;
+
+		/* report progress */
+		if (opts && opts->progress_cb) {
+			if ((error = opts->progress_cb(diff,
+					info.oitem ? info.oitem->path : NULL,
+					info.nitem ? info.nitem->path : NULL,
+					opts->payload)))
+				break;
+		}
+
+		cmp = info.oitem ?
 			(info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
 
 		/* create DELETED records for old items not matched in new */
diff --git a/tests/diff/notify.c b/tests/diff/notify.c
index 6ef4af5..74abbc9 100644
--- a/tests/diff/notify.c
+++ b/tests/diff/notify.c
@@ -55,7 +55,7 @@ static void test_notify(
 	opts.pathspec.strings = searched_pathspecs;
 	opts.pathspec.count   = pathspecs_count;
 
-	opts.notify_payload = expected_matched_pathspecs;
+	opts.payload = expected_matched_pathspecs;
 	memset(&exp, 0, sizeof(exp));
 
 	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
@@ -228,3 +228,30 @@ void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void)
 
 	git_diff_free(diff);
 }
+
+static int progress_abort_diff(
+	const git_diff *diff_so_far,
+	const char *old_path,
+	const char *new_path,
+	void *payload)
+{
+	GIT_UNUSED(old_path);
+	GIT_UNUSED(new_path);
+	GIT_UNUSED(payload);
+
+	return -42;
+}
+
+void test_diff_notify__progress_cb_can_abort_diff(void)
+{
+	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+	git_diff *diff = NULL;
+
+	g_repo = cl_git_sandbox_init("status");
+
+	opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+	opts.progress_cb = progress_abort_diff;
+
+	cl_git_fail_with(
+		git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42);
+}
diff --git a/tests/diff/tree.c b/tests/diff/tree.c
index 2bc9e6a..e4b2a8b 100644
--- a/tests/diff/tree.c
+++ b/tests/diff/tree.c
@@ -90,7 +90,7 @@ void test_diff_tree__0(void)
 
 #define DIFF_OPTS(FLAGS, CTXT) \
 	{GIT_DIFF_OPTIONS_VERSION, (FLAGS), GIT_SUBMODULE_IGNORE_UNSPECIFIED, \
-	{NULL,0}, NULL, NULL, (CTXT), 1}
+	{NULL,0}, NULL, NULL, NULL, (CTXT), 1}
 
 void test_diff_tree__options(void)
 {