push --force-with-lease: tie it all together
authorJunio C Hamano <gitster@pobox.com>
Mon, 8 Jul 2013 21:42:40 +0000 (14:42 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 23 Jul 2013 05:33:21 +0000 (22:33 -0700)
This teaches the deepest part of the callchain for "git push" (and
"git send-pack") to enforce "the old value of the ref must be this,
otherwise fail this push" (aka "compare-and-swap" / "--lockref").

Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/send-pack.c
remote.c
remote.h
send-pack.c
transport-helper.c
transport.c
index 6027ead5a9f60cf1986d0ad6aeb7dca9f776c9b5..41dc51221ceff2ccc8133e93e253b7c8d465ed17 100644 (file)
@@ -55,6 +55,11 @@ static void print_helper_status(struct ref *ref)
                        msg = "needs force";
                        break;
 
                        msg = "needs force";
                        break;
 
+               case REF_STATUS_REJECT_STALE:
+                       res = "error";
+                       msg = "stale info";
+                       break;
+
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                        res = "error";
                        msg = "already exists";
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                        res = "error";
                        msg = "already exists";
index 52e3a12d6c1a9114648d3e45a8d7c565cfefc8da..922822c3cb5ac383045ee0edcc5ade6731e2e018 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1396,12 +1396,13 @@ int match_push_refs(struct ref *src, struct ref **dst,
 }
 
 void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
 }
 
 void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
-       int force_update)
+                            int force_update)
 {
        struct ref *ref;
 
        for (ref = remote_refs; ref; ref = ref->next) {
                int force_ref_update = ref->force || force_update;
 {
        struct ref *ref;
 
        for (ref = remote_refs; ref; ref = ref->next) {
                int force_ref_update = ref->force || force_update;
+               int reject_reason = 0;
 
                if (ref->peer_ref)
                        hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
 
                if (ref->peer_ref)
                        hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
@@ -1416,6 +1417,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
                }
 
                /*
                }
 
                /*
+                * Bypass the usual "must fast-forward" check but
+                * replace it with a weaker "the old value must be
+                * this value we observed".  If the remote ref has
+                * moved and is now different from what we expect,
+                * reject any push.
+                *
+                * It also is an error if the user told us to check
+                * with the remote-tracking branch to find the value
+                * to expect, but we did not have such a tracking
+                * branch.
+                */
+               if (ref->expect_old_sha1) {
+                       if (ref->expect_old_no_trackback ||
+                           hashcmp(ref->old_sha1, ref->old_sha1_expect))
+                               reject_reason = REF_STATUS_REJECT_STALE;
+               }
+
+               /*
+                * The usual "must fast-forward" rules.
+                *
                 * Decide whether an individual refspec A:B can be
                 * pushed.  The push will succeed if any of the
                 * following are true:
                 * Decide whether an individual refspec A:B can be
                 * pushed.  The push will succeed if any of the
                 * following are true:
@@ -1433,24 +1454,26 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
                 *     passing the --force argument
                 */
 
                 *     passing the --force argument
                 */
 
-               if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
-                       int why = 0; /* why would this push require --force? */
-
+               else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
                        if (!prefixcmp(ref->name, "refs/tags/"))
                        if (!prefixcmp(ref->name, "refs/tags/"))
-                               why = REF_STATUS_REJECT_ALREADY_EXISTS;
+                               reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
                        else if (!has_sha1_file(ref->old_sha1))
                        else if (!has_sha1_file(ref->old_sha1))
-                               why = REF_STATUS_REJECT_FETCH_FIRST;
+                               reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
                        else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
                                 !lookup_commit_reference_gently(ref->new_sha1, 1))
                        else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
                                 !lookup_commit_reference_gently(ref->new_sha1, 1))
-                               why = REF_STATUS_REJECT_NEEDS_FORCE;
+                               reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
                        else if (!ref_newer(ref->new_sha1, ref->old_sha1))
                        else if (!ref_newer(ref->new_sha1, ref->old_sha1))
-                               why = REF_STATUS_REJECT_NONFASTFORWARD;
-
-                       if (!force_ref_update)
-                               ref->status = why;
-                       else if (why)
-                               ref->forced_update = 1;
+                               reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
                }
                }
+
+               /*
+                * "--force" will defeat any rejection implemented
+                * by the rules above.
+                */
+               if (!force_ref_update)
+                       ref->status = reject_reason;
+               else if (reject_reason)
+                       ref->forced_update = 1;
        }
 }
 
        }
 }
 
index ca3c8c8de8737e4eded1181c5bd88abd87edb5de..6c42554cc2c03f637d9bebdb1b462b1c4601dec9 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -107,6 +107,7 @@ struct ref {
                REF_STATUS_REJECT_NODELETE,
                REF_STATUS_REJECT_FETCH_FIRST,
                REF_STATUS_REJECT_NEEDS_FORCE,
                REF_STATUS_REJECT_NODELETE,
                REF_STATUS_REJECT_FETCH_FIRST,
                REF_STATUS_REJECT_NEEDS_FORCE,
+               REF_STATUS_REJECT_STALE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
index 9a9908c77449aabe4e138e1a767a8dd010561f61..b228d65613c511ffa59eb3ab254ee2e2b6f26c9b 100644 (file)
@@ -227,6 +227,7 @@ int send_pack(struct send_pack_args *args,
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                case REF_STATUS_REJECT_FETCH_FIRST:
                case REF_STATUS_REJECT_NEEDS_FORCE:
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                case REF_STATUS_REJECT_FETCH_FIRST:
                case REF_STATUS_REJECT_NEEDS_FORCE:
+               case REF_STATUS_REJECT_STALE:
                case REF_STATUS_UPTODATE:
                        continue;
                default:
                case REF_STATUS_UPTODATE:
                        continue;
                default:
index db9bd182984f88f512cf7983ea734de4c415d21c..95d22f8d96d556a40639da2a38043b1d1b5bf380 100644 (file)
@@ -683,6 +683,11 @@ static int push_update_ref_status(struct strbuf *buf,
                        free(msg);
                        msg = NULL;
                }
                        free(msg);
                        msg = NULL;
                }
+               else if (!strcmp(msg, "stale info")) {
+                       status = REF_STATUS_REJECT_STALE;
+                       free(msg);
+                       msg = NULL;
+               }
        }
 
        if (*ref)
        }
 
        if (*ref)
@@ -756,6 +761,7 @@ static int push_refs_with_push(struct transport *transport,
                /* Check for statuses set by set_ref_status_for_push() */
                switch (ref->status) {
                case REF_STATUS_REJECT_NONFASTFORWARD:
                /* Check for statuses set by set_ref_status_for_push() */
                switch (ref->status) {
                case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_REJECT_STALE:
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                case REF_STATUS_UPTODATE:
                        continue;
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                case REF_STATUS_UPTODATE:
                        continue;
index 5dd92b7801b4189f78112965f23ee16d49b15aad..b321d6a49d40c78612284557f7ef6d7822a45c8f 100644 (file)
@@ -709,6 +709,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
                                                 "needs force", porcelain);
                break;
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
                                                 "needs force", porcelain);
                break;
+       case REF_STATUS_REJECT_STALE:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "stale info", porcelain);
+               break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
                                                 ref->deletion ? NULL : ref->peer_ref,
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
                                                 ref->deletion ? NULL : ref->peer_ref,
@@ -1078,6 +1082,7 @@ static int run_pre_push_hook(struct transport *transport,
        for (r = remote_refs; r; r = r->next) {
                if (!r->peer_ref) continue;
                if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
        for (r = remote_refs; r; r = r->next) {
                if (!r->peer_ref) continue;
                if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue;
+               if (r->status == REF_STATUS_REJECT_STALE) continue;
                if (r->status == REF_STATUS_UPTODATE) continue;
 
                strbuf_reset(&buf);
                if (r->status == REF_STATUS_UPTODATE) continue;
 
                strbuf_reset(&buf);