diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 1e0c7af014..445341a906 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -9,7 +9,9 @@ branch.autoSetupMerge::
 	automatic setup is done when the starting point is either a
 	local branch or remote-tracking branch; `inherit` -- if the starting point
 	has a tracking configuration, it is copied to the new
-	branch. This option defaults to true.
+	branch; `simple` -- automatic setup is done only when the starting point
+	is a remote-tracking branch and the new branch has the same name as the
+	remote branch. This option defaults to true.
 
 branch.autoSetupRebase::
 	When a new branch is created with 'git branch', 'git switch' or 'git checkout'
@@ -38,8 +40,9 @@ branch.<name>.remote::
 	may be overridden with `remote.pushDefault` (for all branches).
 	The remote to push to, for the current branch, may be further
 	overridden by `branch.<name>.pushRemote`.  If no remote is
-	configured, or if you are not on any branch, it defaults to
-	`origin` for fetching and `remote.pushDefault` for pushing.
+	configured, or if you are not on any branch and there is more than
+	one remote defined in the repository, it defaults to `origin` for
+	fetching and `remote.pushDefault` for pushing.
 	Additionally, `.` (a period) is the current local repository
 	(a dot-repository), see `branch.<name>.merge`'s final note below.
 
diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt
index 632033638c..e32801e6c9 100644
--- a/Documentation/config/push.txt
+++ b/Documentation/config/push.txt
@@ -1,3 +1,14 @@
+push.autoSetupRemote::
+	If set to "true" assume `--set-upstream` on default push when no
+	upstream tracking exists for the current branch; this option
+	takes effect with push.default options 'simple', 'upstream',
+	and 'current'. It is useful if by default you want new branches
+	to be pushed to the default remote (like the behavior of
+	'push.default=current') and you also want the upstream tracking
+	to be set. Workflows most likely to benefit from this option are
+	'simple' central workflows where all branches are expected to
+	have the same name on the remote.
+
 push.default::
 	Defines the action `git push` should take if no refspec is
 	given (whether from the command-line, config, or elsewhere).
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index c8b4f9ce3c..ae82378349 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -221,13 +221,17 @@ The exact upstream branch is chosen depending on the optional argument:
 itself as the upstream; `--track=inherit` means to copy the upstream
 configuration of the start-point branch.
 +
-`--track=direct` is the default when the start point is a remote-tracking branch.
-Set the branch.autoSetupMerge configuration variable to `false` if you
-want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
-were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch. Set it to
-`inherit` if you want to copy the tracking configuration from the
-branch point.
+The branch.autoSetupMerge configuration variable specifies how `git switch`,
+`git checkout` and `git branch` should behave when neither `--track` nor
+`--no-track` are specified:
++
+The default option, `true`, behaves as though `--track=direct`
+were given whenever the start-point is a remote-tracking branch.
+`false` behaves as if `--no-track` were given. `always` behaves as though
+`--track=direct` were given. `inherit` behaves as though `--track=inherit`
+were given. `simple` behaves as though `--track=direct` were given only when
+the start-point is a remote-tracking branch and the new branch has the same
+name as the remote branch.
 +
 See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on
 how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
diff --git a/branch.c b/branch.c
index bde705b092..2d6569b0c6 100644
--- a/branch.c
+++ b/branch.c
@@ -44,9 +44,9 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 			string_list_clear(tracking->srcs, 0);
 		break;
 		}
+		/* remote_find_tracking() searches by src if present */
 		tracking->spec.src = NULL;
 	}
-
 	return 0;
 }
 
@@ -264,15 +264,23 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 
 	if (!tracking.matches)
 		switch (track) {
+		/* If ref is not remote, still use local */
 		case BRANCH_TRACK_ALWAYS:
 		case BRANCH_TRACK_EXPLICIT:
 		case BRANCH_TRACK_OVERRIDE:
+		/* Remote matches not evaluated */
 		case BRANCH_TRACK_INHERIT:
 			break;
+		/* Otherwise, if no remote don't track */
 		default:
 			goto cleanup;
 		}
 
+	/*
+	 * This check does not apply to BRANCH_TRACK_INHERIT;
+	 * that supports multiple entries in tracking_srcs but
+	 * leaves tracking.matches at 0.
+	 */
 	if (tracking.matches > 1) {
 		int status = die_message(_("not tracking: ambiguous information for ref '%s'"),
 					    orig_ref);
@@ -307,6 +315,21 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		exit(status);
 	}
 
+	if (track == BRANCH_TRACK_SIMPLE) {
+		/*
+		 * Only track if remote branch name matches.
+		 * Reaching into items[0].string is safe because
+		 * we know there is at least one and not more than
+		 * one entry (because only BRANCH_TRACK_INHERIT can
+		 * produce more than one entry).
+		 */
+		const char *tracked_branch;
+		if (!skip_prefix(tracking.srcs->items[0].string,
+				 "refs/heads/", &tracked_branch) ||
+		    strcmp(tracked_branch, new_ref))
+			return;
+	}
+
 	if (tracking.srcs->nr < 1)
 		string_list_append(tracking.srcs, orig_ref);
 	if (install_branch_config_multiple_remotes(config_flags, new_ref,
@@ -603,6 +626,8 @@ static int submodule_create_branch(struct repository *r,
 		/* Default for "git checkout". Do not pass --track. */
 	case BRANCH_TRACK_REMOTE:
 		/* Default for "git branch". Do not pass --track. */
+	case BRANCH_TRACK_SIMPLE:
+		/* Config-driven only. Do not pass --track. */
 		break;
 	}
 
diff --git a/branch.h b/branch.h
index 04df2aa5b5..560b6b96a8 100644
--- a/branch.h
+++ b/branch.h
@@ -12,6 +12,7 @@ enum branch_track {
 	BRANCH_TRACK_EXPLICIT,
 	BRANCH_TRACK_OVERRIDE,
 	BRANCH_TRACK_INHERIT,
+	BRANCH_TRACK_SIMPLE,
 };
 
 extern enum branch_track git_branch_track;
diff --git a/builtin/push.c b/builtin/push.c
index cad997965a..86b44f8aa7 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -2,6 +2,7 @@
  * "git push"
  */
 #include "cache.h"
+#include "branch.h"
 #include "config.h"
 #include "refs.h"
 #include "refspec.h"
@@ -151,7 +152,8 @@ static NORETURN void die_push_simple(struct branch *branch,
 	 * upstream to a non-branch, we should probably be showing
 	 * them the big ugly fully qualified ref.
 	 */
-	const char *advice_maybe = "";
+	const char *advice_pushdefault_maybe = "";
+	const char *advice_automergesimple_maybe = "";
 	const char *short_upstream = branch->merge[0]->src;
 
 	skip_prefix(short_upstream, "refs/heads/", &short_upstream);
@@ -161,9 +163,16 @@ static NORETURN void die_push_simple(struct branch *branch,
 	 * push.default.
 	 */
 	if (push_default == PUSH_DEFAULT_UNSPECIFIED)
-		advice_maybe = _("\n"
+		advice_pushdefault_maybe = _("\n"
 				 "To choose either option permanently, "
-				 "see push.default in 'git help config'.");
+				 "see push.default in 'git help config'.\n");
+	if (git_branch_track != BRANCH_TRACK_SIMPLE)
+		advice_automergesimple_maybe = _("\n"
+				 "To avoid automatically configuring "
+				 "upstream branches when their name\n"
+				 "doesn't match the local branch, see option "
+				 "'simple' of branch.autosetupmerge\n"
+				 "in 'git help config'.\n");
 	die(_("The upstream branch of your current branch does not match\n"
 	      "the name of your current branch.  To push to the upstream branch\n"
 	      "on the remote, use\n"
@@ -173,9 +182,10 @@ static NORETURN void die_push_simple(struct branch *branch,
 	      "To push to the branch of the same name on the remote, use\n"
 	      "\n"
 	      "    git push %s HEAD\n"
-	      "%s"),
+	      "%s%s"),
 	    remote->name, short_upstream,
-	    remote->name, advice_maybe);
+	    remote->name, advice_pushdefault_maybe,
+	    advice_automergesimple_maybe);
 }
 
 static const char message_detached_head_die[] =
@@ -185,16 +195,32 @@ static const char message_detached_head_die[] =
 	   "\n"
 	   "    git push %s HEAD:<name-of-remote-branch>\n");
 
-static const char *get_upstream_ref(struct branch *branch, const char *remote_name)
+static const char *get_upstream_ref(int flags, struct branch *branch, const char *remote_name)
 {
-	if (!branch->merge_nr || !branch->merge || !branch->remote_name)
+	if (branch->merge_nr == 0 && (flags & TRANSPORT_PUSH_AUTO_UPSTREAM)) {
+		/* if missing, assume same; set_upstream will be defined later */
+		return branch->refname;
+	}
+
+	if (!branch->merge_nr || !branch->merge || !branch->remote_name) {
+		const char *advice_autosetup_maybe = "";
+		if (!(flags & TRANSPORT_PUSH_AUTO_UPSTREAM)) {
+			advice_autosetup_maybe = _("\n"
+					   "To have this happen automatically for "
+					   "branches without a tracking\n"
+					   "upstream, see 'push.autoSetupRemote' "
+					   "in 'git help config'.\n");
+		}
 		die(_("The current branch %s has no upstream branch.\n"
 		    "To push the current branch and set the remote as upstream, use\n"
 		    "\n"
-		    "    git push --set-upstream %s %s\n"),
+		    "    git push --set-upstream %s %s\n"
+		    "%s"),
 		    branch->name,
 		    remote_name,
-		    branch->name);
+		    branch->name,
+		    advice_autosetup_maybe);
+	}
 	if (branch->merge_nr != 1)
 		die(_("The current branch %s has multiple upstream branches, "
 		    "refusing to push."), branch->name);
@@ -202,7 +228,7 @@ static const char *get_upstream_ref(struct branch *branch, const char *remote_na
 	return branch->merge[0]->src;
 }
 
-static void setup_default_push_refspecs(struct remote *remote)
+static void setup_default_push_refspecs(int *flags, struct remote *remote)
 {
 	struct branch *branch;
 	const char *dst;
@@ -234,7 +260,7 @@ static void setup_default_push_refspecs(struct remote *remote)
 	case PUSH_DEFAULT_SIMPLE:
 		if (!same_remote)
 			break;
-		if (strcmp(branch->refname, get_upstream_ref(branch, remote->name)))
+		if (strcmp(branch->refname, get_upstream_ref(*flags, branch, remote->name)))
 			die_push_simple(branch, remote);
 		break;
 
@@ -244,13 +270,21 @@ static void setup_default_push_refspecs(struct remote *remote)
 			      "your current branch '%s', without telling me what to push\n"
 			      "to update which remote branch."),
 			    remote->name, branch->name);
-		dst = get_upstream_ref(branch, remote->name);
+		dst = get_upstream_ref(*flags, branch, remote->name);
 		break;
 
 	case PUSH_DEFAULT_CURRENT:
 		break;
 	}
 
+	/*
+	 * this is a default push - if auto-upstream is enabled and there is
+	 * no upstream defined, then set it (with options 'simple', 'upstream',
+	 * and 'current').
+	 */
+	if ((*flags & TRANSPORT_PUSH_AUTO_UPSTREAM) && branch->merge_nr == 0)
+		*flags |= TRANSPORT_PUSH_SET_UPSTREAM;
+
 	refspec_appendf(&rs, "%s:%s", branch->refname, dst);
 }
 
@@ -401,7 +435,7 @@ static int do_push(int flags,
 		if (remote->push.nr) {
 			push_refspec = &remote->push;
 		} else if (!(flags & TRANSPORT_PUSH_MIRROR))
-			setup_default_push_refspecs(remote);
+			setup_default_push_refspecs(&flags, remote);
 	}
 	errs = 0;
 	url_nr = push_url_of_remote(remote, &url);
@@ -472,6 +506,10 @@ static int git_push_config(const char *k, const char *v, void *cb)
 		else
 			*flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS;
 		return 0;
+	} else if (!strcmp(k, "push.autosetupremote")) {
+		if (git_config_bool(k, v))
+			*flags |= TRANSPORT_PUSH_AUTO_UPSTREAM;
+		return 0;
 	} else if (!strcmp(k, "push.gpgsign")) {
 		const char *value;
 		if (!git_config_get_value("push.gpgsign", &value)) {
diff --git a/config.c b/config.c
index fa471dbdb8..75cc16f36b 100644
--- a/config.c
+++ b/config.c
@@ -1781,6 +1781,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		} else if (value && !strcmp(value, "inherit")) {
 			git_branch_track = BRANCH_TRACK_INHERIT;
 			return 0;
+		} else if (value && !strcmp(value, "simple")) {
+			git_branch_track = BRANCH_TRACK_SIMPLE;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/remote.c b/remote.c
index 42a4e7106e..930fdc9c2f 100644
--- a/remote.c
+++ b/remote.c
@@ -543,6 +543,8 @@ static const char *remotes_remote_for_branch(struct remote_state *remote_state,
 	}
 	if (explicit)
 		*explicit = 0;
+	if (remote_state->remotes_nr == 1)
+		return remote_state->remotes[0]->name;
 	return "origin";
 }
 
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index e12db59361..9723c2827c 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -886,6 +886,41 @@ test_expect_success 'branch from tag w/--track causes failure' '
 	test_must_fail git branch --track my11 foobar
 '
 
+test_expect_success 'simple tracking works when remote branch name matches' '
+	test_when_finished "rm -rf otherserver" &&
+	git init otherserver &&
+	test_commit -C otherserver my_commit 1 &&
+	git -C otherserver branch feature &&
+	test_config branch.autosetupmerge simple &&
+	test_config remote.otherserver.url otherserver &&
+	test_config remote.otherserver.fetch refs/heads/*:refs/remotes/otherserver/* &&
+	git fetch otherserver &&
+	git branch feature otherserver/feature &&
+	test_cmp_config otherserver branch.feature.remote &&
+	test_cmp_config refs/heads/feature branch.feature.merge
+'
+
+test_expect_success 'simple tracking skips when remote branch name does not match' '
+	test_config branch.autosetupmerge simple &&
+	test_config remote.local.url . &&
+	test_config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+	git fetch local &&
+	git branch my-other local/main &&
+	test_cmp_config "" --default "" branch.my-other.remote &&
+	test_cmp_config "" --default "" branch.my-other.merge
+'
+
+test_expect_success 'simple tracking skips when remote ref is not a branch' '
+	test_config branch.autosetupmerge simple &&
+	test_config remote.localtags.url . &&
+	test_config remote.localtags.fetch refs/tags/*:refs/remotes/localtags/* &&
+	git tag mytag12 main &&
+	git fetch localtags &&
+	git branch mytag12 localtags/mytag12 &&
+	test_cmp_config "" --default "" branch.mytag12.remote &&
+	test_cmp_config "" --default "" branch.mytag12.merge
+'
+
 test_expect_success '--set-upstream-to fails on multiple branches' '
 	echo "fatal: too many arguments to set new upstream" >expect &&
 	test_must_fail git branch --set-upstream-to main a b c 2>err &&
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index f53f58895a..20d063fb9a 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -15,6 +15,10 @@ generate_references () {
 	done
 }
 
+test_expect_success 'dies when no remote found' '
+	test_must_fail git ls-remote
+'
+
 test_expect_success setup '
 	>file &&
 	git add file &&
@@ -30,7 +34,8 @@ test_expect_success setup '
 	git show-ref -d	>refs &&
 	sed -e "s/ /	/" refs >>expected.all &&
 
-	git remote add self "$(pwd)/.git"
+	git remote add self "$(pwd)/.git" &&
+	git remote add self2 "."
 '
 
 test_expect_success 'ls-remote --tags .git' '
@@ -83,11 +88,17 @@ test_expect_success 'ls-remote --sort="-refname" --tags self' '
 	test_cmp expect actual
 '
 
-test_expect_success 'dies when no remote specified and no default remotes found' '
+test_expect_success 'dies when no remote specified, multiple remotes found, and no default specified' '
 	test_must_fail git ls-remote
 '
 
-test_expect_success 'use "origin" when no remote specified' '
+test_expect_success 'succeeds when no remote specified but only one found' '
+	test_when_finished git remote add self2 "." &&
+	git remote remove self2 &&
+	git ls-remote
+'
+
+test_expect_success 'use "origin" when no remote specified and multiple found' '
 	URL="$(pwd)/.git" &&
 	echo "From $URL" >exp_err &&
 
diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh
index f280e00eb7..284e20fefd 100755
--- a/t/t5528-push-default.sh
+++ b/t/t5528-push-default.sh
@@ -94,13 +94,88 @@ test_expect_success '"upstream" does not push when remotes do not match' '
 	test_must_fail git push parent2
 '
 
-test_expect_success 'push from/to new branch with upstream, matching and simple' '
+test_expect_success '"current" does not push when multiple remotes and none origin' '
+	git checkout main &&
+	test_config push.default current &&
+	test_commit current-multi &&
+	test_must_fail git push
+'
+
+test_expect_success '"current" pushes when remote explicitly specified' '
+	git checkout main &&
+	test_config push.default current &&
+	test_commit current-specified &&
+	git push parent1
+'
+
+test_expect_success '"current" pushes to origin when no remote specified among multiple' '
+	git checkout main &&
+	test_config remote.origin.url repo1 &&
+	test_config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" &&
+	test_commit current-origin &&
+	test_push_success current main
+'
+
+test_expect_success '"current" pushes to single remote even when not specified' '
+	git checkout main &&
+	test_when_finished git remote add parent1 repo1 &&
+	git remote remove parent1 &&
+	test_commit current-implied &&
+	test_push_success current main repo2
+'
+
+test_expect_success 'push from/to new branch with non-defaulted remote fails with upstream, matching, current and simple ' '
 	git checkout -b new-branch &&
 	test_push_failure simple &&
 	test_push_failure matching &&
+	test_push_failure upstream &&
+	test_push_failure current
+'
+
+test_expect_success 'push from/to new branch fails with upstream and simple ' '
+	git checkout -b new-branch-1 &&
+	test_config branch.new-branch-1.remote parent1 &&
+	test_push_failure simple &&
 	test_push_failure upstream
 '
 
+# The behavior here is surprising but not entirely wrong:
+#  - the current branch is used to determine the target remote
+#  - the "matching" push default pushes matching branches, *ignoring* the
+#       current new branch as it does not have upstream tracking
+#  - the default push succeeds
+#
+# A previous test expected this to fail, but for the wrong reasons:
+# it expected a fail becaause the branch is new and cannot be pushed, but
+# in fact it was failing because of an ambiguous remote
+#
+test_expect_failure 'push from/to new branch fails with matching ' '
+	git checkout -b new-branch-2 &&
+	test_config branch.new-branch-2.remote parent1 &&
+	test_push_failure matching
+'
+
+test_expect_success 'push from/to branch with tracking fails with nothing ' '
+	git checkout -b tracked-branch &&
+	test_config branch.tracked-branch.remote parent1 &&
+	test_config branch.tracked-branch.merge refs/heads/tracked-branch &&
+	test_push_failure nothing
+'
+
+test_expect_success 'push from/to new branch succeeds with upstream if push.autoSetupRemote' '
+	git checkout -b new-branch-a &&
+	test_config push.autoSetupRemote true &&
+	test_config branch.new-branch-a.remote parent1 &&
+	test_push_success upstream new-branch-a
+'
+
+test_expect_success 'push from/to new branch succeeds with simple if push.autoSetupRemote' '
+	git checkout -b new-branch-c &&
+	test_config push.autoSetupRemote true &&
+	test_config branch.new-branch-c.remote parent1 &&
+	test_push_success simple new-branch-c
+'
+
 test_expect_success '"matching" fails if none match' '
 	git init --bare empty &&
 	test_must_fail git push empty : 2>actual &&
diff --git a/transport.h b/transport.h
index 12bc08fc33..b5bf7b3e70 100644
--- a/transport.h
+++ b/transport.h
@@ -145,6 +145,7 @@ struct transport {
 #define TRANSPORT_PUSH_OPTIONS			(1<<14)
 #define TRANSPORT_RECURSE_SUBMODULES_ONLY	(1<<15)
 #define TRANSPORT_PUSH_FORCE_IF_INCLUDES	(1<<16)
+#define TRANSPORT_PUSH_AUTO_UPSTREAM		(1<<17)
 
 int transport_summary_width(const struct ref *refs);