Commit 3fbd7485d8a100f55b02132096d3ff66d390999a

Philip Kelley 2013-01-24T11:03:11

Merge pull request #1250 from jamill/push_update_tips Update remote tips on push

diff --git a/include/git2/push.h b/include/git2/push.h
index 51f059a..6e07f36 100644
--- a/include/git2/push.h
+++ b/include/git2/push.h
@@ -39,6 +39,15 @@ GIT_EXTERN(int) git_push_new(git_push **out, git_remote *remote);
 GIT_EXTERN(int) git_push_add_refspec(git_push *push, const char *refspec);
 
 /**
+ * Update remote tips after a push
+ *
+ * @param push The push object
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_push_update_tips(git_push *push);
+
+/**
  * Actually push all given refspecs
  *
  * @param push The push object
diff --git a/src/push.c b/src/push.c
index 452ead4..ddfe5ec 100644
--- a/src/push.c
+++ b/src/push.c
@@ -161,6 +161,60 @@ int git_push_add_refspec(git_push *push, const char *refspec)
 	return 0;
 }
 
+int git_push_update_tips(git_push *push)
+{
+	git_refspec *fetch_spec = &push->remote->fetch;
+	git_buf remote_ref_name = GIT_BUF_INIT;
+	size_t i, j;
+	push_spec *push_spec;
+	git_reference *remote_ref;
+	push_status *status;
+	int error = 0;
+
+	git_vector_foreach(&push->status, i, status) {
+		/* If this ref update was successful (ok, not ng), it will have an empty message */
+		if (status->msg)
+			continue;
+
+		/* Find the corresponding remote ref */
+		if (!git_refspec_src_matches(fetch_spec, status->ref))
+			continue;
+
+		if ((error = git_refspec_transform_r(&remote_ref_name, fetch_spec, status->ref)) < 0)
+			goto on_error;
+
+		/* Find matching  push ref spec */
+		git_vector_foreach(&push->specs, j, push_spec) {
+			if (!strcmp(push_spec->rref, status->ref))
+				break;
+		}
+
+		/* Could not find the corresponding push ref spec for this push update */
+		if (j == push->specs.length)
+			continue;
+
+		/* Update the remote ref */
+		if (git_oid_iszero(&push_spec->loid)) {
+			error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name));
+
+			if (!error) {
+				if ((error = git_reference_delete(remote_ref)) < 0)
+					goto on_error;
+			} else if (error == GIT_ENOTFOUND)
+				giterr_clear();
+			else
+				goto on_error;
+		} else if ((error = git_reference_create(NULL, push->remote->repo, git_buf_cstr(&remote_ref_name), &push_spec->loid, 1)) < 0)
+			goto on_error;
+	}
+
+	error = 0;
+
+on_error:
+	git_buf_free(&remote_ref_name);
+	return error;
+}
+
 static int revwalk(git_vector *commits, git_push *push)
 {
 	git_remote_head *head;
diff --git a/tests-clar/online/push.c b/tests-clar/online/push.c
index 15351ae..a065e4b 100644
--- a/tests-clar/online/push.c
+++ b/tests-clar/online/push.c
@@ -4,6 +4,8 @@
 #include "vector.h"
 #include "../submodule/submodule_helpers.h"
 #include "push_util.h"
+#include "refspec.h"
+#include "remote.h"
 
 static git_repository *_repo;
 
@@ -127,6 +129,100 @@ static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t
 	git_vector_free(&actual_refs);
 }
 
+static int tracking_branch_list_cb(const char *branch_name, git_branch_t branch_type, void *payload)
+{
+	git_vector *tracking = (git_vector *)payload;
+
+	if (branch_type == GIT_BRANCH_REMOTE)
+		git_vector_insert(tracking, git__strdup(branch_name));
+	else
+		GIT_UNUSED(branch_name);
+
+	return 0;
+}
+
+/**
+ * Verifies that after git_push_update_tips(), remote tracking branches have the expected
+ * names and oids.
+ *
+ * @param remote remote to verify
+ * @param expected_refs expected remote refs after push
+ * @param expected_refs_len length of expected_refs
+ */
+static void verify_tracking_branches(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
+{
+	git_refspec *fetch_spec = &remote->fetch;
+	size_t i, j;
+	git_buf msg = GIT_BUF_INIT;
+	git_buf ref_name = GIT_BUF_INIT;
+	git_buf canonical_ref_name = GIT_BUF_INIT;
+	git_vector actual_refs = GIT_VECTOR_INIT;
+	char *actual_ref;
+	git_oid oid;
+	int failed = 0;
+
+	/* Get current remote branches */
+	cl_git_pass(git_branch_foreach(remote->repo, GIT_BRANCH_REMOTE, tracking_branch_list_cb, &actual_refs));
+
+	/* Loop through expected refs, make sure they exist */
+	for (i = 0; i < expected_refs_len; i++) {
+
+		/* Convert remote reference name into tracking branch name.
+		 * If the spec is not under refs/heads/, then skip.
+		 */
+		if (!git_refspec_src_matches(fetch_spec, expected_refs[i].name))
+			continue;
+
+		cl_git_pass(git_refspec_transform_r(&ref_name, fetch_spec, expected_refs[i].name));
+
+		/* Find matching remote branch */
+		git_vector_foreach(&actual_refs, j, actual_ref) {
+
+			/* Construct canonical ref name from the actual_ref name */
+			git_buf_clear(&canonical_ref_name);
+			cl_git_pass(git_buf_printf(&canonical_ref_name, "refs/remotes/%s", actual_ref));
+			if (!strcmp(git_buf_cstr(&ref_name), git_buf_cstr(&canonical_ref_name)))
+				break;
+		}
+
+		if (j == actual_refs.length) {
+			git_buf_printf(&msg, "Did not find expected tracking branch '%s'.", git_buf_cstr(&ref_name));
+			failed = 1;
+			goto failed;
+		}
+
+		/* Make sure tracking branch is at expected commit ID */
+		cl_git_pass(git_reference_name_to_id(&oid, remote->repo, git_buf_cstr(&canonical_ref_name)));
+
+		if (git_oid_cmp(expected_refs[i].oid, &oid) != 0) {
+			git_buf_puts(&msg, "Tracking branch commit does not match expected ID.");
+			failed = 1;
+			goto failed;
+		}
+
+		cl_git_pass(git_vector_remove(&actual_refs, j));
+	}
+
+	/* Make sure there are no extra branches */
+	if (actual_refs.length > 0) {
+		git_buf_puts(&msg, "Unexpected remote tracking branches exist.");
+		failed = 1;
+		goto failed;
+	}
+
+failed:
+
+	if(failed)
+		cl_fail(git_buf_cstr(&msg));
+
+	git_vector_foreach(&actual_refs, i, actual_ref)
+		git__free(actual_ref);
+
+	git_vector_free(&actual_refs);
+	git_buf_free(&msg);
+	return;
+}
+
 void test_online_push__initialize(void)
 {
 	git_vector delete_specs = GIT_VECTOR_INIT;
@@ -265,11 +361,12 @@ static void do_push(const char *refspecs[], size_t refspecs_len,
 
 		cl_assert_equal_i(expected_ret, ret);
 
-		git_push_free(push);
-
 		verify_refs(_remote, expected_refs, expected_refs_len);
 
-		cl_git_pass(git_remote_update_tips(_remote));
+		cl_git_pass(git_push_update_tips(push));
+		verify_tracking_branches(_remote, expected_refs, expected_refs_len);
+
+		git_push_free(push);
 
 		git_remote_disconnect(_remote);
 	}