Update remote tips on push
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
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);
}