Skip to content

Commit f49c478

Browse files
committed
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
2 parents 6afdb07 + 05d5775 commit f49c478

File tree

12 files changed

+237
-28
lines changed

12 files changed

+237
-28
lines changed

Documentation/config/branch.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ branch.autoSetupMerge::
99
automatic setup is done when the starting point is either a
1010
local branch or remote-tracking branch; `inherit` -- if the starting point
1111
has a tracking configuration, it is copied to the new
12-
branch. This option defaults to true.
12+
branch; `simple` -- automatic setup is done only when the starting point
13+
is a remote-tracking branch and the new branch has the same name as the
14+
remote branch. This option defaults to true.
1315

1416
branch.autoSetupRebase::
1517
When a new branch is created with 'git branch', 'git switch' or 'git checkout'
@@ -38,8 +40,9 @@ branch.<name>.remote::
3840
may be overridden with `remote.pushDefault` (for all branches).
3941
The remote to push to, for the current branch, may be further
4042
overridden by `branch.<name>.pushRemote`. If no remote is
41-
configured, or if you are not on any branch, it defaults to
42-
`origin` for fetching and `remote.pushDefault` for pushing.
43+
configured, or if you are not on any branch and there is more than
44+
one remote defined in the repository, it defaults to `origin` for
45+
fetching and `remote.pushDefault` for pushing.
4346
Additionally, `.` (a period) is the current local repository
4447
(a dot-repository), see `branch.<name>.merge`'s final note below.
4548

Documentation/config/push.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
push.autoSetupRemote::
2+
If set to "true" assume `--set-upstream` on default push when no
3+
upstream tracking exists for the current branch; this option
4+
takes effect with push.default options 'simple', 'upstream',
5+
and 'current'. It is useful if by default you want new branches
6+
to be pushed to the default remote (like the behavior of
7+
'push.default=current') and you also want the upstream tracking
8+
to be set. Workflows most likely to benefit from this option are
9+
'simple' central workflows where all branches are expected to
10+
have the same name on the remote.
11+
112
push.default::
213
Defines the action `git push` should take if no refspec is
314
given (whether from the command-line, config, or elsewhere).

Documentation/git-branch.txt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,17 @@ The exact upstream branch is chosen depending on the optional argument:
221221
itself as the upstream; `--track=inherit` means to copy the upstream
222222
configuration of the start-point branch.
223223
+
224-
`--track=direct` is the default when the start point is a remote-tracking branch.
225-
Set the branch.autoSetupMerge configuration variable to `false` if you
226-
want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
227-
were given. Set it to `always` if you want this behavior when the
228-
start-point is either a local or remote-tracking branch. Set it to
229-
`inherit` if you want to copy the tracking configuration from the
230-
branch point.
224+
The branch.autoSetupMerge configuration variable specifies how `git switch`,
225+
`git checkout` and `git branch` should behave when neither `--track` nor
226+
`--no-track` are specified:
227+
+
228+
The default option, `true`, behaves as though `--track=direct`
229+
were given whenever the start-point is a remote-tracking branch.
230+
`false` behaves as if `--no-track` were given. `always` behaves as though
231+
`--track=direct` were given. `inherit` behaves as though `--track=inherit`
232+
were given. `simple` behaves as though `--track=direct` were given only when
233+
the start-point is a remote-tracking branch and the new branch has the same
234+
name as the remote branch.
231235
+
232236
See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on
233237
how the `branch.<name>.remote` and `branch.<name>.merge` options are used.

branch.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ static int find_tracked_branch(struct remote *remote, void *priv)
4444
string_list_clear(tracking->srcs, 0);
4545
break;
4646
}
47+
/* remote_find_tracking() searches by src if present */
4748
tracking->spec.src = NULL;
4849
}
49-
5050
return 0;
5151
}
5252

@@ -264,15 +264,23 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
264264

265265
if (!tracking.matches)
266266
switch (track) {
267+
/* If ref is not remote, still use local */
267268
case BRANCH_TRACK_ALWAYS:
268269
case BRANCH_TRACK_EXPLICIT:
269270
case BRANCH_TRACK_OVERRIDE:
271+
/* Remote matches not evaluated */
270272
case BRANCH_TRACK_INHERIT:
271273
break;
274+
/* Otherwise, if no remote don't track */
272275
default:
273276
goto cleanup;
274277
}
275278

279+
/*
280+
* This check does not apply to BRANCH_TRACK_INHERIT;
281+
* that supports multiple entries in tracking_srcs but
282+
* leaves tracking.matches at 0.
283+
*/
276284
if (tracking.matches > 1) {
277285
int status = die_message(_("not tracking: ambiguous information for ref '%s'"),
278286
orig_ref);
@@ -307,6 +315,21 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
307315
exit(status);
308316
}
309317

318+
if (track == BRANCH_TRACK_SIMPLE) {
319+
/*
320+
* Only track if remote branch name matches.
321+
* Reaching into items[0].string is safe because
322+
* we know there is at least one and not more than
323+
* one entry (because only BRANCH_TRACK_INHERIT can
324+
* produce more than one entry).
325+
*/
326+
const char *tracked_branch;
327+
if (!skip_prefix(tracking.srcs->items[0].string,
328+
"refs/heads/", &tracked_branch) ||
329+
strcmp(tracked_branch, new_ref))
330+
return;
331+
}
332+
310333
if (tracking.srcs->nr < 1)
311334
string_list_append(tracking.srcs, orig_ref);
312335
if (install_branch_config_multiple_remotes(config_flags, new_ref,
@@ -603,6 +626,8 @@ static int submodule_create_branch(struct repository *r,
603626
/* Default for "git checkout". Do not pass --track. */
604627
case BRANCH_TRACK_REMOTE:
605628
/* Default for "git branch". Do not pass --track. */
629+
case BRANCH_TRACK_SIMPLE:
630+
/* Config-driven only. Do not pass --track. */
606631
break;
607632
}
608633

branch.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ enum branch_track {
1212
BRANCH_TRACK_EXPLICIT,
1313
BRANCH_TRACK_OVERRIDE,
1414
BRANCH_TRACK_INHERIT,
15+
BRANCH_TRACK_SIMPLE,
1516
};
1617

1718
extern enum branch_track git_branch_track;

builtin/push.c

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* "git push"
33
*/
44
#include "cache.h"
5+
#include "branch.h"
56
#include "config.h"
67
#include "refs.h"
78
#include "refspec.h"
@@ -151,7 +152,8 @@ static NORETURN void die_push_simple(struct branch *branch,
151152
* upstream to a non-branch, we should probably be showing
152153
* them the big ugly fully qualified ref.
153154
*/
154-
const char *advice_maybe = "";
155+
const char *advice_pushdefault_maybe = "";
156+
const char *advice_automergesimple_maybe = "";
155157
const char *short_upstream = branch->merge[0]->src;
156158

157159
skip_prefix(short_upstream, "refs/heads/", &short_upstream);
@@ -161,9 +163,16 @@ static NORETURN void die_push_simple(struct branch *branch,
161163
* push.default.
162164
*/
163165
if (push_default == PUSH_DEFAULT_UNSPECIFIED)
164-
advice_maybe = _("\n"
166+
advice_pushdefault_maybe = _("\n"
165167
"To choose either option permanently, "
166-
"see push.default in 'git help config'.");
168+
"see push.default in 'git help config'.\n");
169+
if (git_branch_track != BRANCH_TRACK_SIMPLE)
170+
advice_automergesimple_maybe = _("\n"
171+
"To avoid automatically configuring "
172+
"upstream branches when their name\n"
173+
"doesn't match the local branch, see option "
174+
"'simple' of branch.autosetupmerge\n"
175+
"in 'git help config'.\n");
167176
die(_("The upstream branch of your current branch does not match\n"
168177
"the name of your current branch. To push to the upstream branch\n"
169178
"on the remote, use\n"
@@ -173,9 +182,10 @@ static NORETURN void die_push_simple(struct branch *branch,
173182
"To push to the branch of the same name on the remote, use\n"
174183
"\n"
175184
" git push %s HEAD\n"
176-
"%s"),
185+
"%s%s"),
177186
remote->name, short_upstream,
178-
remote->name, advice_maybe);
187+
remote->name, advice_pushdefault_maybe,
188+
advice_automergesimple_maybe);
179189
}
180190

181191
static const char message_detached_head_die[] =
@@ -185,24 +195,40 @@ static const char message_detached_head_die[] =
185195
"\n"
186196
" git push %s HEAD:<name-of-remote-branch>\n");
187197

188-
static const char *get_upstream_ref(struct branch *branch, const char *remote_name)
198+
static const char *get_upstream_ref(int flags, struct branch *branch, const char *remote_name)
189199
{
190-
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
200+
if (branch->merge_nr == 0 && (flags & TRANSPORT_PUSH_AUTO_UPSTREAM)) {
201+
/* if missing, assume same; set_upstream will be defined later */
202+
return branch->refname;
203+
}
204+
205+
if (!branch->merge_nr || !branch->merge || !branch->remote_name) {
206+
const char *advice_autosetup_maybe = "";
207+
if (!(flags & TRANSPORT_PUSH_AUTO_UPSTREAM)) {
208+
advice_autosetup_maybe = _("\n"
209+
"To have this happen automatically for "
210+
"branches without a tracking\n"
211+
"upstream, see 'push.autoSetupRemote' "
212+
"in 'git help config'.\n");
213+
}
191214
die(_("The current branch %s has no upstream branch.\n"
192215
"To push the current branch and set the remote as upstream, use\n"
193216
"\n"
194-
" git push --set-upstream %s %s\n"),
217+
" git push --set-upstream %s %s\n"
218+
"%s"),
195219
branch->name,
196220
remote_name,
197-
branch->name);
221+
branch->name,
222+
advice_autosetup_maybe);
223+
}
198224
if (branch->merge_nr != 1)
199225
die(_("The current branch %s has multiple upstream branches, "
200226
"refusing to push."), branch->name);
201227

202228
return branch->merge[0]->src;
203229
}
204230

205-
static void setup_default_push_refspecs(struct remote *remote)
231+
static void setup_default_push_refspecs(int *flags, struct remote *remote)
206232
{
207233
struct branch *branch;
208234
const char *dst;
@@ -234,7 +260,7 @@ static void setup_default_push_refspecs(struct remote *remote)
234260
case PUSH_DEFAULT_SIMPLE:
235261
if (!same_remote)
236262
break;
237-
if (strcmp(branch->refname, get_upstream_ref(branch, remote->name)))
263+
if (strcmp(branch->refname, get_upstream_ref(*flags, branch, remote->name)))
238264
die_push_simple(branch, remote);
239265
break;
240266

@@ -244,13 +270,21 @@ static void setup_default_push_refspecs(struct remote *remote)
244270
"your current branch '%s', without telling me what to push\n"
245271
"to update which remote branch."),
246272
remote->name, branch->name);
247-
dst = get_upstream_ref(branch, remote->name);
273+
dst = get_upstream_ref(*flags, branch, remote->name);
248274
break;
249275

250276
case PUSH_DEFAULT_CURRENT:
251277
break;
252278
}
253279

280+
/*
281+
* this is a default push - if auto-upstream is enabled and there is
282+
* no upstream defined, then set it (with options 'simple', 'upstream',
283+
* and 'current').
284+
*/
285+
if ((*flags & TRANSPORT_PUSH_AUTO_UPSTREAM) && branch->merge_nr == 0)
286+
*flags |= TRANSPORT_PUSH_SET_UPSTREAM;
287+
254288
refspec_appendf(&rs, "%s:%s", branch->refname, dst);
255289
}
256290

@@ -401,7 +435,7 @@ static int do_push(int flags,
401435
if (remote->push.nr) {
402436
push_refspec = &remote->push;
403437
} else if (!(flags & TRANSPORT_PUSH_MIRROR))
404-
setup_default_push_refspecs(remote);
438+
setup_default_push_refspecs(&flags, remote);
405439
}
406440
errs = 0;
407441
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)
472506
else
473507
*flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS;
474508
return 0;
509+
} else if (!strcmp(k, "push.autosetupremote")) {
510+
if (git_config_bool(k, v))
511+
*flags |= TRANSPORT_PUSH_AUTO_UPSTREAM;
512+
return 0;
475513
} else if (!strcmp(k, "push.gpgsign")) {
476514
const char *value;
477515
if (!git_config_get_value("push.gpgsign", &value)) {

config.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,6 +1781,9 @@ static int git_default_branch_config(const char *var, const char *value)
17811781
} else if (value && !strcmp(value, "inherit")) {
17821782
git_branch_track = BRANCH_TRACK_INHERIT;
17831783
return 0;
1784+
} else if (value && !strcmp(value, "simple")) {
1785+
git_branch_track = BRANCH_TRACK_SIMPLE;
1786+
return 0;
17841787
}
17851788
git_branch_track = git_config_bool(var, value);
17861789
return 0;

remote.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ static const char *remotes_remote_for_branch(struct remote_state *remote_state,
543543
}
544544
if (explicit)
545545
*explicit = 0;
546+
if (remote_state->remotes_nr == 1)
547+
return remote_state->remotes[0]->name;
546548
return "origin";
547549
}
548550

t/t3200-branch.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,41 @@ test_expect_success 'branch from tag w/--track causes failure' '
886886
test_must_fail git branch --track my11 foobar
887887
'
888888

889+
test_expect_success 'simple tracking works when remote branch name matches' '
890+
test_when_finished "rm -rf otherserver" &&
891+
git init otherserver &&
892+
test_commit -C otherserver my_commit 1 &&
893+
git -C otherserver branch feature &&
894+
test_config branch.autosetupmerge simple &&
895+
test_config remote.otherserver.url otherserver &&
896+
test_config remote.otherserver.fetch refs/heads/*:refs/remotes/otherserver/* &&
897+
git fetch otherserver &&
898+
git branch feature otherserver/feature &&
899+
test_cmp_config otherserver branch.feature.remote &&
900+
test_cmp_config refs/heads/feature branch.feature.merge
901+
'
902+
903+
test_expect_success 'simple tracking skips when remote branch name does not match' '
904+
test_config branch.autosetupmerge simple &&
905+
test_config remote.local.url . &&
906+
test_config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
907+
git fetch local &&
908+
git branch my-other local/main &&
909+
test_cmp_config "" --default "" branch.my-other.remote &&
910+
test_cmp_config "" --default "" branch.my-other.merge
911+
'
912+
913+
test_expect_success 'simple tracking skips when remote ref is not a branch' '
914+
test_config branch.autosetupmerge simple &&
915+
test_config remote.localtags.url . &&
916+
test_config remote.localtags.fetch refs/tags/*:refs/remotes/localtags/* &&
917+
git tag mytag12 main &&
918+
git fetch localtags &&
919+
git branch mytag12 localtags/mytag12 &&
920+
test_cmp_config "" --default "" branch.mytag12.remote &&
921+
test_cmp_config "" --default "" branch.mytag12.merge
922+
'
923+
889924
test_expect_success '--set-upstream-to fails on multiple branches' '
890925
echo "fatal: too many arguments to set new upstream" >expect &&
891926
test_must_fail git branch --set-upstream-to main a b c 2>err &&

t/t5512-ls-remote.sh

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ generate_references () {
1515
done
1616
}
1717

18+
test_expect_success 'dies when no remote found' '
19+
test_must_fail git ls-remote
20+
'
21+
1822
test_expect_success setup '
1923
>file &&
2024
git add file &&
@@ -30,7 +34,8 @@ test_expect_success setup '
3034
git show-ref -d >refs &&
3135
sed -e "s/ / /" refs >>expected.all &&
3236
33-
git remote add self "$(pwd)/.git"
37+
git remote add self "$(pwd)/.git" &&
38+
git remote add self2 "."
3439
'
3540

3641
test_expect_success 'ls-remote --tags .git' '
@@ -83,11 +88,17 @@ test_expect_success 'ls-remote --sort="-refname" --tags self' '
8388
test_cmp expect actual
8489
'
8590

86-
test_expect_success 'dies when no remote specified and no default remotes found' '
91+
test_expect_success 'dies when no remote specified, multiple remotes found, and no default specified' '
8792
test_must_fail git ls-remote
8893
'
8994

90-
test_expect_success 'use "origin" when no remote specified' '
95+
test_expect_success 'succeeds when no remote specified but only one found' '
96+
test_when_finished git remote add self2 "." &&
97+
git remote remove self2 &&
98+
git ls-remote
99+
'
100+
101+
test_expect_success 'use "origin" when no remote specified and multiple found' '
91102
URL="$(pwd)/.git" &&
92103
echo "From $URL" >exp_err &&
93104

0 commit comments

Comments
 (0)