aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config.txt26
-rw-r--r--builtin/push.c47
-rw-r--r--cache.h1
-rw-r--r--config.c6
-rwxr-xr-xt/t5528-push-default.sh78
-rwxr-xr-xt/t5570-git-daemon.sh30
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:
diff --git a/cache.h b/cache.h
index d8f6f1e78..5e419a11c 100644
--- a/cache.h
+++ b/cache.h
@@ -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
diff --git a/config.c b/config.c
index ac69cb629..eeee98602 100644
--- a/config.c
+++ b/config.c
@@ -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