Skip to content

Commit f25950f

Browse files
ctiwaldgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Christopher Tiwald <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0e2d57f commit f25950f

File tree

7 files changed

+99
-12
lines changed

7 files changed

+99
-12
lines changed

Documentation/config.txt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,23 @@ advice.*::
138138
+
139139
--
140140
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.
143158
statusHints::
144159
Directions on how to stage/unstage/add shown in the
145160
output of linkgit:git-status[1] and the template shown

advice.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#include "cache.h"
22

33
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;
47
int advice_status_hints = 1;
58
int advice_commit_before_merge = 1;
69
int advice_resolve_conflict = 1;
@@ -12,6 +15,9 @@ static struct {
1215
int *preference;
1316
} advice_config[] = {
1417
{ "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 },
1521
{ "statushints", &advice_status_hints },
1622
{ "commitbeforemerge", &advice_commit_before_merge },
1723
{ "resolveconflict", &advice_resolve_conflict },

advice.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include "git-compat-util.h"
55

66
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;
710
extern int advice_status_hints;
811
extern int advice_commit_before_merge;
912
extern int advice_resolve_conflict;

builtin/push.c

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ static int progress = -1;
2424
static const char **refspec;
2525
static int refspec_nr;
2626
static int refspec_alloc;
27+
static int default_matching_used;
2728

2829
static void add_refspec(const char *ref)
2930
{
@@ -95,6 +96,9 @@ static void setup_default_push_refspecs(struct remote *remote)
9596
{
9697
switch (push_default) {
9798
default:
99+
case PUSH_DEFAULT_UNSPECIFIED:
100+
default_matching_used = 1;
101+
/* fallthru */
98102
case PUSH_DEFAULT_MATCHING:
99103
add_refspec(":");
100104
break;
@@ -114,6 +118,45 @@ static void setup_default_push_refspecs(struct remote *remote)
114118
}
115119
}
116120

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+
117160
static int push_with_options(struct transport *transport, int flags)
118161
{
119162
int err;
@@ -135,14 +178,21 @@ static int push_with_options(struct transport *transport, int flags)
135178
error(_("failed to push some refs to '%s'"), transport->url);
136179

137180
err |= transport_disconnect(transport);
138-
139181
if (!err)
140182
return 0;
141183

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;
146196
}
147197

148198
return 1;

cache.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,8 @@ enum push_default_type {
625625
PUSH_DEFAULT_NOTHING = 0,
626626
PUSH_DEFAULT_MATCHING,
627627
PUSH_DEFAULT_UPSTREAM,
628-
PUSH_DEFAULT_CURRENT
628+
PUSH_DEFAULT_CURRENT,
629+
PUSH_DEFAULT_UNSPECIFIED
629630
};
630631

631632
extern enum branch_track git_branch_track;
@@ -1008,7 +1009,6 @@ struct ref {
10081009
char *symref;
10091010
unsigned int force:1,
10101011
merge:1,
1011-
nonfastforward:1,
10121012
deletion:1;
10131013
enum {
10141014
REF_STATUS_NONE = 0,
@@ -1019,6 +1019,10 @@ struct ref {
10191019
REF_STATUS_REMOTE_REJECT,
10201020
REF_STATUS_EXPECTING_REPORT
10211021
} status;
1022+
enum {
1023+
NON_FF_HEAD = 1,
1024+
NON_FF_OTHER
1025+
} nonfastforward;
10221026
char *remote_status;
10231027
struct ref *peer_ref; /* when renaming */
10241028
char name[FLEX_ARRAY]; /* more */

environment.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
5252
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
5353
enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
5454
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;
5656
#ifndef OBJECT_CREATION_MODE
5757
#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
5858
#endif

transport.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,10 @@ void transport_print_push_status(const char *dest, struct ref *refs,
721721
{
722722
struct ref *ref;
723723
int n = 0;
724+
unsigned char head_sha1[20];
725+
char *head;
726+
727+
head = resolve_refdup("HEAD", head_sha1, 1, NULL);
724728

725729
if (verbose) {
726730
for (ref = refs; ref; ref = ref->next)
@@ -738,8 +742,13 @@ void transport_print_push_status(const char *dest, struct ref *refs,
738742
ref->status != REF_STATUS_UPTODATE &&
739743
ref->status != REF_STATUS_OK)
740744
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+
}
743752
}
744753
}
745754

0 commit comments

Comments
 (0)