push: introduce REJECT_FETCH_FIRST and REJECT_NEEDS_FORCE
authorJunio C Hamano <gitster@pobox.com>
Wed, 23 Jan 2013 21:55:30 +0000 (13:55 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 24 Jan 2013 22:37:23 +0000 (14:37 -0800)
When we push to update an existing ref, if:

* the object at the tip of the remote is not a commit; or
* the object we are pushing is not a commit,

it won't be correct to suggest to fetch, integrate and push again,
as the old and new objects will not "merge". We should explain that
the push must be forced when there is a non-committish object is
involved in such a case.

If we do not have the current object at the tip of the remote, we do
not even know that object, when fetched, is something that can be
merged. In such a case, suggesting to pull first just like
non-fast-forward case may not be technically correct, but in
practice, most such failures are seen when you try to push your work
to a branch without knowing that somebody else already pushed to
update the same branch since you forked, so "pull first" would work
as a suggestion most of the time. And if the object at the tip is
not a commit, "pull first" will fail, without making any permanent
damage. As a side effect, it also makes the error message the user
will get during the next "push" attempt easier to understand, now
the user is aware that a non-commit object is involved.

In these cases, the current code already rejects such a push on the
client end, but we used the same error and advice messages as the
ones used when rejecting a non-fast-forward push, i.e. pull from
there and integrate before pushing again.

Introduce new rejection reasons and reword the messages
appropriately.

[jc: with help by Peff on message details]

Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/config.txt
advice.c
advice.h
builtin/push.c
builtin/send-pack.c
cache.h
remote.c
send-pack.c
transport-helper.c
transport.c
transport.h
index 90e7d10baddeecbf11dd09145abab1e5dbd3e7cc..1f47761c890768555f39c8d2b816c9a91b359114 100644 (file)
@@ -143,7 +143,8 @@ advice.*::
        pushUpdateRejected::
                Set this variable to 'false' if you want to disable
                'pushNonFFCurrent', 'pushNonFFDefault',
-               'pushNonFFMatching', and 'pushAlreadyExists'
+               'pushNonFFMatching', 'pushAlreadyExists',
+               'pushFetchFirst', and 'pushNeedsForce'
                simultaneously.
        pushNonFFCurrent::
                Advice shown when linkgit:git-push[1] fails due to a
@@ -162,6 +163,15 @@ advice.*::
        pushAlreadyExists::
                Shown when linkgit:git-push[1] rejects an update that
                does not qualify for fast-forwarding (e.g., a tag.)
+       pushFetchFirst::
+               Shown when linkgit:git-push[1] rejects an update that
+               tries to overwrite a remote ref that points at an
+               object we do not have.
+       pushNeedsForce::
+               Shown when linkgit:git-push[1] rejects an update that
+               tries to overwrite a remote ref that points at an
+               object that is not a committish, or make the remote
+               ref point at an object that is not a committish.
        statusHints::
                Show directions on how to proceed from the current
                state in the output of linkgit:git-status[1] and in
index d2879272802cb3477dd6b47a78098ffdc26dcb37..780f58da0f5b508eaa278c98dd15bdf6f919c468 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -5,6 +5,8 @@ int advice_push_non_ff_current = 1;
 int advice_push_non_ff_default = 1;
 int advice_push_non_ff_matching = 1;
 int advice_push_already_exists = 1;
+int advice_push_fetch_first = 1;
+int advice_push_needs_force = 1;
 int advice_status_hints = 1;
 int advice_commit_before_merge = 1;
 int advice_resolve_conflict = 1;
@@ -20,6 +22,8 @@ static struct {
        { "pushnonffdefault", &advice_push_non_ff_default },
        { "pushnonffmatching", &advice_push_non_ff_matching },
        { "pushalreadyexists", &advice_push_already_exists },
+       { "pushfetchfirst", &advice_push_fetch_first },
+       { "pushneedsforce", &advice_push_needs_force },
        { "statushints", &advice_status_hints },
        { "commitbeforemerge", &advice_commit_before_merge },
        { "resolveconflict", &advice_resolve_conflict },
index 8bf63563a5cf1573e0e6bf7ffcd5c55643d3f439..fad36df467f065bf7ff7a82d9d447ee4ac5cecb9 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -8,6 +8,8 @@ extern int advice_push_non_ff_current;
 extern int advice_push_non_ff_default;
 extern int advice_push_non_ff_matching;
 extern int advice_push_already_exists;
+extern int advice_push_fetch_first;
+extern int advice_push_needs_force;
 extern int advice_status_hints;
 extern int advice_commit_before_merge;
 extern int advice_resolve_conflict;
index 8491e431e41aaff32c76ab0c61f528682b3babdb..a2b3fbed6a4be128bbfc2c3f7c3b278a91680f80 100644 (file)
@@ -220,10 +220,22 @@ static const char message_advice_checkout_pull_push[] =
           "(e.g. 'git pull') before pushing again.\n"
           "See the 'Note about fast-forwards' in 'git push --help' for details.");
 
+static const char message_advice_ref_fetch_first[] =
+       N_("Updates were rejected because the remote contains work that you do\n"
+          "not have locally. This is usually caused by another repository pushing\n"
+          "to the same ref. You may want to first merge the remote changes (e.g.,\n"
+          "'git pull') before pushing again.\n"
+          "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
 static const char message_advice_ref_already_exists[] =
        N_("Updates were rejected because the destination reference already exists\n"
           "in the remote.");
 
+static const char message_advice_ref_needs_force[] =
+       N_("You cannot update a remote ref that points at a non-commit object,\n"
+          "or update a remote ref to make it point at a non-commit object,\n"
+          "without using the '--force' option.\n");
+
 static void advise_pull_before_push(void)
 {
        if (!advice_push_non_ff_current || !advice_push_update_rejected)
@@ -252,6 +264,20 @@ static void advise_ref_already_exists(void)
        advise(_(message_advice_ref_already_exists));
 }
 
+static void advise_ref_fetch_first(void)
+{
+       if (!advice_push_fetch_first || !advice_push_update_rejected)
+               return;
+       advise(_(message_advice_ref_fetch_first));
+}
+
+static void advise_ref_needs_force(void)
+{
+       if (!advice_push_needs_force || !advice_push_update_rejected)
+               return;
+       advise(_(message_advice_ref_needs_force));
+}
+
 static int push_with_options(struct transport *transport, int flags)
 {
        int err;
@@ -285,6 +311,10 @@ static int push_with_options(struct transport *transport, int flags)
                        advise_checkout_pull_push();
        } else if (reject_reasons & REJECT_ALREADY_EXISTS) {
                advise_ref_already_exists();
+       } else if (reject_reasons & REJECT_FETCH_FIRST) {
+               advise_ref_fetch_first();
+       } else if (reject_reasons & REJECT_NEEDS_FORCE) {
+               advise_ref_needs_force();
        }
 
        return 1;
index f849e0a4a041610dc258a9dc56cc8a357cff31b6..57a46b2654aa82b154d32eb77addd6a4f89c8e8d 100644 (file)
@@ -44,6 +44,16 @@ static void print_helper_status(struct ref *ref)
                        msg = "non-fast forward";
                        break;
 
+               case REF_STATUS_REJECT_FETCH_FIRST:
+                       res = "error";
+                       msg = "fetch first";
+                       break;
+
+               case REF_STATUS_REJECT_NEEDS_FORCE:
+                       res = "error";
+                       msg = "needs force";
+                       break;
+
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                        res = "error";
                        msg = "already exists";
diff --git a/cache.h b/cache.h
index 12631a17d3367c8dd1478c9061fd214c291cd019..377a3df157664c7ec71aba3423808ea495eeca9b 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1010,6 +1010,8 @@ struct ref {
                REF_STATUS_REJECT_NONFASTFORWARD,
                REF_STATUS_REJECT_ALREADY_EXISTS,
                REF_STATUS_REJECT_NODELETE,
+               REF_STATUS_REJECT_FETCH_FIRST,
+               REF_STATUS_REJECT_NEEDS_FORCE,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
index 969aa1169036ce8978790e4dae2df66b3f2e4190..a772e747b7d595e8802ffa031734a6c09de5b54f 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1322,8 +1322,12 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
 
                        if (!prefixcmp(ref->name, "refs/tags/"))
                                why = REF_STATUS_REJECT_ALREADY_EXISTS;
-                       else if (!has_sha1_file(ref->old_sha1)
-                                || !ref_newer(ref->new_sha1, ref->old_sha1))
+                       else if (!has_sha1_file(ref->old_sha1))
+                               why = REF_STATUS_REJECT_FETCH_FIRST;
+                       else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
+                                !lookup_commit_reference_gently(ref->new_sha1, 1))
+                               why = REF_STATUS_REJECT_NEEDS_FORCE;
+                       else if (!ref_newer(ref->new_sha1, ref->old_sha1))
                                why = REF_STATUS_REJECT_NONFASTFORWARD;
 
                        if (!force_ref_update)
@@ -1512,7 +1516,8 @@ int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
        struct commit_list *list, *used;
        int found = 0;
 
-       /* Both new and old must be commit-ish and new is descendant of
+       /*
+        * Both new and old must be commit-ish and new is descendant of
         * old.  Otherwise we require --force.
         */
        o = deref_tag(parse_object(old_sha1), NULL, 0);
index 1c375f0a28c6417f1f6cb48afbde681f458c544d..97ab336097bfb45c63aec739a6eef2df26cc4790 100644 (file)
@@ -230,6 +230,8 @@ int send_pack(struct send_pack_args *args,
                switch (ref->status) {
                case REF_STATUS_REJECT_NONFASTFORWARD:
                case REF_STATUS_REJECT_ALREADY_EXISTS:
+               case REF_STATUS_REJECT_FETCH_FIRST:
+               case REF_STATUS_REJECT_NEEDS_FORCE:
                case REF_STATUS_UPTODATE:
                        continue;
                default:
index 965b778cb3bb6fdd8068dbc7f9603cf9815c028d..cb3ef7d38e475480a5d81ce6077c4c87ea09a318 100644 (file)
@@ -666,6 +666,16 @@ static void push_update_ref_status(struct strbuf *buf,
                        free(msg);
                        msg = NULL;
                }
+               else if (!strcmp(msg, "fetch first")) {
+                       status = REF_STATUS_REJECT_FETCH_FIRST;
+                       free(msg);
+                       msg = NULL;
+               }
+               else if (!strcmp(msg, "needs force")) {
+                       status = REF_STATUS_REJECT_NEEDS_FORCE;
+                       free(msg);
+                       msg = NULL;
+               }
        }
 
        if (*ref)
index 585ebcd2bffd6c113145bf47c7bd11989905fc66..5105562d6e1d4a2d11e0047be57114daf16540c1 100644 (file)
@@ -699,6 +699,14 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
                                                 "already exists", porcelain);
                break;
+       case REF_STATUS_REJECT_FETCH_FIRST:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "fetch first", porcelain);
+               break;
+       case REF_STATUS_REJECT_NEEDS_FORCE:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "needs force", porcelain);
+               break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
                                                 ref->deletion ? NULL : ref->peer_ref,
@@ -750,6 +758,10 @@ void transport_print_push_status(const char *dest, struct ref *refs,
                                *reject_reasons |= REJECT_NON_FF_OTHER;
                } else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) {
                        *reject_reasons |= REJECT_ALREADY_EXISTS;
+               } else if (ref->status == REF_STATUS_REJECT_FETCH_FIRST) {
+                       *reject_reasons |= REJECT_FETCH_FIRST;
+               } else if (ref->status == REF_STATUS_REJECT_NEEDS_FORCE) {
+                       *reject_reasons |= REJECT_NEEDS_FORCE;
                }
        }
 }
index bfd2df5823aac55e4ce8674b7980cccabf0fed5f..c818763c469fd62e239fc66c6b18f80aec301b72 100644 (file)
@@ -143,6 +143,8 @@ void transport_set_verbosity(struct transport *transport, int verbosity,
 #define REJECT_NON_FF_HEAD     0x01
 #define REJECT_NON_FF_OTHER    0x02
 #define REJECT_ALREADY_EXISTS  0x04
+#define REJECT_FETCH_FIRST     0x08
+#define REJECT_NEEDS_FORCE     0x10
 
 int transport_push(struct transport *connection,
                   int refspec_nr, const char **refspec, int flags,