Commit cbe8a61dfacf107f49dbfc65f8385576c72fe41e

Edward Thomson 2015-05-01T11:28:54

Merge pull request #3059 from libgit2/cmn/negotiation-notify [WIP/RFC] push: report the update plan to the caller

diff --git a/include/git2/push.h b/include/git2/push.h
index ecabff3..3f85045 100644
--- a/include/git2/push.h
+++ b/include/git2/push.h
@@ -59,6 +59,36 @@ typedef int (*git_push_transfer_progress)(
 	size_t bytes,
 	void* payload);
 
+/**
+ * Represents an update which will be performed on the remote during push
+ */
+typedef struct {
+	/**
+	 * The source name of the reference
+	 */
+	char *src_refname;
+	/**
+	 * The name of the reference to update on the server
+	 */
+	char *dst_refname;
+	/**
+	 * The current target of the reference
+	 */
+	git_oid src;
+	/**
+	 * The new target for the reference
+	 */
+	git_oid dst;
+} git_push_update;
+
+/**
+ * @param updates an array containing the updates which will be sent
+ * as commands to the destination.
+ * @param len number of elements in `updates`
+ * @param payload Payload provided by the caller
+ */
+typedef int (*git_push_negotiation)(const git_push_update **updates, size_t len, void *payload);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/include/git2/remote.h b/include/git2/remote.h
index f85c384..6e88a46 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -521,6 +521,12 @@ struct git_remote_callbacks {
 	int (*push_update_reference)(const char *refname, const char *status, void *data);
 
 	/**
+	 * Called once between the negotiation step and the upload. It
+	 * provides information about what updates will be performed.
+	 */
+	git_push_negotiation push_negotiation;
+
+	/**
 	 * This will be passed to each of the callbacks in this struct
 	 * as the last parameter.
 	 */
diff --git a/include/git2/types.h b/include/git2/types.h
index c90ac47..fdb5f2b 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -273,6 +273,7 @@ typedef int (*git_transfer_progress_cb)(const git_transfer_progress *stats, void
  */
 typedef int (*git_transport_message_cb)(const char *str, int len, void *payload);
 
+
 /**
  * Type of host certificate structure that is passed to the check callback
  */
diff --git a/src/push.c b/src/push.c
index d4171bb..3ac6fbf 100644
--- a/src/push.c
+++ b/src/push.c
@@ -54,6 +54,13 @@ int git_push_new(git_push **out, git_remote *remote)
 		return -1;
 	}
 
+	if (git_vector_init(&p->updates, 0, NULL) < 0) {
+		git_vector_free(&p->status);
+		git_vector_free(&p->specs);
+		git__free(p);
+		return -1;
+	}
+
 	*out = p;
 	return 0;
 }
@@ -75,7 +82,9 @@ int git_push_set_callbacks(
 	git_packbuilder_progress pack_progress_cb,
 	void *pack_progress_cb_payload,
 	git_push_transfer_progress transfer_progress_cb,
-	void *transfer_progress_cb_payload)
+	void *transfer_progress_cb_payload,
+	git_push_negotiation negotiation_cb,
+	void *negotiation_cb_payload)
 {
 	if (!push)
 		return -1;
@@ -86,6 +95,9 @@ int git_push_set_callbacks(
 	push->transfer_progress_cb = transfer_progress_cb;
 	push->transfer_progress_cb_payload = transfer_progress_cb_payload;
 
+	push->negotiation_cb = negotiation_cb;
+	push->negotiation_cb_payload = negotiation_cb_payload;
+
 	return 0;
 }
 
@@ -534,6 +546,22 @@ on_error:
 	return error;
 }
 
+static int add_update(git_push *push, push_spec *spec)
+{
+	git_push_update *u = git__calloc(1, sizeof(git_push_update));
+	GITERR_CHECK_ALLOC(u);
+
+	u->src_refname = git__strdup(spec->refspec.src);
+	GITERR_CHECK_ALLOC(u->src_refname);
+	u->dst_refname = git__strdup(spec->refspec.src);
+	GITERR_CHECK_ALLOC(u->dst_refname);
+
+	git_oid_cpy(&u->src, &spec->loid);
+	git_oid_cpy(&u->dst, &spec->roid);
+
+	return git_vector_insert(&push->updates, u);
+}
+
 static int calculate_work(git_push *push)
 {
 	git_remote_head *head;
@@ -559,6 +587,9 @@ static int calculate_work(git_push *push)
 				break;
 			}
 		}
+
+		if (add_update(push, spec) < 0)
+			return -1;
 	}
 
 	return 0;
@@ -590,9 +621,17 @@ static int do_push(git_push *push)
 		if ((error = git_packbuilder_set_callbacks(push->pb, push->pack_progress_cb, push->pack_progress_cb_payload)) < 0)
 			goto on_error;
 
-	if ((error = calculate_work(push)) < 0 ||
-		(error = queue_objects(push)) < 0 ||
-		(error = transport->push(transport, push)) < 0)
+	if ((error = calculate_work(push)) < 0)
+		goto on_error;
+
+	if (push->negotiation_cb &&
+	    (error = push->negotiation_cb((const git_push_update **) push->updates.contents,
+					  push->updates.length,
+					  push->negotiation_cb_payload)))
+	    goto on_error;
+
+	if ((error = queue_objects(push)) < 0 ||
+	    (error = transport->push(transport, push)) < 0)
 		goto on_error;
 
 on_error:
diff --git a/src/push.h b/src/push.h
index b19d40e..fb5f014 100644
--- a/src/push.h
+++ b/src/push.h
@@ -29,6 +29,7 @@ struct git_push {
 	git_packbuilder *pb;
 	git_remote *remote;
 	git_vector specs;
+	git_vector updates;
 	bool report_status;
 
 	/* report-status */
@@ -42,6 +43,8 @@ struct git_push {
 	void *pack_progress_cb_payload;
 	git_push_transfer_progress transfer_progress_cb;
 	void *transfer_progress_cb_payload;
+	git_push_negotiation negotiation_cb;
+	void *negotiation_cb_payload;
 };
 
 /**
@@ -85,6 +88,8 @@ int git_push_set_options(
  * the upload portion of a push. Be aware that this is called inline with
  * pack building operations, so performance may be affected.
  * @param transfer_progress_cb_payload Payload for the network progress callback.
+ * @param push_negotiation_cb Function to call before sending the commands to the remote.
+ * @param push_negotiation_cb_payload Payload for the negotiation callback
  * @return 0 or an error code
  */
 int git_push_set_callbacks(
@@ -92,7 +97,9 @@ int git_push_set_callbacks(
 	git_packbuilder_progress pack_progress_cb,
 	void *pack_progress_cb_payload,
 	git_push_transfer_progress transfer_progress_cb,
-	void *transfer_progress_cb_payload);
+	void *transfer_progress_cb_payload,
+	git_push_negotiation negotiation_cb,
+	void *negotiation_cb_payload);
 
 /**
  * Add a refspec to be pushed
diff --git a/src/remote.c b/src/remote.c
index 3f840b6..5257e85 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -2363,7 +2363,8 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi
 	cbs = &remote->callbacks;
 	if ((error = git_push_set_callbacks(push,
 					    cbs->pack_progress, cbs->payload,
-					    cbs->push_transfer_progress, cbs->payload)) < 0)
+					    cbs->push_transfer_progress, cbs->payload,
+					    cbs->push_negotiation, cbs->payload)) < 0)
 		goto cleanup;
 
 	if ((error = git_push_finish(push)) < 0)
diff --git a/tests/online/push_util.h b/tests/online/push_util.h
index 2e05c4e..83d46b5 100644
--- a/tests/online/push_util.h
+++ b/tests/online/push_util.h
@@ -12,7 +12,7 @@ extern const git_oid OID_ZERO;
  * @param data pointer to a record_callbacks_data instance
  */
 #define RECORD_CALLBACKS_INIT(data) \
-	{ GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, data }
+	{ GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, data }
 
 typedef struct {
 	char *name;