diff options
-rw-r--r-- | Documentation/config.txt | 26 | ||||
-rw-r--r-- | builtin/push.c | 47 | ||||
-rw-r--r-- | cache.h | 1 | ||||
-rw-r--r-- | config.c | 6 | ||||
-rwxr-xr-x | t/t5528-push-default.sh | 78 | ||||
-rwxr-xr-x | t/t5570-git-daemon.sh | 30 |
6 files changed, 157 insertions, 31 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 83ad8ebce..40a6e8fb8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1683,12 +1683,30 @@ push.default:: line. Possible values are: + * `nothing` - do not push anything. -* `matching` - push all matching branches. - All branches having the same name in both ends are considered to be - matching. This is the default. +* `matching` - push all branches having the same name in both ends. + This is for those who prepare all the branches into a publishable + shape and then push them out with a single command. It is not + appropriate for pushing into a repository shared by multiple users, + since locally stalled branches will attempt a non-fast forward push + if other users updated the branch. + + + This is currently the default, but Git 2.0 will change the default + to `simple`. * `upstream` - push the current branch to its upstream branch. -* `tracking` - deprecated synonym for `upstream`. + With this, `git push` will update the same remote ref as the one which + is merged by `git pull`, making `push` and `pull` symmetrical. + See "branch.<name>.merge" for how to configure the upstream branch. +* `simple` - like `upstream`, but refuses to push if the upstream + branch's name is different from the local one. This is the safest + option and is well-suited for beginners. It will become the default + 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 + push out a single branch after finishing work, even when the other + branches are not yet ready to be pushed out. If you are working with + other people to push into the same shared repository, you would want + to use one of these. rebase.stat:: Whether to show a diffstat of what changed upstream since the last diff --git a/builtin/push.c b/builtin/push.c index 19c40d7a5..fdfcc6c71 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -76,7 +76,44 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p) return remote->url_nr; } -static void setup_push_upstream(struct remote *remote) +static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) { + /* + * There's no point in using shorten_unambiguous_ref here, + * as the ambiguity would be on the remote side, not what + * we have locally. Plus, this is supposed to be the simple + * mode. If the user is doing something crazy like setting + * upstream to a non-branch, we should probably be showing + * them the big ugly fully qualified ref. + */ + const char *advice_maybe = ""; + const char *short_upstream = + skip_prefix(branch->merge[0]->src, "refs/heads/"); + + if (!short_upstream) + short_upstream = branch->merge[0]->src; + /* + * Don't show advice for people who explicitely set + * push.default. + */ + if (push_default == PUSH_DEFAULT_UNSPECIFIED) + advice_maybe = _("\n" + "To choose either option permanently, " + "see push.default in 'git help config'."); + 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" + "\n" + " git push %s HEAD:%s\n" + "\n" + "To push to the branch of the same name on the remote, use\n" + "\n" + " git push %s %s\n" + "%s"), + remote->name, short_upstream, + remote->name, branch->name, advice_maybe); +} + +static void setup_push_upstream(struct remote *remote, int simple) { struct strbuf refspec = STRBUF_INIT; struct branch *branch = branch_get(NULL); @@ -103,6 +140,8 @@ static void setup_push_upstream(struct remote *remote) "your current branch '%s', without telling me what to push\n" "to update which remote branch."), remote->name, branch->name); + if (simple && strcmp(branch->refname, branch->merge[0]->src)) + die_push_simple(branch, remote); strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); add_refspec(refspec.buf); @@ -119,8 +158,12 @@ static void setup_default_push_refspecs(struct remote *remote) add_refspec(":"); break; + case PUSH_DEFAULT_SIMPLE: + setup_push_upstream(remote, 1); + break; + case PUSH_DEFAULT_UPSTREAM: - setup_push_upstream(remote); + setup_push_upstream(remote, 0); break; case PUSH_DEFAULT_CURRENT: @@ -580,6 +580,7 @@ enum rebase_setup_type { enum push_default_type { PUSH_DEFAULT_NOTHING = 0, PUSH_DEFAULT_MATCHING, + PUSH_DEFAULT_SIMPLE, PUSH_DEFAULT_UPSTREAM, PUSH_DEFAULT_CURRENT, PUSH_DEFAULT_UNSPECIFIED @@ -835,6 +835,8 @@ static int git_default_push_config(const char *var, const char *value) push_default = PUSH_DEFAULT_NOTHING; else if (!strcmp(value, "matching")) push_default = PUSH_DEFAULT_MATCHING; + else if (!strcmp(value, "simple")) + push_default = PUSH_DEFAULT_SIMPLE; else if (!strcmp(value, "upstream")) push_default = PUSH_DEFAULT_UPSTREAM; else if (!strcmp(value, "tracking")) /* deprecated */ @@ -843,8 +845,8 @@ static int git_default_push_config(const char *var, const char *value) push_default = PUSH_DEFAULT_CURRENT; else { error("Malformed value for %s: %s", var, value); - return error("Must be one of nothing, matching, " - "tracking or current."); + return error("Must be one of nothing, matching, simple, " + "upstream or current."); } return 0; } diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh index c334c51a0..4736da8f3 100755 --- a/t/t5528-push-default.sh +++ b/t/t5528-push-default.sh @@ -13,16 +13,36 @@ test_expect_success 'setup bare remotes' ' git push parent2 HEAD ' +# $1 = local revision +# $2 = remote revision (tested to be equal to the local one) +check_pushed_commit () { + git log -1 --format='%h %s' "$1" >expect && + git --git-dir=repo1 log -1 --format='%h %s' "$2" >actual && + test_cmp expect actual +} + +# $1 = push.default value +# $2 = expected target branch for the push +test_push_success () { + git -c push.default="$1" push && + check_pushed_commit HEAD "$2" +} + +# $1 = push.default value +# check that push fails and does not modify any remote branch +test_push_failure () { + git --git-dir=repo1 log --no-walk --format='%h %s' --all >expect && + test_must_fail git -c push.default="$1" push && + git --git-dir=repo1 log --no-walk --format='%h %s' --all >actual && + test_cmp expect actual +} + test_expect_success '"upstream" pushes to configured upstream' ' git checkout master && test_config branch.master.remote parent1 && test_config branch.master.merge refs/heads/foo && - test_config push.default upstream && test_commit two && - git push && - echo two >expect && - git --git-dir=repo1 log -1 --format=%s foo >actual && - test_cmp expect actual + test_push_success upstream foo ' test_expect_success '"upstream" does not push on unconfigured remote' ' @@ -30,7 +50,7 @@ test_expect_success '"upstream" does not push on unconfigured remote' ' test_unconfig branch.master.remote && test_config push.default upstream && test_commit three && - test_must_fail git push + test_push_failure upstream ' test_expect_success '"upstream" does not push on unconfigured branch' ' @@ -39,7 +59,7 @@ test_expect_success '"upstream" does not push on unconfigured branch' ' test_unconfig branch.master.merge && test_config push.default upstream test_commit four && - test_must_fail git push + test_push_failure upstream ' test_expect_success '"upstream" does not push when remotes do not match' ' @@ -51,4 +71,48 @@ 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' ' + git checkout -b new-branch && + test_push_failure simple && + test_push_failure matching && + test_push_failure upstream +' + +test_expect_success 'push from/to new branch with current creates remote branch' ' + test_config branch.new-branch.remote repo1 && + git checkout new-branch && + test_push_success current new-branch +' + +test_expect_success 'push to existing branch, with no upstream configured' ' + test_config branch.master.remote repo1 && + git checkout master && + test_push_failure simple && + test_push_failure upstream +' + +test_expect_success 'push to existing branch, upstream configured with same name' ' + test_config branch.master.remote repo1 && + test_config branch.master.merge refs/heads/master && + git checkout master && + test_commit six && + test_push_success upstream master && + test_commit seven && + test_push_success simple master +' + +test_expect_success 'push to existing branch, upstream configured with different name' ' + test_config branch.master.remote repo1 && + test_config branch.master.merge refs/heads/other-name && + git checkout master && + test_commit eight && + test_push_success upstream other-name && + test_commit nine && + test_push_failure simple && + git --git-dir=repo1 log -1 --format="%h %s" "other-name" >expect-other-name && + test_push_success current master && + git --git-dir=repo1 log -1 --format="%h %s" "other-name" >actual-other-name && + test_cmp expect-other-name actual-other-name +' + test_done diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 7cbc9994a..a3a4e47e1 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -103,14 +103,12 @@ test_remote_error() esac done - if test $# -ne 3 - then - error "invalid number of arguments" - fi - + msg=$1 + shift cmd=$1 - repo=$2 - msg=$3 + shift + repo=$1 + shift || error "invalid number of arguments" if test -x "$GIT_DAEMON_DOCUMENT_ROOT_PATH/$repo" then @@ -122,7 +120,7 @@ test_remote_error() fi fi - test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" 2>output && + test_must_fail git "$cmd" "$GIT_DAEMON_URL/$repo" "$@" 2>output && echo "fatal: remote error: $msg: /$repo" >expect && test_cmp expect output ret=$? @@ -131,18 +129,18 @@ test_remote_error() } msg="access denied or repository not exported" -test_expect_success 'clone non-existent' "test_remote_error clone nowhere.git '$msg'" -test_expect_success 'push disabled' "test_remote_error push repo.git '$msg'" -test_expect_success 'read access denied' "test_remote_error -x fetch repo.git '$msg'" -test_expect_success 'not exported' "test_remote_error -n fetch repo.git '$msg'" +test_expect_success 'clone non-existent' "test_remote_error '$msg' clone nowhere.git " +test_expect_success 'push disabled' "test_remote_error '$msg' push repo.git master" +test_expect_success 'read access denied' "test_remote_error -x '$msg' fetch repo.git " +test_expect_success 'not exported' "test_remote_error -n '$msg' fetch repo.git " stop_git_daemon start_git_daemon --informative-errors -test_expect_success 'clone non-existent' "test_remote_error clone nowhere.git 'no such repository'" -test_expect_success 'push disabled' "test_remote_error push repo.git 'service not enabled'" -test_expect_success 'read access denied' "test_remote_error -x fetch repo.git 'no such repository'" -test_expect_success 'not exported' "test_remote_error -n fetch repo.git 'repository not exported'" +test_expect_success 'clone non-existent' "test_remote_error 'no such repository' clone nowhere.git " +test_expect_success 'push disabled' "test_remote_error 'service not enabled' push repo.git master" +test_expect_success 'read access denied' "test_remote_error -x 'no such repository' fetch repo.git " +test_expect_success 'not exported' "test_remote_error -n 'repository not exported' fetch repo.git " stop_git_daemon test_done |