{"thread":{"id":"31986","subject":"[PATCH] git-push: update remote tags only with force","startedAt":"2012-10-31T05:37:55Z","lastAt":"2012-10-31T12:52:08Z","messageCount":5,"participants":["Chris Rorvick","Felipe Contreras","Drew Northup"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"202288","messageId":"1351661875-4307-1-git-send-email-chris@rorvick.com","threadId":"31986","inReplyTo":null,"subject":"[PATCH] git-push: update remote tags only with force","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-10-31T05:37:55Z","receivedAt":"2012-10-31T05:37:55Z","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\nThe new status has the added benefit of being able to provide accurate\nfeedback as to why the ref update failed and what can be done.\nCurrently all ref update rejections are assumed to be for branches.\n\nSigned-off-by: Chris Rorvick <chris@rorvick.com>\n---\n Documentation/git-push.txt | 10 +++++-----\n builtin/push.c             | 12 ++++++++++++\n builtin/send-pack.c        |  6 ++++++\n cache.h                    |  3 +++\n remote.c                   | 34 +++++++++++++++++++++++++++-------\n t/t5516-fetch-push.sh      | 30 +++++++++++++++++++++++++++++-\n transport-helper.c         |  6 ++++++\n transport.c                | 25 ++++++++++++++++---------\n transport.h                |  3 ++-\n 9 files changed, 106 insertions(+), 23 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 db9ba30..fabcea0 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -220,6 +220,10 @@ 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.  Use git push -f if 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 +245,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@@ -277,6 +286,9 @@ static int push_with_options(struct transport *transport, int flags)\n \t\telse\n \t\t\tadvise_checkout_pull_push();\n \t\tbreak;\n+\tcase ALREADY_EXISTS:\n+\t\tadvise_ref_already_exists();\n+\t\tbreak;\n \t}\n \n \treturn 1;\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 a58df84..2d160a9 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1002,11 +1002,14 @@ struct ref {\n \tunsigned int force:1,\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,\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 04fd9ea..0d94888 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1309,22 +1309,42 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \t\t *     to overwrite it; you would not know what you are losing\n \t\t *     otherwise.\n \t\t *\n-\t\t * (3) if both new and old are commit-ish, and new is a\n-\t\t *     descendant of old, it is OK.\n+\t\t * (3) if both new and old are commits, the reference is not\n+\t\t *     a tag, and new is a descendant of old, it is OK.\n \t\t *\n \t\t * (4) regardless of all of the above, removing :B is\n \t\t *     always allowed.\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!is_null_sha1(ref->old_sha1);\n+\n+\t\tref->nonfastforward =\n+\t\t\tref->update &&\n \t\t\t(!has_sha1_file(ref->old_sha1)\n \t\t\t  || !ref_newer(ref->new_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 (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} else\n+\t\t\tref->forwardable = 0;\n+\n+\t\tif (!ref->force && !force_update) {\n+\t\t\tif (ref->forwardable) {\n+\t\t\t\tif (ref->nonfastforward) {\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} else {\n+\t\t\t\tif (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}\n \t\t}\n \t}\n }\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex b5417cc..cff559f 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\t! git push ../child2 lw_tag &&\n+\t\t! 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\t! git push ../child2 lw_tag &&\n+\t\t! 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 9932f40..d218884 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->nonfastforward || (!ref->forwardable && ref->update)) {\n \t\t\tstrcat(quickref, \"...\");\n \t\t\ttype = '+';\n \t\t\tmsg = \"forced update\";\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@@ -714,7 +718,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, int *willnotupdate)\n {\n \tstruct ref *ref;\n \tint n = 0;\n@@ -733,18 +737,21 @@ 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*willnotupdate = 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\t    *willnotupdate != NON_FF_HEAD) {\n \t\t\tif (!strcmp(head, ref->name))\n-\t\t\t\t*nonfastforward = NON_FF_HEAD;\n+\t\t\t\t*willnotupdate = NON_FF_HEAD;\n \t\t\telse\n-\t\t\t\t*nonfastforward = NON_FF_OTHER;\n+\t\t\t\t*willnotupdate = NON_FF_OTHER;\n+\t\t} else if (ref->status == REF_STATUS_REJECT_ALREADY_EXISTS &&\n+\t\t    *willnotupdate == 0) {\n+\t\t\t\t*willnotupdate = ALREADY_EXISTS;\n \t\t}\n \t}\n }\n@@ -1031,9 +1038,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   int *willnotupdate)\n {\n-\t*nonfastforward = 0;\n+\t*willnotupdate = 0;\n \ttransport_verify_remote_names(refspec_nr, refspec);\n \n \tif (transport->push) {\n@@ -1099,7 +1106,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\twillnotupdate);\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..326271e 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -142,6 +142,7 @@ void transport_set_verbosity(struct transport *transport, int verbosity,\n \n #define NON_FF_HEAD 1\n #define NON_FF_OTHER 2\n+#define ALREADY_EXISTS 3\n int transport_push(struct transport *connection,\n \t\t   int refspec_nr, const char **refspec, int flags,\n \t\t   int * nonfastforward);\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, int *willnotupdate);\n \n typedef void alternate_ref_fn(const struct ref *, void *);\n extern void for_each_alternate_ref(alternate_ref_fn, void *);\n-- \n1.8.0.1.ga7c0bae\n"},{"id":"202289","messageId":"CAMP44s2T9Rmfjd8r+2+eYh8JBPXEofm3cHuEkkY+R3cW6R6HxA@mail.gmail.com","threadId":"31986","inReplyTo":"1351661875-4307-1-git-send-email-chris@rorvick.com","subject":"Re: [PATCH] git-push: update remote tags only with force","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2012-10-31T05:55:06Z","receivedAt":"2012-10-31T05:55:06Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Hi,\n\n(again because the mailing list rejected it) (Gmal switched interface\nand HTML is the default)\n\nOn Wed, Oct 31, 2012 at 6:37 AM, Chris Rorvick <chris@rorvick.com> wrote:\n>\n> References are allowed to update from one commit-ish to another if the\n> former is a ancestor of the latter.  This behavior is oriented to\n> branches which are expected to move with commits.  Tag references are\n> expected to be static in a repository, though, thus an update to a\n> tag (lightweight and annotated) should be rejected unless the update is\n> forced.\n>\n> To enable this functionality, the following checks have been added to\n> set_ref_status_for_push() for updating refs (i.e, not new or deletion)\n> to 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>\n> If either of these checks fails, then it is flagged (by default) with a\n> status indicating the update is being rejected due to the reference\n> already existing in the remote.  This can be overridden by passing\n> --force to git push.\n>\n> The new status has the added benefit of being able to provide accurate\n> feedback as to why the ref update failed and what can be done.\n> Currently all ref update rejections are assumed to be for branches.\n\nMakes sense to me. I've believe I've been hit by this a couple of\ntimes when tags were updated, and a colleague did 'git push' and they\nwent all back, or something like that. To handle that case properly\nprobably more changes are needed, but this is a change in the right\ndirection.\n\n> +test_expect_success 'push tag requires --force to update remote tag' '\n> +       mk_test heads/master &&\n> +       mk_child child1 &&\n> +       mk_child child2 &&\n> +       (\n> +               cd child1 &&\n> +               git tag lw_tag &&\n> +               git tag -a -m \"message 1\" ann_tag &&\n> +               git push ../child2 lw_tag &&\n> +               git push ../child2 ann_tag &&\n> +               >file1 &&\n> +               git add file1 &&\n> +               git commit -m \"file1\" &&\n> +               git tag -f lw_tag &&\n> +               git tag -f -a -m \"message 2\" ann_tag &&\n> +               ! git push ../child2 lw_tag &&\n\nYou probably should use test_must_fail.\n\nI don't see anything wrong with the patch, but I wonder if it might be\npossible to split it to ease the review.\n\nCheers.\n\n--\nFelipe Contreras\n"},{"id":"202304","messageId":"CAM9Z-nk6YRtNXNHbp-ReXB78V0O01qng+zmWfzm7Yxz51x22Yw@mail.gmail.com","threadId":"31986","inReplyTo":"CAMP44s2T9Rmfjd8r+2+eYh8JBPXEofm3cHuEkkY+R3cW6R6HxA@mail.gmail.com","subject":"Re: [PATCH] git-push: update remote tags only with force","fromName":"Drew Northup","fromEmail":"n1xim.email@gmail.com","sentAt":"2012-10-31T12:26:58Z","receivedAt":"2012-10-31T12:26:58Z","isPatch":true,"sender":{"key":"n1xim.email@gmail.com","avatar":null},"body":"On Wed, Oct 31, 2012 at 1:55 AM, Felipe Contreras\n<felipe.contreras@gmail.com> wrote:\n\n> but I wonder if it might be\n> possible to split it to ease the review.\n\nAgreed. Also, do please CC ALL interested parties from the pre-patch\ndiscussion thread as well as those who previously maintained that\nchunk of code.\n\n[Attempted to reconstruct CC list of discussion]\n\n-- \n-Drew Northup\n--------------------------------------------------------------\n\"As opposed to vegetable or mineral error?\"\n-John Pescatore, SANS NewsBites Vol. 12 Num. 59\n"},{"id":"202307","messageId":"CAEUsAPbpvuvoEEU8g7+dgnF=97dm76CUTgbdWOpDWTY5LoWysg@mail.gmail.com","threadId":"31986","inReplyTo":"CAMP44s2T9Rmfjd8r+2+eYh8JBPXEofm3cHuEkkY+R3cW6R6HxA@mail.gmail.com","subject":"Re: [PATCH] git-push: update remote tags only with force","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-10-31T12:36:41Z","receivedAt":"2012-10-31T12:36:41Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"(oops, now my email was rejected)\n\nOn Wed, Oct 31, 2012 at 12:55 AM, Felipe Contreras\n<felipe.contreras@gmail.com> wrote:\n> Hi,\n>\n> (again because the mailing list rejected it) (Gmal switched interface\n> and HTML is the default)\n>\n> On Wed, Oct 31, 2012 at 6:37 AM, Chris Rorvick <chris@rorvick.com> wrote:\n>>\n>> References are allowed to update from one commit-ish to another if the\n>> former is a ancestor of the latter.  This behavior is oriented to\n>> branches which are expected to move with commits.  Tag references are\n>> expected to be static in a repository, though, thus an update to a\n>> tag (lightweight and annotated) should be rejected unless the update is\n>> forced.\n>>\n>> To enable this functionality, the following checks have been added to\n>> set_ref_status_for_push() for updating refs (i.e, not new or deletion)\n>> to 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>>\n>> If either of these checks fails, then it is flagged (by default) with a\n>> status indicating the update is being rejected due to the reference\n>> already existing in the remote.  This can be overridden by passing\n>> --force to git push.\n>>\n>> The new status has the added benefit of being able to provide accurate\n>> feedback as to why the ref update failed and what can be done.\n>> Currently all ref update rejections are assumed to be for branches.\n>\n> Makes sense to me. I've believe I've been hit by this a couple of\n> times when tags were updated, and a colleague did 'git push' and they\n> went all back, or something like that. To handle that case properly\n> probably more changes are needed, but this is a change in the right\n> direction.\n>\n>> +test_expect_success 'push tag requires --force to update remote tag' '\n>> +       mk_test heads/master &&\n>> +       mk_child child1 &&\n>> +       mk_child child2 &&\n>> +       (\n>> +               cd child1 &&\n>> +               git tag lw_tag &&\n>> +               git tag -a -m \"message 1\" ann_tag &&\n>> +               git push ../child2 lw_tag &&\n>> +               git push ../child2 ann_tag &&\n>> +               >file1 &&\n>> +               git add file1 &&\n>> +               git commit -m \"file1\" &&\n>> +               git tag -f lw_tag &&\n>> +               git tag -f -a -m \"message 2\" ann_tag &&\n>> +               ! git push ../child2 lw_tag &&\n>\n> You probably should use test_must_fail.\n\nThanks, will fix.\n\n> I don't see anything wrong with the patch, but I wonder if it might be\n> possible to split it to ease the review.\n\nI initially thought I'd split it into two: 1) to improve the feedback\nand 2) to change the behavior.  But (1) was shaping up to be similar\nin size to the sum so I scrapped that idea.  I will see what I can do.\n\nThanks,\n\nChris\n"},{"id":"202308","messageId":"CAEUsAPYOS8YYw3NjRN+Hnaib9jSTZttFVR+FJjGWWaEK4jsmVw@mail.gmail.com","threadId":"31986","inReplyTo":"CAM9Z-nk6YRtNXNHbp-ReXB78V0O01qng+zmWfzm7Yxz51x22Yw@mail.gmail.com","subject":"Re: [PATCH] git-push: update remote tags only with force","fromName":"Chris Rorvick","fromEmail":"chris@rorvick.com","sentAt":"2012-10-31T12:52:08Z","receivedAt":"2012-10-31T12:52:08Z","isPatch":true,"sender":{"key":"chris@rorvick.com","avatar":"https://avatars.githubusercontent.com/u/824726?v=4"},"body":"On Wed, Oct 31, 2012 at 7:26 AM, Drew Northup <n1xim.email@gmail.com> wrote:\n> On Wed, Oct 31, 2012 at 1:55 AM, Felipe Contreras\n> <felipe.contreras@gmail.com> wrote:\n>\n>> but I wonder if it might be\n>> possible to split it to ease the review.\n>\n> Agreed.\n\nI'll look at splitting it up, probably not tonight though.  :-)\n\n> Also, do please CC ALL interested parties from the pre-patch\n> discussion thread as well as those who previously maintained that\n> chunk of code.\n>\n> [Attempted to reconstruct CC list of discussion]\n\nThanks for fixing that.\n\nChris\n"}]}