Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.kernel.org/pub/scm/git/git.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2022-05-27 00:51:30 +0300
committerJunio C Hamano <gitster@pobox.com>2022-05-27 00:51:30 +0300
commitf49c478f628910de206c5e882a3cd3abec76d4e6 (patch)
treede71e2834c8cd48e6c2aaf447453d408d67fced0
parent6afdb07b7b918ed9282ea6e955f53369df862be8 (diff)
parent05d57750c66e4b58233787954c06b8f714bbee75 (diff)
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
-rw-r--r--Documentation/config/branch.txt9
-rw-r--r--Documentation/config/push.txt11
-rw-r--r--Documentation/git-branch.txt18
-rw-r--r--branch.c27
-rw-r--r--branch.h1
-rw-r--r--builtin/push.c64
-rw-r--r--config.c3
-rw-r--r--remote.c2
-rwxr-xr-xt/t3200-branch.sh35
-rwxr-xr-xt/t5512-ls-remote.sh17
-rwxr-xr-xt/t5528-push-default.sh77
-rw-r--r--transport.h1
12 files changed, 237 insertions, 28 deletions
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);