Commit 9c258af094aec53b8e4bcaabe1e2f054ba385982

Russell Belfer 2013-02-12T10:13:56

Merge pull request #1316 from ben/clone-cancel Allow network operations to cancel

diff --git a/examples/network/clone.c b/examples/network/clone.c
index 5b0a810..80e80af 100644
--- a/examples/network/clone.c
+++ b/examples/network/clone.c
@@ -44,11 +44,12 @@ static void print_progress(const progress_data *pd)
 		   pd->path);
 }
 
-static void fetch_progress(const git_transfer_progress *stats, void *payload)
+static int fetch_progress(const git_transfer_progress *stats, void *payload)
 {
 	progress_data *pd = (progress_data*)payload;
 	pd->fetch_progress = *stats;
 	print_progress(pd);
+	return 0;
 }
 static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload)
 {
diff --git a/include/git2/indexer.h b/include/git2/indexer.h
index c428d43..151f5b4 100644
--- a/include/git2/indexer.h
+++ b/include/git2/indexer.h
@@ -25,9 +25,13 @@ typedef struct git_transfer_progress {
 
 
 /**
- * Type for progress callbacks during indexing
+ * Type for progress callbacks during indexing.  Return a value less than zero
+ * to cancel the transfer.
+ *
+ * @param stats Structure containing information about the state of the transfer
+ * @param payload Payload provided by caller
  */
-typedef void (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload);
+typedef int (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload);
 
 typedef struct git_indexer git_indexer;
 typedef struct git_indexer_stream git_indexer_stream;
diff --git a/src/clone.c b/src/clone.c
index 4b72b83..409a77f 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -355,8 +355,8 @@ static int setup_remotes_and_fetch(
 
 		/* Connect and download everything */
 		if (!git_remote_connect(origin, GIT_DIRECTION_FETCH)) {
-			if (!git_remote_download(origin, options->fetch_progress_cb,
-						options->fetch_progress_payload)) {
+			if (!(retcode = git_remote_download(origin, options->fetch_progress_cb,
+						options->fetch_progress_payload))) {
 				/* Create "origin/foo" branches for all remote branches */
 				if (!git_remote_update_tips(origin)) {
 					/* Point HEAD to the requested branch */
diff --git a/src/indexer.c b/src/indexer.c
index 3f6b107..4ff5e72 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -394,15 +394,15 @@ on_error:
 	return -1;
 }
 
-static void do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats)
+static int do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats)
 {
-	if (!idx->progress_cb) return;
-	idx->progress_cb(stats, idx->progress_payload);
+	if (!idx->progress_cb) return 0;
+	return idx->progress_cb(stats, idx->progress_payload);
 }
 
 int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats)
 {
-	int error;
+	int error = -1;
 	struct git_pack_header hdr;
 	size_t processed; 
 	git_mwindow_file *mwf = &idx->pack->mwf;
@@ -536,14 +536,17 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
 		}
 		stats->received_objects++;
 
-		do_progress_callback(idx, stats);
+		if (do_progress_callback(idx, stats) != 0) {
+			error = GIT_EUSER;
+			goto on_error;
+		}
 	}
 
 	return 0;
 
 on_error:
 	git_mwindow_free_all(mwf);
-	return -1;
+	return error;
 }
 
 static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char *suffix)
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 0fae086..596dba6 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -493,7 +493,7 @@ int git_smart__download_pack(
 			git__free(pkt);
 		} else if (pkt->type == GIT_PKT_DATA) {
 			git_pkt_data *p = (git_pkt_data *) pkt;
-			if (writepack->add(writepack, p->data, p->len, stats) < 0)
+			if ((error = writepack->add(writepack, p->data, p->len, stats)) < 0)
 				goto on_error;
 
 			git__free(pkt);
diff --git a/tests-clar/network/fetchlocal.c b/tests-clar/network/fetchlocal.c
index ee3bd9d..e2ba675 100644
--- a/tests-clar/network/fetchlocal.c
+++ b/tests-clar/network/fetchlocal.c
@@ -4,11 +4,12 @@
 #include "path.h"
 #include "remote.h"
 
-static void transfer_cb(const git_transfer_progress *stats, void *payload)
+static int transfer_cb(const git_transfer_progress *stats, void *payload)
 {
 	int *callcount = (int*)payload;
 	GIT_UNUSED(stats);
 	(*callcount)++;
+	return 0;
 }
 
 static void cleanup_local_repo(void *path)
diff --git a/tests-clar/online/clone.c b/tests-clar/online/clone.c
index 6a46fa5..c1a9a9a 100644
--- a/tests-clar/online/clone.c
+++ b/tests-clar/online/clone.c
@@ -81,11 +81,12 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa
 	(*was_called) = true;
 }
 
-static void fetch_progress(const git_transfer_progress *stats, void *payload)
+static int fetch_progress(const git_transfer_progress *stats, void *payload)
 {
 	bool *was_called = (bool*)payload;
 	GIT_UNUSED(stats);
 	(*was_called) = true;
+	return 0;
 }
 
 void test_online_clone__can_checkout_a_cloned_repo(void)
@@ -182,3 +183,18 @@ void test_online_clone__bitbucket_style(void)
 	git_repository_free(g_repo); g_repo = NULL;
 	cl_fixture_cleanup("./foo");
 }
+
+static int cancel_at_half(const git_transfer_progress *stats, void *payload)
+{
+	GIT_UNUSED(payload);
+
+	if (stats->received_objects > (stats->total_objects/2))
+		return 1;
+	return 0;
+}
+
+void test_online_clone__can_cancel(void)
+{
+	g_options.fetch_progress_cb = cancel_at_half;
+	cl_git_fail_with(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options), GIT_EUSER);
+}
diff --git a/tests-clar/online/fetch.c b/tests-clar/online/fetch.c
index 41cdb30..a0ee7aa 100644
--- a/tests-clar/online/fetch.c
+++ b/tests-clar/online/fetch.c
@@ -25,10 +25,11 @@ static int update_tips(const char *refname, const git_oid *a, const git_oid *b, 
 	return 0;
 }
 
-static void progress(const git_transfer_progress *stats, void *payload)
+static int progress(const git_transfer_progress *stats, void *payload)
 {
 	size_t *bytes_received = (size_t *)payload;
 	*bytes_received = stats->received_bytes;
+	return 0;
 }
 
 static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n)
@@ -73,12 +74,13 @@ void test_online_fetch__no_tags_http(void)
 	do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
 }
 
-static void transferProgressCallback(const git_transfer_progress *stats, void *payload)
+static int transferProgressCallback(const git_transfer_progress *stats, void *payload)
 {
 	bool *invoked = (bool *)payload;
 
 	GIT_UNUSED(stats);
 	*invoked = true;
+	return 0;
 }
 
 void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date(void)
@@ -110,3 +112,25 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date
 	git_remote_free(remote);
 	git_repository_free(_repository);
 }
+
+static int cancel_at_half(const git_transfer_progress *stats, void *payload)
+{
+	GIT_UNUSED(payload);
+
+	if (stats->received_objects > (stats->total_objects/2))
+		return -1;
+	return 0;
+}
+
+void test_online_fetch__can_cancel(void)
+{
+	git_remote *remote;
+	size_t bytes_received = 0;
+
+	cl_git_pass(git_remote_create(&remote, _repo, "test",
+				"http://github.com/libgit2/TestGitRepository.git"));
+	cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+	cl_git_fail_with(git_remote_download(remote, cancel_at_half, &bytes_received), GIT_EUSER);
+	git_remote_disconnect(remote);
+	git_remote_free(remote);
+}