Merge branch 'jc/triangle-push-fixup'

Earlier remote.pushdefault (and per-branch branch.*.pushremote)
were introduced as an additional mechanism to choose what
repository to push into when "git push" did not say it from the
command line, to help people who push to a repository that is
different from where they fetch from.  This attempts to finish that
topic by teaching the default mechanism to choose branch in the
remote repository to be updated by such a push.

The 'current', 'matching' and 'nothing' modes (specified by the
push.default configuration variable) extend to such a "triangular"
workflow naturally, but 'upstream' and 'simple' have to be updated.

. 'upstream' is about pushing back to update the branch in the
  remote repository that the current branch fetches from and
  integrates with, it errors out in a triangular workflow.

. 'simple' is meant to help new people by avoiding mistakes, and
  will be the safe default in Git 2.0.

  In a non-triangular workflow, it will continue to act as a cross
  between 'upstream' and 'current' in that it pushes to the current
  branch's @{upstream} only when it is set to the same name as the
  current branch (e.g. your 'master' forks from the 'master' from
  the central repository).

  In a triangular workflow, this series tentatively defines it as
  the same as 'current', but we may have to tighten it to avoid
  surprises in some way.

* jc/triangle-push-fixup:
  t/t5528-push-default: test pushdefault workflows
  t/t5528-push-default: generalize test_push_*
  push: change `simple` to accommodate triangular workflows
  config doc: rewrite push.default section
  t/t5528-push-default: remove redundant test_config lines
This commit is contained in:
Junio C Hamano 2013-07-11 13:03:21 -07:00
Родитель fb58544ec7 6e1696b7c4
Коммит 3b8d2765c7
3 изменённых файлов: 142 добавлений и 46 удалений

Просмотреть файл

@ -1844,39 +1844,59 @@ pull.twohead::
The default merge strategy to use when pulling a single branch. The default merge strategy to use when pulling a single branch.
push.default:: push.default::
Defines the action `git push` should take if no refspec is given Defines the action `git push` should take if no refspec is
on the command line, no refspec is configured in the remote, and explicitly given. Different values are well-suited for
no refspec is implied by any of the options given on the command specific workflows; for instance, in a purely central workflow
line. Possible values are: (i.e. the fetch source is equal to the push destination),
`upstream` is probably what you want. Possible values are:
+ +
-- --
* `nothing` - do not push anything.
* `matching` - push all branches having the same name in both ends. * `nothing` - do not push anything (error out) unless a refspec is
This is for those who prepare all the branches into a publishable explicitly given. This is primarily meant for people who want to
shape and then push them out with a single command. It is not avoid mistakes by always being explicit.
appropriate for pushing into a repository shared by multiple users,
since locally stalled branches will attempt a non-fast forward push * `current` - push the current branch to update a branch with the same
if other users updated the branch. name on the receiving end. Works in both central and non-central
+ workflows.
This is currently the default, but Git 2.0 will change the default
to `simple`. * `upstream` - push the current branch back to the branch whose
* `upstream` - push the current branch to its upstream branch changes are usually integrated into the current branch (which is
(`tracking` is a deprecated synonym for this). called `@{upstream}`). This mode only makes sense if you are
With this, `git push` will update the same remote ref as the one which pushing to the same repository you would normally pull from
is merged by `git pull`, making `push` and `pull` symmetrical. (i.e. central workflow).
See "branch.<name>.merge" for how to configure the upstream branch.
* `simple` - like `upstream`, but refuses to push if the upstream * `simple` - in centralized workflow, work like `upstream` with an
branch's name is different from the local one. This is the safest added safety to refuse to push if the upstream branch's name is
option and is well-suited for beginners. It will become the default different from the local one.
in Git 2.0.
* `current` - push the current branch to a branch of the same name.
--
+ +
The `simple`, `current` and `upstream` modes are for those who want to When pushing to a remote that is different from the remote you normally
push out a single branch after finishing work, even when the other pull from, work as `current`. This is the safest option and is suited
branches are not yet ready to be pushed out. If you are working with for beginners.
other people to push into the same shared repository, you would want +
to use one of these. This mode will become the default in Git 2.0.
* `matching` - push all branches having the same name on both ends.
This makes the repository you are pushing to remember the set of
branches that will be pushed out (e.g. if you always push 'maint'
and 'master' there and no other branches, the repository you push
to will have these two branches, and your local 'maint' and
'master' will be pushed there).
+
To use this mode effectively, you have to make sure _all_ the
branches you would push out are ready to be pushed out before
running 'git push', as the whole point of this mode is to allow you
to push all of the branches in one go. If you usually finish work
on only one branch and push out the result, while other branches are
unfinished, this mode is not for you. Also this mode is not
suitable for pushing into a shared central repository, as other
people may add new branches there, or update the tip of existing
branches outside your control.
+
This is currently the default, but Git 2.0 will change the default
to `simple`.
--
rebase.stat:: rebase.stat::
Whether to show a diffstat of what changed upstream since the last Whether to show a diffstat of what changed upstream since the last

Просмотреть файл

@ -120,10 +120,11 @@ static const char message_detached_head_die[] =
"\n" "\n"
" git push %s HEAD:<name-of-remote-branch>\n"); " git push %s HEAD:<name-of-remote-branch>\n");
static void setup_push_upstream(struct remote *remote, int simple) static void setup_push_upstream(struct remote *remote, struct branch *branch,
int triangular)
{ {
struct strbuf refspec = STRBUF_INIT; struct strbuf refspec = STRBUF_INIT;
struct branch *branch = branch_get(NULL);
if (!branch) if (!branch)
die(_(message_detached_head_die), remote->name); die(_(message_detached_head_die), remote->name);
if (!branch->merge_nr || !branch->merge || !branch->remote_name) if (!branch->merge_nr || !branch->merge || !branch->remote_name)
@ -137,18 +138,29 @@ static void setup_push_upstream(struct remote *remote, int simple)
if (branch->merge_nr != 1) if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, " die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name); "refusing to push."), branch->name);
if (strcmp(branch->remote_name, remote->name)) if (triangular)
die(_("You are pushing to remote '%s', which is not the upstream of\n" die(_("You are pushing to remote '%s', which is not the upstream of\n"
"your current branch '%s', without telling me what to push\n" "your current branch '%s', without telling me what to push\n"
"to update which remote branch."), "to update which remote branch."),
remote->name, branch->name); remote->name, branch->name);
if (simple && strcmp(branch->refname, branch->merge[0]->src))
die_push_simple(branch, remote); if (push_default == PUSH_DEFAULT_SIMPLE) {
/* Additional safety */
if (strcmp(branch->refname, branch->merge[0]->src))
die_push_simple(branch, remote);
}
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf); add_refspec(refspec.buf);
} }
static void setup_push_current(struct remote *remote, struct branch *branch)
{
if (!branch)
die(_(message_detached_head_die), remote->name);
add_refspec(branch->name);
}
static char warn_unspecified_push_default_msg[] = static char warn_unspecified_push_default_msg[] =
N_("push.default is unset; its implicit value is changing in\n" N_("push.default is unset; its implicit value is changing in\n"
"Git 2.0 from 'matching' to 'simple'. To squelch this message\n" "Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
@ -173,9 +185,16 @@ static void warn_unspecified_push_default_configuration(void)
warning("%s\n", _(warn_unspecified_push_default_msg)); warning("%s\n", _(warn_unspecified_push_default_msg));
} }
static int is_workflow_triangular(struct remote *remote)
{
struct remote *fetch_remote = remote_get(NULL);
return (fetch_remote && fetch_remote != remote);
}
static void setup_default_push_refspecs(struct remote *remote) static void setup_default_push_refspecs(struct remote *remote)
{ {
struct branch *branch; struct branch *branch = branch_get(NULL);
int triangular = is_workflow_triangular(remote);
switch (push_default) { switch (push_default) {
default: default:
@ -188,18 +207,18 @@ static void setup_default_push_refspecs(struct remote *remote)
break; break;
case PUSH_DEFAULT_SIMPLE: case PUSH_DEFAULT_SIMPLE:
setup_push_upstream(remote, 1); if (triangular)
setup_push_current(remote, branch);
else
setup_push_upstream(remote, branch, triangular);
break; break;
case PUSH_DEFAULT_UPSTREAM: case PUSH_DEFAULT_UPSTREAM:
setup_push_upstream(remote, 0); setup_push_upstream(remote, branch, triangular);
break; break;
case PUSH_DEFAULT_CURRENT: case PUSH_DEFAULT_CURRENT:
branch = branch_get(NULL); setup_push_current(remote, branch);
if (!branch)
die(_(message_detached_head_die), remote->name);
add_refspec(branch->name);
break; break;
case PUSH_DEFAULT_NOTHING: case PUSH_DEFAULT_NOTHING:

Просмотреть файл

@ -15,17 +15,19 @@ test_expect_success 'setup bare remotes' '
# $1 = local revision # $1 = local revision
# $2 = remote revision (tested to be equal to the local one) # $2 = remote revision (tested to be equal to the local one)
# $3 = [optional] repo to check for actual output (repo1 by default)
check_pushed_commit () { check_pushed_commit () {
git log -1 --format='%h %s' "$1" >expect && git log -1 --format='%h %s' "$1" >expect &&
git --git-dir=repo1 log -1 --format='%h %s' "$2" >actual && git --git-dir="${3:-repo1}" log -1 --format='%h %s' "$2" >actual &&
test_cmp expect actual test_cmp expect actual
} }
# $1 = push.default value # $1 = push.default value
# $2 = expected target branch for the push # $2 = expected target branch for the push
# $3 = [optional] repo to check for actual output (repo1 by default)
test_push_success () { test_push_success () {
git -c push.default="$1" push && git -c push.default="$1" push &&
check_pushed_commit HEAD "$2" check_pushed_commit HEAD "$2" "$3"
} }
# $1 = push.default value # $1 = push.default value
@ -37,6 +39,26 @@ test_push_failure () {
test_cmp expect actual test_cmp expect actual
} }
# $1 = success or failure
# $2 = push.default value
# $3 = branch to check for actual output (master or foo)
# $4 = [optional] switch to triangular workflow
test_pushdefault_workflow () {
workflow=central
pushdefault=parent1
if test -n "${4-}"; then
workflow=triangular
pushdefault=parent2
fi
test_expect_success "push.default = $2 $1 in $workflow workflows" "
test_config branch.master.remote parent1 &&
test_config branch.master.merge refs/heads/foo &&
test_config remote.pushdefault $pushdefault &&
test_commit commit-for-$2${4+-triangular} &&
test_push_$1 $2 $3 ${4+repo2}
"
}
test_expect_success '"upstream" pushes to configured upstream' ' test_expect_success '"upstream" pushes to configured upstream' '
git checkout master && git checkout master &&
test_config branch.master.remote parent1 && test_config branch.master.remote parent1 &&
@ -48,7 +70,6 @@ test_expect_success '"upstream" pushes to configured upstream' '
test_expect_success '"upstream" does not push on unconfigured remote' ' test_expect_success '"upstream" does not push on unconfigured remote' '
git checkout master && git checkout master &&
test_unconfig branch.master.remote && test_unconfig branch.master.remote &&
test_config push.default upstream &&
test_commit three && test_commit three &&
test_push_failure upstream test_push_failure upstream
' '
@ -57,7 +78,6 @@ test_expect_success '"upstream" does not push on unconfigured branch' '
git checkout master && git checkout master &&
test_config branch.master.remote parent1 && test_config branch.master.remote parent1 &&
test_unconfig branch.master.merge && test_unconfig branch.master.merge &&
test_config push.default upstream
test_commit four && test_commit four &&
test_push_failure upstream test_push_failure upstream
' '
@ -115,4 +135,41 @@ test_expect_success 'push to existing branch, upstream configured with different
test_cmp expect-other-name actual-other-name test_cmp expect-other-name actual-other-name
' '
# We are on 'master', which integrates with 'foo' from parent1
# remote (set in test_pushdefault_workflow helper). Push to
# parent1 in centralized, and push to parent2 in triangular workflow.
# The parent1 repository has 'master' and 'foo' branches, while
# the parent2 repository has only 'master' branch.
#
# test_pushdefault_workflow() arguments:
# $1 = success or failure
# $2 = push.default value
# $3 = branch to check for actual output (master or foo)
# $4 = [optional] switch to triangular workflow
# update parent1's master (which is not our upstream)
test_pushdefault_workflow success current master
# update parent1's foo (which is our upstream)
test_pushdefault_workflow success upstream foo
# upsream is foo which is not the name of the current branch
test_pushdefault_workflow failure simple master
# master and foo are updated
test_pushdefault_workflow success matching master
# master is updated
test_pushdefault_workflow success current master triangular
# upstream mode cannot be used in triangular
test_pushdefault_workflow failure upstream foo triangular
# in triangular, 'simple' works as 'current' and update the branch
# with the same name.
test_pushdefault_workflow success simple master triangular
# master is updated (parent2 does not have foo)
test_pushdefault_workflow success matching master triangular
test_done test_done