Skip to content

Commit

Permalink
Merge branch 'tk/simple-autosetupmerge' into jch
Browse files Browse the repository at this point in the history
"git -c branch.autosetupmerge=simple branch $A $B" will set the $B
as $A's upstream only when $A and $B shares the same name, and "git
-c push.default=simple" on branch $A would push to update the
branch $A at the remote $B came from.  Also more places use the
sole remote, if exists, before defaulting to 'origin'.

* tk/simple-autosetupmerge:
  push: new config option "push.autoSetupRemote" supports "simple" push
  push: default to single remote even when not named origin
  branch: new autosetupmerge option 'simple' for matching branches
  • Loading branch information
gitster committed May 20, 2022
2 parents b45bb86 + 05d5775 commit d19c015
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 28 deletions.
9 changes: 6 additions & 3 deletions Documentation/config/branch.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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.

Expand Down
11 changes: 11 additions & 0 deletions Documentation/config/push.txt
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
18 changes: 11 additions & 7 deletions Documentation/git-branch.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
27 changes: 26 additions & 1 deletion branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions branch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
64 changes: 51 additions & 13 deletions builtin/push.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* "git push"
*/
#include "cache.h"
#include "branch.h"
#include "config.h"
#include "refs.h"
#include "refspec.h"
Expand Down Expand Up @@ -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);
Expand All @@ -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"
Expand All @@ -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[] =
Expand All @@ -185,24 +195,40 @@ 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);

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;
Expand Down Expand Up @@ -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;

Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)) {
Expand Down
3 changes: 3 additions & 0 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}

Expand Down
35 changes: 35 additions & 0 deletions t/t3200-branch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
17 changes: 14 additions & 3 deletions t/t5512-ls-remote.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand All @@ -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' '
Expand Down Expand Up @@ -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 &&
Expand Down

0 comments on commit d19c015

Please sign in to comment.