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/builtin/push.c b/builtin/push.c index 447f91f5b4..86b44f8aa7 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -195,16 +195,32 @@ static const char message_detached_head_die[] = "\n" " git push %s HEAD:\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); @@ -212,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; @@ -244,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; @@ -254,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); } @@ -411,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); @@ -482,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/t/t5528-push-default.sh b/t/t5528-push-default.sh index 0d6c9869ed..284e20fefd 100755 --- a/t/t5528-push-default.sh +++ b/t/t5528-push-default.sh @@ -162,6 +162,20 @@ test_expect_success 'push from/to branch with tracking fails with nothing ' ' 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);