{"thread":{"id":"32016","subject":"[PATCH v2 4/5] push: flag updates that require force","startedAt":"2012-11-05T03:08:23Z","lastAt":"2012-11-10T01:15:49Z","messageCount":9,"participants":["Chris Rorvick","Jeff King"],"isPatch":true,"patchVersion":2,"patchTotal":5},"messages":[{"id":"202532","messageId":"1352084908-32333-1-git-send-email-chris@rorvick.com","threadId":"32016","inReplyTo":null,"subject":"[PATCH v2 0/5] push: update remote tags only with force","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-11-05T03:08:23Z","receivedAt":"2012-11-05T03:08:23Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"Patch series to prevent push from updating remote tags w/o forcing them.\nSplit out original patch to ease review.\n\nChris Rorvick (5):\n  push: return reject reasons via a mask\n  push: add advice for rejected tag reference\n  push: flag updates\n  push: flag updates that require force\n  push: update remote tags only with force\n\n Documentation/git-push.txt |   10 +++++-----\n builtin/push.c             |   24 +++++++++++++++---------\n builtin/send-pack.c        |    6 ++++++\n cache.h                    |    7 ++++++-\n remote.c                   |   39 +++++++++++++++++++++++++++++++--------\n t/t5516-fetch-push.sh      |   30 +++++++++++++++++++++++++++++-\n transport-helper.c         |    6 ++++++\n transport.c                |   25 +++++++++++++++----------\n transport.h                |   10 ++++++----\n 9 files changed, 119 insertions(+), 38 deletions(-)\n"},{"id":"202529","messageId":"1352084908-32333-2-git-send-email-chris@rorvick.com","threadId":"32016","inReplyTo":"1352084908-32333-1-git-send-email-chris@rorvick.com","subject":"[PATCH v2 1/5] push: return reject reasons via a mask","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-11-05T03:08:24Z","receivedAt":"2012-11-05T03:08:24Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"Pass all rejection reasons back from transport_push().  The logic is\nsimpler and more flexible with regard to providing useful feedback.\n\nSigned-off-by: Chris Rorvick <chris@rorvick.com>\n---\n builtin/push.c |   13 ++++---------\n transport.c    |   17 ++++++++---------\n transport.h    |    9 +++++----\n 3 files changed, 17 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex db9ba30..eaeaf7e 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -244,7 +244,7 @@ static void advise_checkout_pull_push(void)\n static int push_with_options(struct transport *transport, int flags)\n {\n \tint err;\n-\tint nonfastforward;\n+\tunsigned int reject_mask;\n \n \ttransport_set_verbosity(transport, verbosity, progress);\n \n@@ -257,7 +257,7 @@ static int push_with_options(struct transport *transport, int flags)\n \tif (verbosity > 0)\n \t\tfprintf(stderr, _(\"Pushing to %s\\n\"), transport->url);\n \terr = transport_push(transport, refspec_nr, refspec, flags,\n-\t\t\t     &nonfastforward);\n+\t\t\t     &reject_mask);\n \tif (err != 0)\n \t\terror(_(\"failed to push some refs to '%s'\"), transport->url);\n \n@@ -265,18 +265,13 @@ static int push_with_options(struct transport *transport, int flags)\n \tif (!err)\n \t\treturn 0;\n \n-\tswitch (nonfastforward) {\n-\tdefault:\n-\t\tbreak;\n-\tcase NON_FF_HEAD:\n+\tif (reject_mask & NON_FF_HEAD) {\n \t\tadvise_pull_before_push();\n-\t\tbreak;\n-\tcase NON_FF_OTHER:\n+\t} else if (reject_mask & NON_FF_OTHER) {\n \t\tif (default_matching_used)\n \t\t\tadvise_use_upstream();\n \t\telse\n \t\t\tadvise_checkout_pull_push();\n-\t\tbreak;\n \t}\n \n \treturn 1;\ndiff --git a/transport.c b/transport.c\nindex 9932f40..ae9fda8 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -714,7 +714,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i\n }\n \n void transport_print_push_status(const char *dest, struct ref *refs,\n-\t\t\t\t  int verbose, int porcelain, int *nonfastforward)\n+\t\t\t\t  int verbose, int porcelain, unsigned int *reject_mask)\n {\n \tstruct ref *ref;\n \tint n = 0;\n@@ -733,18 +733,17 @@ void transport_print_push_status(const char *dest, struct ref *refs,\n \t\tif (ref->status == REF_STATUS_OK)\n \t\t\tn += print_one_push_status(ref, dest, n, porcelain);\n \n-\t*nonfastforward = 0;\n+\t*reject_mask = 0;\n \tfor (ref = refs; ref; ref = ref->next) {\n \t\tif (ref->status != REF_STATUS_NONE &&\n \t\t    ref->status != REF_STATUS_UPTODATE &&\n \t\t    ref->status != REF_STATUS_OK)\n \t\t\tn += print_one_push_status(ref, dest, n, porcelain);\n-\t\tif (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&\n-\t\t    *nonfastforward != NON_FF_HEAD) {\n+\t\tif (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {\n \t\t\tif (!strcmp(head, ref->name))\n-\t\t\t\t*nonfastforward = NON_FF_HEAD;\n+\t\t\t\t*reject_mask |= NON_FF_HEAD;\n \t\t\telse\n-\t\t\t\t*nonfastforward = NON_FF_OTHER;\n+\t\t\t\t*reject_mask |= NON_FF_OTHER;\n \t\t}\n \t}\n }\n@@ -1031,9 +1030,9 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)\n \n int transport_push(struct transport *transport,\n \t\t   int refspec_nr, const char **refspec, int flags,\n-\t\t   int *nonfastforward)\n+\t\t   unsigned int *reject_mask)\n {\n-\t*nonfastforward = 0;\n+\t*reject_mask = 0;\n \ttransport_verify_remote_names(refspec_nr, refspec);\n \n \tif (transport->push) {\n@@ -1099,7 +1098,7 @@ int transport_push(struct transport *transport,\n \t\tif (!quiet || err)\n \t\t\ttransport_print_push_status(transport->url, remote_refs,\n \t\t\t\t\tverbose | porcelain, porcelain,\n-\t\t\t\t\tnonfastforward);\n+\t\t\t\t\treject_mask);\n \n \t\tif (flags & TRANSPORT_PUSH_SET_UPSTREAM)\n \t\t\tset_upstreams(transport, remote_refs, pretend);\ndiff --git a/transport.h b/transport.h\nindex 3b21c4a..b9e124a 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -140,11 +140,12 @@ int transport_set_option(struct transport *transport, const char *name,\n void transport_set_verbosity(struct transport *transport, int verbosity,\n \tint force_progress);\n \n-#define NON_FF_HEAD 1\n-#define NON_FF_OTHER 2\n+#define NON_FF_HEAD     0x01\n+#define NON_FF_OTHER    0x02\n+\n int transport_push(struct transport *connection,\n \t\t   int refspec_nr, const char **refspec, int flags,\n-\t\t   int * nonfastforward);\n+\t\t   unsigned int * reject_mask);\n \n const struct ref *transport_get_remote_refs(struct transport *transport);\n \n@@ -170,7 +171,7 @@ void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int v\n int transport_refs_pushed(struct ref *ref);\n \n void transport_print_push_status(const char *dest, struct ref *refs,\n-\t\t  int verbose, int porcelain, int *nonfastforward);\n+\t\t  int verbose, int porcelain, unsigned int *reject_mask);\n \n typedef void alternate_ref_fn(const struct ref *, void *);\n extern void for_each_alternate_ref(alternate_ref_fn, void *);\n-- \n1.7.1\n"},{"id":"202530","messageId":"1352084908-32333-3-git-send-email-chris@rorvick.com","threadId":"32016","inReplyTo":"1352084908-32333-1-git-send-email-chris@rorvick.com","subject":"[PATCH v2 2/5] push: add advice for rejected tag reference","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-11-05T03:08:25Z","receivedAt":"2012-11-05T03:08:25Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"Advising the user to fetch and merge only makes sense if the rejected\nreference is a branch.  If none of the rejections were for branches,\ntell the user they need to force the update(s).\n\nSigned-off-by: Chris Rorvick <chris@rorvick.com>\n---\n builtin/push.c |   16 ++++++++++++++--\n cache.h        |    1 +\n remote.c       |    7 +++++++\n transport.c    |    6 ++++--\n transport.h    |    5 +++--\n 5 files changed, 29 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex eaeaf7e..77340c0 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -220,6 +220,11 @@ static const char message_advice_checkout_pull_push[] =\n \t   \"(e.g. 'git pull') before pushing again.\\n\"\n \t   \"See the 'Note about fast-forwards' in 'git push --help' for details.\");\n \n+static const char message_advice_ref_already_exists[] =\n+\tN_(\"Updates were rejected because a matching reference already exists in\\n\"\n+\t   \"the remote and the update is not a fast-forward.  Use git push -f if\\n\"\n+\t   \"you really want to make this update.\");\n+\n static void advise_pull_before_push(void)\n {\n \tif (!advice_push_non_ff_current || !advice_push_nonfastforward)\n@@ -241,6 +246,11 @@ static void advise_checkout_pull_push(void)\n \tadvise(_(message_advice_checkout_pull_push));\n }\n \n+static void advise_ref_already_exists(void)\n+{\n+\tadvise(_(message_advice_ref_already_exists));\n+}\n+\n static int push_with_options(struct transport *transport, int flags)\n {\n \tint err;\n@@ -265,13 +275,15 @@ static int push_with_options(struct transport *transport, int flags)\n \tif (!err)\n \t\treturn 0;\n \n-\tif (reject_mask & NON_FF_HEAD) {\n+\tif (reject_mask & REJECT_NON_FF_HEAD) {\n \t\tadvise_pull_before_push();\n-\t} else if (reject_mask & NON_FF_OTHER) {\n+\t} else if (reject_mask & REJECT_NON_FF_OTHER) {\n \t\tif (default_matching_used)\n \t\t\tadvise_use_upstream();\n \t\telse\n \t\t\tadvise_checkout_pull_push();\n+\t} else if (reject_mask & REJECT_ALREADY_EXISTS) {\n+\t\tadvise_ref_already_exists();\n \t}\n \n \treturn 1;\ndiff --git a/cache.h b/cache.h\nindex a58df84..bc2fc9a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1002,6 +1002,7 @@ struct ref {\n \tunsigned int force:1,\n \t\tmerge:1,\n \t\tnonfastforward:1,\n+\t\tforwardable:1,\n \t\tdeletion:1;\n \tenum {\n \t\tREF_STATUS_NONE = 0,\ndiff --git a/remote.c b/remote.c\nindex 04fd9ea..5ecd58d 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1316,6 +1316,13 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \t\t *     always allowed.\n \t\t */\n \n+\t\tif (prefixcmp(ref->name, \"refs/tags/\")) {\n+\t\t\tstruct object *old = parse_object(ref->old_sha1);\n+\t\t\tstruct object *new = parse_object(ref->new_sha1);\n+\t\t\tref->forwardable = (old && new &&\n+\t\t\t  old->type == OBJ_COMMIT && new->type == OBJ_COMMIT);\n+\t\t}\n+\n \t\tref->nonfastforward =\n \t\t\t!ref->deletion &&\n \t\t\t!is_null_sha1(ref->old_sha1) &&\ndiff --git a/transport.c b/transport.c\nindex ae9fda8..1657798 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -740,10 +740,12 @@ void transport_print_push_status(const char *dest, struct ref *refs,\n \t\t    ref->status != REF_STATUS_OK)\n \t\t\tn += print_one_push_status(ref, dest, n, porcelain);\n \t\tif (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {\n+\t\t\tif (!ref->forwardable)\n+\t\t\t\t*reject_mask |= REJECT_ALREADY_EXISTS;\n \t\t\tif (!strcmp(head, ref->name))\n-\t\t\t\t*reject_mask |= NON_FF_HEAD;\n+\t\t\t\t*reject_mask |= REJECT_NON_FF_HEAD;\n \t\t\telse\n-\t\t\t\t*reject_mask |= NON_FF_OTHER;\n+\t\t\t\t*reject_mask |= REJECT_NON_FF_OTHER;\n \t\t}\n \t}\n }\ndiff --git a/transport.h b/transport.h\nindex b9e124a..30b88e8 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -140,8 +140,9 @@ int transport_set_option(struct transport *transport, const char *name,\n void transport_set_verbosity(struct transport *transport, int verbosity,\n \tint force_progress);\n \n-#define NON_FF_HEAD     0x01\n-#define NON_FF_OTHER    0x02\n+#define REJECT_NON_FF_HEAD     0x01\n+#define REJECT_NON_FF_OTHER    0x02\n+#define REJECT_ALREADY_EXISTS  0x04\n \n int transport_push(struct transport *connection,\n \t\t   int refspec_nr, const char **refspec, int flags,\n-- \n1.7.1\n"},{"id":"202531","messageId":"1352084908-32333-4-git-send-email-chris@rorvick.com","threadId":"32016","inReplyTo":"1352084908-32333-1-git-send-email-chris@rorvick.com","subject":"[PATCH v2 3/5] push: flag updates","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-11-05T03:08:26Z","receivedAt":"2012-11-05T03:08:26Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"If the reference exists on the remote and the the update is not a\ndelete, then mark as an update.  This is in preparation for handling\ntags and branches differently when pushing.\n\nSigned-off-by: Chris Rorvick <chris@rorvick.com>\n---\n cache.h  |    1 +\n remote.c |   19 ++++++++++++-------\n 2 files changed, 13 insertions(+), 7 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex bc2fc9a..1d10761 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1003,6 +1003,7 @@ struct ref {\n \t\tmerge:1,\n \t\tnonfastforward:1,\n \t\tforwardable:1,\n+\t\tupdate:1,\n \t\tdeletion:1;\n \tenum {\n \t\tREF_STATUS_NONE = 0,\ndiff --git a/remote.c b/remote.c\nindex 5ecd58d..3d43bb5 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1323,15 +1323,20 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \t\t\t  old->type == OBJ_COMMIT && new->type == OBJ_COMMIT);\n \t\t}\n \n-\t\tref->nonfastforward =\n+\t\tref->update =\n \t\t\t!ref->deletion &&\n-\t\t\t!is_null_sha1(ref->old_sha1) &&\n-\t\t\t(!has_sha1_file(ref->old_sha1)\n-\t\t\t  || !ref_newer(ref->new_sha1, ref->old_sha1));\n+\t\t\t!is_null_sha1(ref->old_sha1);\n \n-\t\tif (ref->nonfastforward && !ref->force && !force_update) {\n-\t\t\tref->status = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\tcontinue;\n+\t\tif (ref->update) {\n+\t\t\tref->nonfastforward =\n+\t\t\t\tref->update &&\n+\t\t\t\t(!has_sha1_file(ref->old_sha1)\n+\t\t\t\t  || !ref_newer(ref->new_sha1, ref->old_sha1));\n+\n+\t\t\tif (ref->nonfastforward && !ref->force && !force_update) {\n+\t\t\t\tref->status = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t}\n \t}\n }\n-- \n1.7.1\n"},{"id":"202528","messageId":"1352084908-32333-5-git-send-email-chris@rorvick.com","threadId":"32016","inReplyTo":"1352084908-32333-1-git-send-email-chris@rorvick.com","subject":"[PATCH v2 4/5] push: flag updates that require force","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-11-05T03:08:27Z","receivedAt":"2012-11-05T03:08:27Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"Add a flag for indicating an update to a reference requires force.\nCurrently the nonfastforward flag of a ref is used for this when\ngenerating status the status message.  A separate flag insulates the\nstatus logic from the details of set_ref_status_for_push().\n\nSigned-off-by: Chris Rorvick <chris@rorvick.com>\n---\n cache.h     |    4 +++-\n remote.c    |   11 ++++++++---\n transport.c |    2 +-\n 3 files changed, 12 insertions(+), 5 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 1d10761..b74c744 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -999,7 +999,9 @@ struct ref {\n \tunsigned char old_sha1[20];\n \tunsigned char new_sha1[20];\n \tchar *symref;\n-\tunsigned int force:1,\n+\tunsigned int\n+\t\tforce:1,\n+\t\trequires_force:1,\n \t\tmerge:1,\n \t\tnonfastforward:1,\n \t\tforwardable:1,\ndiff --git a/remote.c b/remote.c\nindex 3d43bb5..552afd0 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1285,6 +1285,8 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \tstruct ref *ref;\n \n \tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\tint force_ref_update = ref->force || force_update;\n+\n \t\tif (ref->peer_ref)\n \t\t\thashcpy(ref->new_sha1, ref->peer_ref->new_sha1);\n \t\telse if (!send_mirror)\n@@ -1333,9 +1335,12 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \t\t\t\t(!has_sha1_file(ref->old_sha1)\n \t\t\t\t  || !ref_newer(ref->new_sha1, ref->old_sha1));\n \n-\t\t\tif (ref->nonfastforward && !ref->force && !force_update) {\n-\t\t\t\tref->status = REF_STATUS_REJECT_NONFASTFORWARD;\n-\t\t\t\tcontinue;\n+\t\t\tif (ref->nonfastforward) {\n+\t\t\t\tref->requires_force = 1;\n+\t\t\t\tif (!force_ref_update) {\n+\t\t\t\t\tref->status = REF_STATUS_REJECT_NONFASTFORWARD;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \t}\ndiff --git a/transport.c b/transport.c\nindex 1657798..632f8b0 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -659,7 +659,7 @@ static void print_ok_ref_status(struct ref *ref, int porcelain)\n \t\tconst char *msg;\n \n \t\tstrcpy(quickref, status_abbrev(ref->old_sha1));\n-\t\tif (ref->nonfastforward) {\n+\t\tif (ref->requires_force) {\n \t\t\tstrcat(quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\n-- \n1.7.1\n"},{"id":"202533","messageId":"1352084908-32333-6-git-send-email-chris@rorvick.com","threadId":"32016","inReplyTo":"1352084908-32333-1-git-send-email-chris@rorvick.com","subject":"[PATCH v2 5/5] push: update remote tags only with force","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-11-05T03:08:28Z","receivedAt":"2012-11-05T03:08:28Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"References are allowed to update from one commit-ish to another if the\nformer is a ancestor of the latter.  This behavior is oriented to\nbranches which are expected to move with commits.  Tag references are\nexpected to be static in a repository, though, thus an update to a\ntag (lightweight and annotated) should be rejected unless the update is\nforced.\n\nTo enable this functionality, the following checks have been added to\nset_ref_status_for_push() for updating refs (i.e, not new or deletion)\nto restrict fast-forwarding in pushes:\n\n  1) The old and new references must be commits.  If this fails,\n     it is not a valid update for a branch.\n\n  2) The reference name cannot start with \"refs/tags/\".  This\n     catches lightweight tags which (usually) point to commits\n     and therefore would not be caught by (1).\n\nIf either of these checks fails, then it is flagged (by default) with a\nstatus indicating the update is being rejected due to the reference\nalready existing in the remote.  This can be overridden by passing\n--force to git push.\n\nSigned-off-by: Chris Rorvick <chris@rorvick.com>\n---\n Documentation/git-push.txt |   10 +++++-----\n builtin/push.c             |    3 +--\n builtin/send-pack.c        |    6 ++++++\n cache.h                    |    1 +\n remote.c                   |    8 +++++++-\n t/t5516-fetch-push.sh      |   30 +++++++++++++++++++++++++++++-\n transport-helper.c         |    6 ++++++\n transport.c                |    8 ++++++--\n 8 files changed, 61 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex 22d2580..7107d76 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -51,11 +51,11 @@ be named. If `:`<dst> is omitted, the same ref as <src> will be\n updated.\n +\n The object referenced by <src> is used to update the <dst> reference\n-on the remote side, but by default this is only allowed if the\n-update can fast-forward <dst>.  By having the optional leading `+`,\n-you can tell git to update the <dst> ref even when the update is not a\n-fast-forward.  This does *not* attempt to merge <src> into <dst>.  See\n-EXAMPLES below for details.\n+on the remote side.  By default this is only allowed if the update is\n+a branch, and then only if it can fast-forward <dst>.  By having the\n+optional leading `+`, you can tell git to update the <dst> ref even when\n+the update is not a branch or it is not a fast-forward.  This does *not*\n+attempt to merge <src> into <dst>.  See EXAMPLES below for details.\n +\n `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.\n +\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 77340c0..d097348 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -222,8 +222,7 @@ static const char message_advice_checkout_pull_push[] =\n \n static const char message_advice_ref_already_exists[] =\n \tN_(\"Updates were rejected because a matching reference already exists in\\n\"\n-\t   \"the remote and the update is not a fast-forward.  Use git push -f if\\n\"\n-\t   \"you really want to make this update.\");\n+\t   \"the remote.  Use git push -f if you really want to make this update.\");\n \n static void advise_pull_before_push(void)\n {\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 7d05064..f159ec3 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -202,6 +202,11 @@ static void print_helper_status(struct ref *ref)\n \t\t\tmsg = \"non-fast forward\";\n \t\t\tbreak;\n \n+\t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n+\t\t\tres = \"error\";\n+\t\t\tmsg = \"already exists\";\n+\t\t\tbreak;\n+\n \t\tcase REF_STATUS_REJECT_NODELETE:\n \t\tcase REF_STATUS_REMOTE_REJECT:\n \t\t\tres = \"error\";\n@@ -288,6 +293,7 @@ int send_pack(struct send_pack_args *args,\n \t\t/* Check for statuses set by set_ref_status_for_push() */\n \t\tswitch (ref->status) {\n \t\tcase REF_STATUS_REJECT_NONFASTFORWARD:\n+\t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tcase REF_STATUS_UPTODATE:\n \t\t\tcontinue;\n \t\tdefault:\ndiff --git a/cache.h b/cache.h\nindex b74c744..77786b6 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1011,6 +1011,7 @@ struct ref {\n \t\tREF_STATUS_NONE = 0,\n \t\tREF_STATUS_OK,\n \t\tREF_STATUS_REJECT_NONFASTFORWARD,\n+\t\tREF_STATUS_REJECT_ALREADY_EXISTS,\n \t\tREF_STATUS_REJECT_NODELETE,\n \t\tREF_STATUS_UPTODATE,\n \t\tREF_STATUS_REMOTE_REJECT,\ndiff --git a/remote.c b/remote.c\nindex 552afd0..3e04f47 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1335,7 +1335,13 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \t\t\t\t(!has_sha1_file(ref->old_sha1)\n \t\t\t\t  || !ref_newer(ref->new_sha1, ref->old_sha1));\n \n-\t\t\tif (ref->nonfastforward) {\n+\t\t\tif (!ref->forwardable) {\n+\t\t\t\tref->requires_force = 1;\n+\t\t\t\tif (!force_ref_update) {\n+\t\t\t\t\tref->status = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t} else if (ref->nonfastforward) {\n \t\t\t\tref->requires_force = 1;\n \t\t\t\tif (!force_ref_update) {\n \t\t\t\t\tref->status = REF_STATUS_REJECT_NONFASTFORWARD;\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex b5417cc..afb9b1b 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -368,7 +368,7 @@ test_expect_success 'push with colon-less refspec (2)' '\n \t\tgit branch -D frotz\n \tfi &&\n \tgit tag -f frotz &&\n-\tgit push testrepo frotz &&\n+\tgit push -f testrepo frotz &&\n \tcheck_push_result $the_commit tags/frotz &&\n \tcheck_push_result $the_first_commit heads/frotz\n \n@@ -929,6 +929,34 @@ test_expect_success 'push into aliased refs (inconsistent)' '\n \t)\n '\n \n+test_expect_success 'push tag requires --force to update remote tag' '\n+\tmk_test heads/master &&\n+\tmk_child child1 &&\n+\tmk_child child2 &&\n+\t(\n+\t\tcd child1 &&\n+\t\tgit tag lw_tag &&\n+\t\tgit tag -a -m \"message 1\" ann_tag &&\n+\t\tgit push ../child2 lw_tag &&\n+\t\tgit push ../child2 ann_tag &&\n+\t\t>file1 &&\n+\t\tgit add file1 &&\n+\t\tgit commit -m \"file1\" &&\n+\t\tgit tag -f lw_tag &&\n+\t\tgit tag -f -a -m \"message 2\" ann_tag &&\n+\t\ttest_must_fail git push ../child2 lw_tag &&\n+\t\ttest_must_fail git push ../child2 ann_tag &&\n+\t\tgit push --force ../child2 lw_tag &&\n+\t\tgit push --force ../child2 ann_tag &&\n+\t\tgit tag -f lw_tag HEAD~ &&\n+\t\tgit tag -f -a -m \"message 3\" ann_tag &&\n+\t\ttest_must_fail git push ../child2 lw_tag &&\n+\t\ttest_must_fail git push ../child2 ann_tag &&\n+\t\tgit push --force ../child2 lw_tag &&\n+\t\tgit push --force ../child2 ann_tag\n+\t)\n+'\n+\n test_expect_success 'push --porcelain' '\n \tmk_empty &&\n \techo >.git/foo  \"To testrepo\" &&\ndiff --git a/transport-helper.c b/transport-helper.c\nindex cfe0988..ef9a6f8 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -643,6 +643,11 @@ static void push_update_ref_status(struct strbuf *buf,\n \t\t\tfree(msg);\n \t\t\tmsg = NULL;\n \t\t}\n+\t\telse if (!strcmp(msg, \"already exists\")) {\n+\t\t\tstatus = REF_STATUS_REJECT_ALREADY_EXISTS;\n+\t\t\tfree(msg);\n+\t\t\tmsg = NULL;\n+\t\t}\n \t}\n \n \tif (*ref)\n@@ -702,6 +707,7 @@ static int push_refs_with_push(struct transport *transport,\n \t\t/* Check for statuses set by set_ref_status_for_push() */\n \t\tswitch (ref->status) {\n \t\tcase REF_STATUS_REJECT_NONFASTFORWARD:\n+\t\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n \t\tcase REF_STATUS_UPTODATE:\n \t\t\tcontinue;\n \t\tdefault:\ndiff --git a/transport.c b/transport.c\nindex 632f8b0..c183971 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -695,6 +695,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i\n \t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n \t\t\t\t\t\t \"non-fast-forward\", porcelain);\n \t\tbreak;\n+\tcase REF_STATUS_REJECT_ALREADY_EXISTS:\n+\t\tprint_ref_status('!', \"[rejected]\", ref, ref->peer_ref,\n+\t\t\t\t\t\t \"already exists\", porcelain);\n+\t\tbreak;\n \tcase REF_STATUS_REMOTE_REJECT:\n \t\tprint_ref_status('!', \"[remote rejected]\", ref,\n \t\t\t\t\t\t ref->deletion ? NULL : ref->peer_ref,\n@@ -740,12 +744,12 @@ void transport_print_push_status(const char *dest, struct ref *refs,\n \t\t    ref->status != REF_STATUS_OK)\n \t\t\tn += print_one_push_status(ref, dest, n, porcelain);\n \t\tif (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {\n-\t\t\tif (!ref->forwardable)\n-\t\t\t\t*reject_mask |= REJECT_ALREADY_EXISTS;\n \t\t\tif (!strcmp(head, ref->name))\n \t\t\t\t*reject_mask |= REJECT_NON_FF_HEAD;\n \t\t\telse\n \t\t\t\t*reject_mask |= REJECT_NON_FF_OTHER;\n+\t\t} else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) {\n+\t\t\t\t*reject_mask |= REJECT_ALREADY_EXISTS;\n \t\t}\n \t}\n }\n-- \n1.7.1\n"},{"id":"202711","messageId":"20121109183535.GA22164@sigill.intra.peff.net","threadId":"32016","inReplyTo":"1352084908-32333-2-git-send-email-chris@rorvick.com","subject":"Re: [PATCH v2 1/5] push: return reject reasons via a mask","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-11-09T18:35:35Z","receivedAt":"2012-11-09T18:35:35Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Nov 04, 2012 at 09:08:24PM -0600, Chris Rorvick wrote:\n\n> Pass all rejection reasons back from transport_push().  The logic is\n> simpler and more flexible with regard to providing useful feedback.\n> [...]\n>  \n>  void transport_print_push_status(const char *dest, struct ref *refs,\n> -\t\t  int verbose, int porcelain, int *nonfastforward);\n> +\t\t  int verbose, int porcelain, unsigned int *reject_mask);\n\nYou missed updating one call-site of this function in\nbuiltin/send-pack.c (gcc -Wall will now complain of the signed/unsigned\nmismatch in the passed-in pointer).\n\n-Peff\n"},{"id":"202712","messageId":"20121109183834.GB22164@sigill.intra.peff.net","threadId":"32016","inReplyTo":"1352084908-32333-1-git-send-email-chris@rorvick.com","subject":"Re: [PATCH v2 0/5] push: update remote tags only with force","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2012-11-09T18:38:34Z","receivedAt":"2012-11-09T18:38:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Nov 04, 2012 at 09:08:23PM -0600, Chris Rorvick wrote:\n\n> Patch series to prevent push from updating remote tags w/o forcing them.\n> Split out original patch to ease review.\n> \n> Chris Rorvick (5):\n>   push: return reject reasons via a mask\n>   push: add advice for rejected tag reference\n>   push: flag updates\n>   push: flag updates that require force\n>   push: update remote tags only with force\n> \n>  Documentation/git-push.txt |   10 +++++-----\n>  builtin/push.c             |   24 +++++++++++++++---------\n>  builtin/send-pack.c        |    6 ++++++\n>  cache.h                    |    7 ++++++-\n>  remote.c                   |   39 +++++++++++++++++++++++++++++++--------\n>  t/t5516-fetch-push.sh      |   30 +++++++++++++++++++++++++++++-\n>  transport-helper.c         |    6 ++++++\n>  transport.c                |   25 +++++++++++++++----------\n>  transport.h                |   10 ++++++----\n>  9 files changed, 119 insertions(+), 38 deletions(-)\n\nI have not looked carefully at this topic yet, but I did try merging it\nto \"pu\" and found that it had some textual conflicts with the\nnd/builtin-to-libgit topic, which moves some builtin/send-pack.c code to\nsend-pack.c. Since I am graduating that topic to master, I went ahead\nand just rebased your topic on top.\n\nIf you do a re-roll, please use an updated master, and feel free to\ngrab (and double-check!) the rebase I am about to send out in 'pu'. I\nalso included the minor signed/unsigned pointer warning fixup in the\nrebase, too.\n\n-Peff\n"},{"id":"202733","messageId":"CAEUsAPbmimoEVoJjvXkOFacs-X6hu5b0NO1sHZ9eSSRM07yPFA@mail.gmail.com","threadId":"32016","inReplyTo":"20121109183834.GB22164@sigill.intra.peff.net","subject":"Re: [PATCH v2 0/5] push: update remote tags only with force","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-11-10T01:15:49Z","receivedAt":"2012-11-10T01:15:49Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"On Fri, Nov 9, 2012 at 12:38 PM, Jeff King <peff@peff.net> wrote:\n> On Sun, Nov 04, 2012 at 09:08:23PM -0600, Chris Rorvick wrote:\n>\n>> Patch series to prevent push from updating remote tags w/o forcing them.\n>> Split out original patch to ease review.\n>>\n>> Chris Rorvick (5):\n>>   push: return reject reasons via a mask\n>>   push: add advice for rejected tag reference\n>>   push: flag updates\n>>   push: flag updates that require force\n>>   push: update remote tags only with force\n>>\n>>  Documentation/git-push.txt |   10 +++++-----\n>>  builtin/push.c             |   24 +++++++++++++++---------\n>>  builtin/send-pack.c        |    6 ++++++\n>>  cache.h                    |    7 ++++++-\n>>  remote.c                   |   39 +++++++++++++++++++++++++++++++--------\n>>  t/t5516-fetch-push.sh      |   30 +++++++++++++++++++++++++++++-\n>>  transport-helper.c         |    6 ++++++\n>>  transport.c                |   25 +++++++++++++++----------\n>>  transport.h                |   10 ++++++----\n>>  9 files changed, 119 insertions(+), 38 deletions(-)\n>\n> I have not looked carefully at this topic yet, but I did try merging it\n> to \"pu\" and found that it had some textual conflicts with the\n> nd/builtin-to-libgit topic, which moves some builtin/send-pack.c code to\n> send-pack.c. Since I am graduating that topic to master, I went ahead\n> and just rebased your topic on top.\n>\n> If you do a re-roll, please use an updated master, and feel free to\n> grab (and double-check!) the rebase I am about to send out in 'pu'. I\n> also included the minor signed/unsigned pointer warning fixup in the\n> rebase, too.\n>\n> -Peff\n\nThanks, I've rebased and checked against the changes in pu.  Looks\ngood to me.  I have a couple of other minor fixes (see below for\ndetails.)  I'll include these in an update if there is sufficient\ninterest in this.\n\nChris\n\n-- 8< --\ndiff --git a/remote.c b/remote.c\nindex fde2a79..b025a38 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1351,9 +1351,8 @@ void set_ref_status_for_push(struct ref\n*remote_refs, int send\n\n                if (ref->update) {\n                        ref->nonfastforward =\n-                               ref->update &&\n-                               (!has_sha1_file(ref->old_sha1)\n-                                 || !ref_newer(ref->new_sha1, ref->old_sha1));\n+                               !has_sha1_file(ref->old_sha1)\n+                                 || !ref_newer(ref->new_sha1, ref->old_sha1);\n\n                        if (!ref->forwardable) {\n                                ref->requires_force = 1;\ndiff --git a/transport.c b/transport.c\nindex c183971..a380ad7 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -749,7 +749,7 @@ void transport_print_push_status(const char *dest,\nstruct ref *r\n                        else\n                                *reject_mask |= REJECT_NON_FF_OTHER;\n                } else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS) {\n-                               *reject_mask |= REJECT_ALREADY_EXISTS;\n+                       *reject_mask |= REJECT_ALREADY_EXISTS;\n                }\n        }\n }\n"}]}