Merge branch 'ct/advise-push-default' into maint

The cases "git push" fails due to non-ff can be broken into three
categories; each case is given a separate advise message.

By Christopher Tiwald (2) and Jeff King (1)
* ct/advise-push-default:
  Fix httpd tests that broke when non-ff push advice changed
  clean up struct ref's nonfastforward field
  push: Provide situational hints for non-fast-forward errors
diff --git a/Documentation/config.txt b/Documentation/config.txt
index e67c8ef..83ad8eb 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -141,8 +141,23 @@
 +
 --
 	pushNonFastForward::
-		Advice shown when linkgit:git-push[1] refuses
-		non-fast-forward refs.
+		Set this variable to 'false' if you want to disable
+		'pushNonFFCurrent', 'pushNonFFDefault', and
+		'pushNonFFMatching' simultaneously.
+	pushNonFFCurrent::
+		Advice shown when linkgit:git-push[1] fails due to a
+		non-fast-forward update to the current branch.
+	pushNonFFDefault::
+		Advice to set 'push.default' to 'upstream' or 'current'
+		when you ran linkgit:git-push[1] and pushed 'matching
+		refs' by default (i.e. you did not provide an explicit
+		refspec, and no 'push.default' configuration was set)
+		and it resulted in a non-fast-forward error.
+	pushNonFFMatching::
+		Advice shown when you ran linkgit:git-push[1] and pushed
+		'matching refs' explicitly (i.e. you used ':', or
+		specified a refspec that isn't your current branch) and
+		it resulted in a non-fast-forward error.
 	statusHints::
 		Directions on how to stage/unstage/add shown in the
 		output of linkgit:git-status[1] and the template shown
diff --git a/advice.c b/advice.c
index 01130e5..a492eea 100644
--- a/advice.c
+++ b/advice.c
@@ -1,6 +1,9 @@
 #include "cache.h"
 
 int advice_push_nonfastforward = 1;
+int advice_push_non_ff_current = 1;
+int advice_push_non_ff_default = 1;
+int advice_push_non_ff_matching = 1;
 int advice_status_hints = 1;
 int advice_commit_before_merge = 1;
 int advice_resolve_conflict = 1;
@@ -12,6 +15,9 @@
 	int *preference;
 } advice_config[] = {
 	{ "pushnonfastforward", &advice_push_nonfastforward },
+	{ "pushnonffcurrent", &advice_push_non_ff_current },
+	{ "pushnonffdefault", &advice_push_non_ff_default },
+	{ "pushnonffmatching", &advice_push_non_ff_matching },
 	{ "statushints", &advice_status_hints },
 	{ "commitbeforemerge", &advice_commit_before_merge },
 	{ "resolveconflict", &advice_resolve_conflict },
diff --git a/advice.h b/advice.h
index 7bda45b..f3cdbbf 100644
--- a/advice.h
+++ b/advice.h
@@ -4,6 +4,9 @@
 #include "git-compat-util.h"
 
 extern int advice_push_nonfastforward;
+extern int advice_push_non_ff_current;
+extern int advice_push_non_ff_default;
+extern int advice_push_non_ff_matching;
 extern int advice_status_hints;
 extern int advice_commit_before_merge;
 extern int advice_resolve_conflict;
diff --git a/builtin/push.c b/builtin/push.c
index b6c0fee..6936713 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -24,6 +24,7 @@
 static const char **refspec;
 static int refspec_nr;
 static int refspec_alloc;
+static int default_matching_used;
 
 static void add_refspec(const char *ref)
 {
@@ -111,6 +112,9 @@
 {
 	switch (push_default) {
 	default:
+	case PUSH_DEFAULT_UNSPECIFIED:
+		default_matching_used = 1;
+		/* fallthru */
 	case PUSH_DEFAULT_MATCHING:
 		add_refspec(":");
 		break;
@@ -130,6 +134,45 @@
 	}
 }
 
+static const char message_advice_pull_before_push[] =
+	N_("Updates were rejected because the tip of your current branch is behind\n"
+	   "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
+	   "before pushing again.\n"
+	   "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
+static const char message_advice_use_upstream[] =
+	N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+	   "counterpart. If you did not intend to push that branch, you may want to\n"
+	   "specify branches to push or set the 'push.default' configuration\n"
+	   "variable to 'current' or 'upstream' to push only the current branch.");
+
+static const char message_advice_checkout_pull_push[] =
+	N_("Updates were rejected because a pushed branch tip is behind its remote\n"
+	   "counterpart. Check out this branch and merge the remote changes\n"
+	   "(e.g. 'git pull') before pushing again.\n"
+	   "See the 'Note about fast-forwards' in 'git push --help' for details.");
+
+static void advise_pull_before_push(void)
+{
+	if (!advice_push_non_ff_current || !advice_push_nonfastforward)
+		return;
+	advise(_(message_advice_pull_before_push));
+}
+
+static void advise_use_upstream(void)
+{
+	if (!advice_push_non_ff_default || !advice_push_nonfastforward)
+		return;
+	advise(_(message_advice_use_upstream));
+}
+
+static void advise_checkout_pull_push(void)
+{
+	if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
+		return;
+	advise(_(message_advice_checkout_pull_push));
+}
+
 static int push_with_options(struct transport *transport, int flags)
 {
 	int err;
@@ -151,14 +194,21 @@
 		error(_("failed to push some refs to '%s'"), transport->url);
 
 	err |= transport_disconnect(transport);
-
 	if (!err)
 		return 0;
 
-	if (nonfastforward && advice_push_nonfastforward) {
-		fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
-				"Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
-				"'Note about fast-forwards' section of 'git push --help' for details.\n"));
+	switch (nonfastforward) {
+	default:
+		break;
+	case NON_FF_HEAD:
+		advise_pull_before_push();
+		break;
+	case NON_FF_OTHER:
+		if (default_matching_used)
+			advise_use_upstream();
+		else
+			advise_checkout_pull_push();
+		break;
 	}
 
 	return 1;
diff --git a/cache.h b/cache.h
index 5e6eef9..5bf59ff 100644
--- a/cache.h
+++ b/cache.h
@@ -625,7 +625,8 @@
 	PUSH_DEFAULT_NOTHING = 0,
 	PUSH_DEFAULT_MATCHING,
 	PUSH_DEFAULT_UPSTREAM,
-	PUSH_DEFAULT_CURRENT
+	PUSH_DEFAULT_CURRENT,
+	PUSH_DEFAULT_UNSPECIFIED
 };
 
 extern enum branch_track git_branch_track;
diff --git a/environment.c b/environment.c
index c93b8f4..d7e6c65 100644
--- a/environment.c
+++ b/environment.c
@@ -52,7 +52,7 @@
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
 enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
 enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
-enum push_default_type push_default = PUSH_DEFAULT_MATCHING;
+enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
 #ifndef OBJECT_CREATION_MODE
 #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
 #endif
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh
index f7dc078..094d490 100644
--- a/t/lib-httpd.sh
+++ b/t/lib-httpd.sh
@@ -160,6 +160,6 @@
 	'
 
 	test_expect_success 'non-fast-forward push shows help message' '
-		test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" output
+		test_i18ngrep "Updates were rejected because" output
 	'
 }
diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh
index 1f5bfa2..312e484 100755
--- a/t/t5541-http-push.sh
+++ b/t/t5541-http-push.sh
@@ -168,7 +168,7 @@
 '
 
 test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper: our output' '
-	test_i18ngrep "To prevent you from losing history, non-fast-forward updates were rejected" \
+	test_i18ngrep "Updates were rejected because" \
 		output
 '
 
diff --git a/transport.c b/transport.c
index ea9dcb6..2dfac70 100644
--- a/transport.c
+++ b/transport.c
@@ -721,6 +721,10 @@
 {
 	struct ref *ref;
 	int n = 0;
+	unsigned char head_sha1[20];
+	char *head;
+
+	head = resolve_refdup("HEAD", head_sha1, 1, NULL);
 
 	if (verbose) {
 		for (ref = refs; ref; ref = ref->next)
@@ -738,8 +742,13 @@
 		    ref->status != REF_STATUS_UPTODATE &&
 		    ref->status != REF_STATUS_OK)
 			n += print_one_push_status(ref, dest, n, porcelain);
-		if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
-			*nonfastforward = 1;
+		if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&
+		    *nonfastforward != NON_FF_HEAD) {
+			if (!strcmp(head, ref->name))
+				*nonfastforward = NON_FF_HEAD;
+			else
+				*nonfastforward = NON_FF_OTHER;
+		}
 	}
 }
 
diff --git a/transport.h b/transport.h
index ce99ef8..1631a35 100644
--- a/transport.h
+++ b/transport.h
@@ -138,6 +138,8 @@
 void transport_set_verbosity(struct transport *transport, int verbosity,
 	int force_progress);
 
+#define NON_FF_HEAD 1
+#define NON_FF_OTHER 2
 int transport_push(struct transport *connection,
 		   int refspec_nr, const char **refspec, int flags,
 		   int * nonfastforward);