push: Provide situational hints for non-fast-forward errors

Pushing a non-fast-forward update to a remote repository will result in an error, but the hint text doesn't provide the correct resolution in every case. Give better resolution advice in three push scenarios: 1) If you push your current branch and it triggers a non-fast-forward error, you should merge remote changes with 'git pull' before pushing again. 2) If you push to a shared repository others push to, and your local tracking branches are not kept up to date, the 'matching refs' default will generate non-fast-forward errors on outdated branches. If this is your workflow, the 'matching refs' default is not for you. Consider setting the 'push.default' configuration variable to 'current' or 'upstream' to ensure only your current branch is pushed. 3) If you explicitly specify a ref that is not your current branch or push matching branches with ':', you will generate a non-fast-forward error if any pushed branch tip is out of date. You should checkout the offending branch and merge remote changes before pushing again. Teach transport.c to recognize these scenarios and configure push.c to hint for them. If 'git push's default behavior changes or we discover more scenarios, extension is easy. Standardize on the advice API and add three new advice variables, 'pushNonFFCurrent', 'pushNonFFDefault', and 'pushNonFFMatching'. Setting any of these to 'false' will disable their affiliated advice. Setting 'pushNonFastForward' to false will disable all three, thus preserving the config option for users who already set it, but guaranteeing new users won't disable push advice accidentally. Based-on-patch-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Christopher Tiwald <christiwald@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Christopher Tiwald committed Mar 20, 2012 at 00:31 UTC f25950f3475e263ed6c8f0797bb058ba6444f85e
7 files changed +99 -12
Documentation/config.txt
+17 -2
@@ -138,8 +138,23 @@ advice.*::
138 +
139 --
140 pushNonFastForward::
141 - Advice shown when linkgit:git-push[1] refuses
142 - non-fast-forward refs.
141 + Set this variable to 'false' if you want to disable
142 + 'pushNonFFCurrent', 'pushNonFFDefault', and
143 + 'pushNonFFMatching' simultaneously.
144 + pushNonFFCurrent::
145 + Advice shown when linkgit:git-push[1] fails due to a
146 + non-fast-forward update to the current branch.
147 + pushNonFFDefault::
148 + Advice to set 'push.default' to 'upstream' or 'current'
149 + when you ran linkgit:git-push[1] and pushed 'matching
150 + refs' by default (i.e. you did not provide an explicit
151 + refspec, and no 'push.default' configuration was set)
152 + and it resulted in a non-fast-forward error.
153 + pushNonFFMatching::
154 + Advice shown when you ran linkgit:git-push[1] and pushed
155 + 'matching refs' explicitly (i.e. you used ':', or
156 + specified a refspec that isn't your current branch) and
157 + it resulted in a non-fast-forward error.
158 statusHints::
159 Directions on how to stage/unstage/add shown in the
160 output of linkgit:git-status[1] and the template shown
advice.c
+6
@@ -1,6 +1,9 @@
1 #include "cache.h"
2
3 int advice_push_nonfastforward = 1;
4 +int advice_push_non_ff_current = 1;
5 +int advice_push_non_ff_default = 1;
6 +int advice_push_non_ff_matching = 1;
7 int advice_status_hints = 1;
8 int advice_commit_before_merge = 1;
9 int advice_resolve_conflict = 1;
@@ -12,6 +15,9 @@ static struct {
15 int *preference;
16 } advice_config[] = {
17 { "pushnonfastforward", &advice_push_nonfastforward },
18 + { "pushnonffcurrent", &advice_push_non_ff_current },
19 + { "pushnonffdefault", &advice_push_non_ff_default },
20 + { "pushnonffmatching", &advice_push_non_ff_matching },
21 { "statushints", &advice_status_hints },
22 { "commitbeforemerge", &advice_commit_before_merge },
23 { "resolveconflict", &advice_resolve_conflict },
advice.h
+3
@@ -4,6 +4,9 @@
4 #include "git-compat-util.h"
5
6 extern int advice_push_nonfastforward;
7 +extern int advice_push_non_ff_current;
8 +extern int advice_push_non_ff_default;
9 +extern int advice_push_non_ff_matching;
10 extern int advice_status_hints;
11 extern int advice_commit_before_merge;
12 extern int advice_resolve_conflict;
builtin/push.c
+55 -5
@@ -24,6 +24,7 @@ static int progress = -1;
24 static const char **refspec;
25 static int refspec_nr;
26 static int refspec_alloc;
27 +static int default_matching_used;
28
29 static void add_refspec(const char *ref)
30 {
@@ -95,6 +96,9 @@ static void setup_default_push_refspecs(struct remote *remote)
96 {
97 switch (push_default) {
98 default:
99 + case PUSH_DEFAULT_UNSPECIFIED:
100 + default_matching_used = 1;
101 + /* fallthru */
102 case PUSH_DEFAULT_MATCHING:
103 add_refspec(":");
104 break;
@@ -114,6 +118,45 @@ static void setup_default_push_refspecs(struct remote *remote)
118 }
119 }
120
121 +static const char message_advice_pull_before_push[] =
122 + N_("Updates were rejected because the tip of your current branch is behind\n"
123 + "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
124 + "before pushing again.\n"
125 + "See the 'Note about fast-forwards' in 'git push --help' for details.");
126 +
127 +static const char message_advice_use_upstream[] =
128 + N_("Updates were rejected because a pushed branch tip is behind its remote\n"
129 + "counterpart. If you did not intend to push that branch, you may want to\n"
130 + "specify branches to push or set the 'push.default' configuration\n"
131 + "variable to 'current' or 'upstream' to push only the current branch.");
132 +
133 +static const char message_advice_checkout_pull_push[] =
134 + N_("Updates were rejected because a pushed branch tip is behind its remote\n"
135 + "counterpart. Check out this branch and merge the remote changes\n"
136 + "(e.g. 'git pull') before pushing again.\n"
137 + "See the 'Note about fast-forwards' in 'git push --help' for details.");
138 +
139 +static void advise_pull_before_push(void)
140 +{
141 + if (!advice_push_non_ff_current || !advice_push_nonfastforward)
142 + return;
143 + advise(_(message_advice_pull_before_push));
144 +}
145 +
146 +static void advise_use_upstream(void)
147 +{
148 + if (!advice_push_non_ff_default || !advice_push_nonfastforward)
149 + return;
150 + advise(_(message_advice_use_upstream));
151 +}
152 +
153 +static void advise_checkout_pull_push(void)
154 +{
155 + if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
156 + return;
157 + advise(_(message_advice_checkout_pull_push));
158 +}
159 +
160 static int push_with_options(struct transport *transport, int flags)
161 {
162 int err;
@@ -135,14 +178,21 @@ static int push_with_options(struct transport *transport, int flags)
178 error(_("failed to push some refs to '%s'"), transport->url);
179
180 err |= transport_disconnect(transport);
138 -
181 if (!err)
182 return 0;
183
142 - if (nonfastforward && advice_push_nonfastforward) {
143 - fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
144 - "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n"
145 - "'Note about fast-forwards' section of 'git push --help' for details.\n"));
184 + switch (nonfastforward) {
185 + default:
186 + break;
187 + case NON_FF_HEAD:
188 + advise_pull_before_push();
189 + break;
190 + case NON_FF_OTHER:
191 + if (default_matching_used)
192 + advise_use_upstream();
193 + else
194 + advise_checkout_pull_push();
195 + break;
196 }
197
198 return 1;
cache.h
+6 -2
@@ -625,7 +625,8 @@ enum push_default_type {
625 PUSH_DEFAULT_NOTHING = 0,
626 PUSH_DEFAULT_MATCHING,
627 PUSH_DEFAULT_UPSTREAM,
628 - PUSH_DEFAULT_CURRENT
628 + PUSH_DEFAULT_CURRENT,
629 + PUSH_DEFAULT_UNSPECIFIED
630 };
631
632 extern enum branch_track git_branch_track;
@@ -1008,7 +1009,6 @@ struct ref {
1009 char *symref;
1010 unsigned int force:1,
1011 merge:1,
1011 - nonfastforward:1,
1012 deletion:1;
1013 enum {
1014 REF_STATUS_NONE = 0,
@@ -1019,6 +1019,10 @@ struct ref {
1019 REF_STATUS_REMOTE_REJECT,
1020 REF_STATUS_EXPECTING_REPORT
1021 } status;
1022 + enum {
1023 + NON_FF_HEAD = 1,
1024 + NON_FF_OTHER
1025 + } nonfastforward;
1026 char *remote_status;
1027 struct ref *peer_ref; /* when renaming */
1028 char name[FLEX_ARRAY]; /* more */
environment.c
+1 -1
@@ -52,7 +52,7 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
52 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
53 enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
54 enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
55 -enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
55 +enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
56 #ifndef OBJECT_CREATION_MODE
57 #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
58 #endif
transport.c
+11 -2
@@ -721,6 +721,10 @@ void transport_print_push_status(const char *dest, struct ref *refs,
721 {
722 struct ref *ref;
723 int n = 0;
724 + unsigned char head_sha1[20];
725 + char *head;
726 +
727 + head = resolve_refdup("HEAD", head_sha1, 1, NULL);
728
729 if (verbose) {
730 for (ref = refs; ref; ref = ref->next)
@@ -738,8 +742,13 @@ void transport_print_push_status(const char *dest, struct ref *refs,
742 ref->status != REF_STATUS_UPTODATE &&
743 ref->status != REF_STATUS_OK)
744 n += print_one_push_status(ref, dest, n, porcelain);
741 - if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
742 - *nonfastforward = 1;
745 + if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&
746 + *nonfastforward != NON_FF_HEAD) {
747 + if (!strcmp(head, ref->name))
748 + *nonfastforward = NON_FF_HEAD;
749 + else
750 + *nonfastforward = NON_FF_OTHER;
751 + }
752 }
753 }
754