зеркало из https://github.com/microsoft/git.git
Merge branch 'tk/simple-autosetupmerge'
"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
This commit is contained in:
Коммит
f49c478f62
|
@ -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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
27
branch.c
27
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;
|
||||
}
|
||||
|
||||
|
|
1
branch.h
1
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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
3
config.c
3
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;
|
||||
|
|
2
remote.c
2
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";
|
||||
}
|
||||
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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 &&
|
||||
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче