summary refs log tree commit
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2019-07-15 14:53:53 -0700
committerJunio C Hamano <gitster@pobox.com>2019-07-15 14:53:53 -0700
commit960e92d24f97b83a5afe35e978f7537ea42a72ec (patch)
tree83772f7a1812fe5637856a05d5aa431a11bcce06
parentef358ec2e98432a890775271942bd7ed9095a0fb (diff)
parent3bca1e7f9f9708b970035a641d21f8c5cec1cd88 (diff)
"git push --atomic" that goes over the transport-helper (namely,
the smart http transport) failed to prevent refs to be pushed when
it can locally tell that one of the ref update will fail without
having to consult the other end, which has been corrected.

* es/local-atomic-push-failure-with-http:
  transport-helper: enforce atomic in push_refs_with_push
-rwxr-xr-xt/t5541-http-push-smart.sh49
-rw-r--r--transport-helper.c6
-rw-r--r--transport.c13
3 files changed, 68 insertions, 0 deletions
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 2e4802e206..b86ddb60f2 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -177,6 +177,55 @@ test_expect_success 'push (chunked)' '
          test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
+        # Setup upstream repo - empty for now
+        d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
+        git init --bare "$d" &&
+        test_config -C "$d" http.receivepack true &&
+        up="$HTTPD_URL"/smart/atomic-branches.git &&
+
+        # Tell "$up" about two branches for now
+        test_commit atomic1 &&
+        test_commit atomic2 &&
+        git branch collateral &&
+        git push "$up" master collateral &&
+
+        # collateral is a valid push, but should be failed by atomic push
+        git checkout collateral &&
+        test_commit collateral1 &&
+
+        # Make master incompatible with upstream to provoke atomic
+        git checkout master &&
+        git reset --hard HEAD^ &&
+
+        # Add a new branch which should be failed by atomic push. This is a
+        # regression case.
+        git branch atomic &&
+
+        # --atomic should cause entire push to be rejected
+        test_must_fail git push --atomic "$up" master atomic collateral 2>output &&
+
+        # the new branch should not have been created upstream
+        test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
+
+        # upstream should still reflect atomic2, the last thing we pushed
+        # successfully
+        git rev-parse atomic2 >expected &&
+        # on master...
+        git -C "$d" rev-parse refs/heads/master >actual &&
+        test_cmp expected actual &&
+        # ...and collateral.
+        git -C "$d" rev-parse refs/heads/collateral >actual &&
+        test_cmp expected actual &&
+
+        # the failed refs should be indicated to the user
+        grep "^ ! .*rejected.* master -> master" output &&
+
+        # the collateral failure refs should be indicated to the user
+        grep "^ ! .*rejected.* atomic -> atomic .*atomic push failed" output &&
+        grep "^ ! .*rejected.* collateral -> collateral .*atomic push failed" output
+'
+
 test_expect_success 'push --all can push to empty repo' '
         d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
         git init --bare "$d" &&
diff --git a/transport-helper.c b/transport-helper.c
index c7e17ec9cb..6b05a88faf 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -853,6 +853,7 @@ static int push_refs_with_push(struct transport *transport,
 {
         int force_all = flags & TRANSPORT_PUSH_FORCE;
         int mirror = flags & TRANSPORT_PUSH_MIRROR;
+        int atomic = flags & TRANSPORT_PUSH_ATOMIC;
         struct helper_data *data = transport->data;
         struct strbuf buf = STRBUF_INIT;
         struct ref *ref;
@@ -872,6 +873,11 @@ static int push_refs_with_push(struct transport *transport,
                 case REF_STATUS_REJECT_NONFASTFORWARD:
                 case REF_STATUS_REJECT_STALE:
                 case REF_STATUS_REJECT_ALREADY_EXISTS:
+                        if (atomic) {
+                                string_list_clear(&cas_options, 0);
+                                return 0;
+                        } else
+                                continue;
                 case REF_STATUS_UPTODATE:
                         continue;
                 default:
diff --git a/transport.c b/transport.c
index 2def5a0c35..7e63cfe469 100644
--- a/transport.c
+++ b/transport.c
@@ -1226,6 +1226,19 @@ int transport_push(struct repository *r,
                 err = push_had_errors(remote_refs);
                 ret = push_ret | err;
 
+                if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
+                        for (struct ref *it = remote_refs; it; it = it->next)
+                                switch (it->status) {
+                                case REF_STATUS_NONE:
+                                case REF_STATUS_UPTODATE:
+                                case REF_STATUS_OK:
+                                        it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+                                        break;
+                                default:
+                                        break;
+                                }
+                }
+
                 if (!quiet || err)
                         transport_print_push_status(transport->url, remote_refs,
                                         verbose | porcelain, porcelain,