threads / discuss / 38060

'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

Subject: 'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

## tl;dr

7 messages between Nov 26, 2014 and Dec 1, 2014.

replies: 6people: 3as markdown or json

Adam Williamson· Nov 26, 2014, 22:29 UTC · lore

Hi, folks. Ran into an unfortunate issue with git which helped me mess up a Fedora package repo today :/

The problem can be reproduced thus:
1. Create an empty repo, clone it
2. Push its master branch with something in it (just to get started)
3. git branch --track moo origin/master
4. git checkout moo
5. echo moo >> moo && git commit -a -m "create moo"
6. git push
** BUG HAPPENS - CHANGES ARE PUSHED TO origin/master **
7. git config --local push.default simple
8. echo moo2 >> moo && git commit -a -m "update moo"
9. git push
** PUSH IS CORRECTLY REJECTED **

In both those cases, the push behaviour is supposed to be 'simple' - at step 6 it's *implicitly* set to 'simple' (according to the documentation), while at step 9 it's *explicitly* set to 'simple'. At step 6, a warning is printed to the console:

=============

warning: push.default is unset; its implicit value has changed in Git 2.0 from 'matching' to 'simple'. To squelch this message and maintain the traditional behavior, use:

  git config --global push.default matching
To squelch this message and adopt the new behavior now, use:
  git config --global push.default simple

When push.default is set to 'matching', git will push local branches to the remote branches that already exist with the same name.

Since Git 2.0, Git defaults to the more conservative 'simple' behavior, which only pushes the current branch to the corresponding remote branch that 'git pull' uses to update the current branch.

See 'git help config' and search for 'push.default' for further information. (the 'simple' mode was introduced in Git 1.7.11. Use the similar mode 'current' instead of 'simple' if you sometimes use older versions of Git)

==============

If you follow the trail there and look at 'git help config', you find this:

============
           ·   simple - in centralized workflow, work like upstream with an
               added safety to refuse to push if the upstream branch’s name is
               different from the local one.
===========
However, at step 6, the changes from branch 'moo' are pushed to 'master'
- even though that text clearly says they shouldn't be, as the names
don't match.

After step 7 - *explicitly* setting push.default to simple, rather than relying on it being set to simple *implicitly* - another git push is correctly rejected, with this message:

============

fatal: The upstream branch of your current branch does not match the name of your current branch. To push to the upstream branch on the remote, use

    git push origin HEAD:master
To push to the branch of the same name on the remote, use
    git push origin moo
=============

I believe the 'implicit' case was broken by the commit "push: change `simple` to accommodate triangular workflows":

https://github.com/git/git/commit/ed2b18292bfeedc98c9e2b6bd8a35d8001dab2fc

It changes the condition for running the 'does the branch name match' test from "if (simple)" to "if (push_default == PUSH_DEFAULT_SIMPLE)". AFAICS, in the 'implicit' case, push_default == PUSH_DEFAULT_UNSPECIFIED, not PUSH_DEFAULT_SIMPLE, so the 'does the branch name match' check is not run, even though the behaviour is supposed (according to the documentation) to be the same as if the default were explicitly set to 'simple'.

Thanks to this, when I accidentally did 'git push' on a branch of the Fedora kernel package git repo which only exists in my downstream checkout, all my changes got pushed to the upstream master branch :( So it's a bit dangerous.

-- 
Adam Williamson
Fedora QA Community Monkey
IRC: adamw | Twitter: AdamW_Fedora | XMPP: adamw AT happyassassin . net
http://www.happyassassin.net
Adam Williamson· Nov 26, 2014, 22:38 UTC · re: Adam Williamson · lore

Re: 'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

On Wed, 2014-11-26 at 14:29 -0800, Adam Williamson wrote:
> Hi, folks. Ran into an unfortunate issue with git which helped me mess
> up a Fedora package repo today :/
> 
> The problem can be reproduced thus:
Whoops, I missed step 0:
0. Ensure push.default is not configured globally
Show 11 quoted lines
> 1. Create an empty repo, clone it
> 2. Push its master branch with something in it (just to get started)
> 3. git branch --track moo origin/master
> 4. git checkout moo
> 5. echo moo >> moo && git commit -a -m "create moo"
> 6. git push
> ** BUG HAPPENS - CHANGES ARE PUSHED TO origin/master **
> 7. git config --local push.default simple
> 8. echo moo2 >> moo && git commit -a -m "update moo"
> 9. git push
> ** PUSH IS CORRECTLY REJECTED **
-- 
Adam Williamson
Fedora QA Community Monkey
IRC: adamw | Twitter: AdamW_Fedora | XMPP: adamw AT happyassassin . net
http://www.happyassassin.net
Jeff King· Nov 27, 2014, 03:43 UTC · re: Adam Williamson · lore

Re: 'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

On Wed, Nov 26, 2014 at 02:29:28PM -0800, Adam Williamson wrote:
Show 21 quoted lines
> Hi, folks. Ran into an unfortunate issue with git which helped me mess
> up a Fedora package repo today :/
> 
> The problem can be reproduced thus:
> 
> 1. Create an empty repo, clone it
> 2. Push its master branch with something in it (just to get started)
> 3. git branch --track moo origin/master
> 4. git checkout moo
> 5. echo moo >> moo && git commit -a -m "create moo"
> 6. git push
> ** BUG HAPPENS - CHANGES ARE PUSHED TO origin/master **
> 7. git config --local push.default simple
> 8. echo moo2 >> moo && git commit -a -m "update moo"
> 9. git push
> ** PUSH IS CORRECTLY REJECTED **
> 
> In both those cases, the push behaviour is supposed to be 'simple' - at
> step 6 it's *implicitly* set to 'simple' (according to the
> documentation), while at step 9 it's *explicitly* set to 'simple'. At
> step 6, a warning is printed to the console:

Ugh. Yeah, this never worked properly, even in the original v2.0.0 release. Worse, our tests did not notice it at all. Patch is below.

-- >8 --
Subject: push: truly use "simple" as default, not "upstream"

The plan for the push.default transition had all along been to use the "simple" method rather than "upstream" as a default if the user did not specify their own push.default value. Commit 11037ee (push: switch default from "matching" to "simple", 2013-01-04) tried to implement that by moving PUSH_DEFAULT_UNSPECIFIED in our switch statement to fall-through to the PUSH_DEFAULT_SIMPLE case.

When the commit that became 11037ee was originally written, that would have been enough. We would fall through to calling setup_push_upstream() with the "simple" parameter set to 1. However, it was delayed for a while until we were ready to make the transition in Git 2.0.

And in the meantime, commit ed2b182 (push: change `simple` to accommodate triangular workflows, 2013-06-19) threw a monkey wrench into the works. That commit drops the "simple" parameter to setup_push_upstream, and instead checks whether the global "push_default" is PUSH_DEFAULT_SIMPLE. This is right when the user has explicitly configured push.default to simple, but wrong when we are a fall-through for the "unspecified" case.

We never noticed because our push.default tests do not cover the case of the variable being totally unset; they only check the "simple" behavior itself.

Signed-off-by: Jeff King <peff@peff.net>
---
ed2b182 comes from Ram, but the suggestion for this bit of code actually
comes from Junio in:
  http://thread.gmane.org/gmane.comp.version-control.git/228383/focus=228436

I am not sure I understand the reason for dropping the "simple" parameter in that commit in the first place. If we are in triangular mode, then we would not get to setup_push_upstream from "simple" (or the default) in the first place (we would use "current" instead). The only time "triangular" matters to setup_push_upstream is when push.default really has been set to "upstream", but in that case, "simple" would always be 0 (and likewise, the equality check that replaces it would also be false).

So I have a vague concern that I'm missing something. Maybe one of you who worked on it can recall more.

 builtin/push.c          |  8 ++++----
 t/t5528-push-default.sh | 32 ++++++++++++++++++++++++++++++--
 2 files changed, 34 insertions(+), 6 deletions(-)
diff --git a/builtin/push.c b/builtin/push.c
index a076b19..7aedf6f 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -161,7 +161,7 @@ static const char message_detached_head_die[] =
 	   "    git push %s HEAD:<name-of-remote-branch>\n");
 
 static void setup_push_upstream(struct remote *remote, struct branch *branch,
-				int triangular)
+				int triangular, int simple)
 {
 	struct strbuf refspec = STRBUF_INIT;
 
@@ -184,7 +184,7 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch,
 		      "to update which remote branch."),
 		    remote->name, branch->name);
 
-	if (push_default == PUSH_DEFAULT_SIMPLE) {
+	if (simple) {
 		/* Additional safety */
 		if (strcmp(branch->refname, branch->merge[0]->src))
 			die_push_simple(branch, remote);
@@ -257,11 +257,11 @@ static void setup_default_push_refspecs(struct remote *remote)
 		if (triangular)
 			setup_push_current(remote, branch);
 		else
-			setup_push_upstream(remote, branch, triangular);
+			setup_push_upstream(remote, branch, triangular, 1);
 		break;
 
 	case PUSH_DEFAULT_UPSTREAM:
-		setup_push_upstream(remote, branch, triangular);
+		setup_push_upstream(remote, branch, triangular, 0);
 		break;
 
 	case PUSH_DEFAULT_CURRENT:
diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh
index 6a5ac3a..cc74519 100755
--- a/t/t5528-push-default.sh
+++ b/t/t5528-push-default.sh
@@ -26,7 +26,7 @@ check_pushed_commit () {
 # $2 = expected target branch for the push
 # $3 = [optional] repo to check for actual output (repo1 by default)
 test_push_success () {
-	git -c push.default="$1" push &&
+	git ${1:+-c push.default="$1"} push &&
 	check_pushed_commit HEAD "$2" "$3"
 }
 
@@ -34,7 +34,7 @@ test_push_success () {
 # 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 &&
+	test_must_fail git ${1:+-c push.default="$1"} push &&
 	git --git-dir=repo1 log --no-walk --format='%h %s' --all >actual &&
 	test_cmp expect actual
 }
@@ -172,4 +172,32 @@ test_pushdefault_workflow success simple master triangular
 # master is updated (parent2 does not have foo)
 test_pushdefault_workflow success matching master triangular
 
+# default tests, when no push-default is specified. This
+# should behave the same as "simple" in non-triangular
+# settings, and as "current" otherwise.
+
+test_expect_success 'default behavior allows "simple" push' '
+	test_config branch.master.remote parent1 &&
+	test_config branch.master.merge refs/heads/master &&
+	test_config remote.pushdefault parent1 &&
+	test_commit default-master-master &&
+	test_push_success "" master
+'
+
+test_expect_success 'default behavior rejects non-simple push' '
+	test_config branch.master.remote parent1 &&
+	test_config branch.master.merge refs/heads/foo &&
+	test_config remote.pushdefault parent1 &&
+	test_commit default-master-foo &&
+	test_push_failure ""
+'
+
+test_expect_success 'default triangular behavior acts like "current"' '
+	test_config branch.master.remote parent1 &&
+	test_config branch.master.merge refs/heads/foo &&
+	test_config remote.pushdefault parent2 &&
+	test_commit default-triangular &&
+	test_push_success "" master repo2
+'
+
 test_done
-- 
2.2.0.rc2.402.g4519813
Adam Williamson· Nov 27, 2014, 04:24 UTC · re: Jeff King · lore

Re: 'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

On November 26, 2014 7:43:06 PM PST, Jeff King <peff@peff.net> wrote:
Show 181 quoted lines
>On Wed, Nov 26, 2014 at 02:29:28PM -0800, Adam Williamson wrote:
>
>> Hi, folks. Ran into an unfortunate issue with git which helped me
>mess
>> up a Fedora package repo today :/
>> 
>> The problem can be reproduced thus:
>> 
>> 1. Create an empty repo, clone it
>> 2. Push its master branch with something in it (just to get started)
>> 3. git branch --track moo origin/master
>> 4. git checkout moo
>> 5. echo moo >> moo && git commit -a -m "create moo"
>> 6. git push
>> ** BUG HAPPENS - CHANGES ARE PUSHED TO origin/master **
>> 7. git config --local push.default simple
>> 8. echo moo2 >> moo && git commit -a -m "update moo"
>> 9. git push
>> ** PUSH IS CORRECTLY REJECTED **
>> 
>> In both those cases, the push behaviour is supposed to be 'simple' -
>at
>> step 6 it's *implicitly* set to 'simple' (according to the
>> documentation), while at step 9 it's *explicitly* set to 'simple'. At
>> step 6, a warning is printed to the console:
>
>Ugh. Yeah, this never worked properly, even in the original v2.0.0
>release. Worse, our tests did not notice it at all.  Patch is below.
>
>
>-- >8 --
>Subject: push: truly use "simple" as default, not "upstream"
>
>The plan for the push.default transition had all along been
>to use the "simple" method rather than "upstream" as a
>default if the user did not specify their own push.default
>value. Commit 11037ee (push: switch default from "matching"
>to "simple", 2013-01-04) tried to implement that by moving
>PUSH_DEFAULT_UNSPECIFIED in our switch statement to
>fall-through to the PUSH_DEFAULT_SIMPLE case.
>
>When the commit that became 11037ee was originally written,
>that would have been enough. We would fall through to
>calling setup_push_upstream() with the "simple" parameter
>set to 1. However, it was delayed for a while until we were
>ready to make the transition in Git 2.0.
>
>And in the meantime, commit ed2b182 (push: change `simple`
>to accommodate triangular workflows, 2013-06-19) threw a
>monkey wrench into the works. That commit drops the "simple"
>parameter to setup_push_upstream, and instead checks whether
>the global "push_default" is PUSH_DEFAULT_SIMPLE. This is
>right when the user has explicitly configured push.default
>to simple, but wrong when we are a fall-through for the
>"unspecified" case.
>
>We never noticed because our push.default tests do not cover
>the case of the variable being totally unset; they only
>check the "simple" behavior itself.
>
>Signed-off-by: Jeff King <peff@peff.net>
>---
>ed2b182 comes from Ram, but the suggestion for this bit of code
>actually
>comes from Junio in:
>
>http://thread.gmane.org/gmane.comp.version-control.git/228383/focus=228436
>
>I am not sure I understand the reason for dropping the "simple"
>parameter in that commit in the first place. If we are in triangular
>mode, then we would not get to setup_push_upstream from "simple" (or
>the
>default) in the first place (we would use "current" instead). The only
>time "triangular" matters to setup_push_upstream is when push.default
>really has been set to "upstream", but in that case, "simple" would
>always be 0 (and likewise, the equality check that replaces it would
>also be false).
>
>So I have a vague concern that I'm missing something. Maybe one of you
>who worked on it can recall more.
>
> builtin/push.c          |  8 ++++----
> t/t5528-push-default.sh | 32 ++++++++++++++++++++++++++++++--
> 2 files changed, 34 insertions(+), 6 deletions(-)
>
>diff --git a/builtin/push.c b/builtin/push.c
>index a076b19..7aedf6f 100644
>--- a/builtin/push.c
>+++ b/builtin/push.c
>@@ -161,7 +161,7 @@ static const char message_detached_head_die[] =
> 	   "    git push %s HEAD:<name-of-remote-branch>\n");
> 
>static void setup_push_upstream(struct remote *remote, struct branch
>*branch,
>-				int triangular)
>+				int triangular, int simple)
> {
> 	struct strbuf refspec = STRBUF_INIT;
> 
>@@ -184,7 +184,7 @@ static void setup_push_upstream(struct remote
>*remote, struct branch *branch,
> 		      "to update which remote branch."),
> 		    remote->name, branch->name);
> 
>-	if (push_default == PUSH_DEFAULT_SIMPLE) {
>+	if (simple) {
> 		/* Additional safety */
> 		if (strcmp(branch->refname, branch->merge[0]->src))
> 			die_push_simple(branch, remote);
>@@ -257,11 +257,11 @@ static void setup_default_push_refspecs(struct
>remote *remote)
> 		if (triangular)
> 			setup_push_current(remote, branch);
> 		else
>-			setup_push_upstream(remote, branch, triangular);
>+			setup_push_upstream(remote, branch, triangular, 1);
> 		break;
> 
> 	case PUSH_DEFAULT_UPSTREAM:
>-		setup_push_upstream(remote, branch, triangular);
>+		setup_push_upstream(remote, branch, triangular, 0);
> 		break;
> 
> 	case PUSH_DEFAULT_CURRENT:
>diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh
>index 6a5ac3a..cc74519 100755
>--- a/t/t5528-push-default.sh
>+++ b/t/t5528-push-default.sh
>@@ -26,7 +26,7 @@ check_pushed_commit () {
> # $2 = expected target branch for the push
> # $3 = [optional] repo to check for actual output (repo1 by default)
> test_push_success () {
>-	git -c push.default="$1" push &&
>+	git ${1:+-c push.default="$1"} push &&
> 	check_pushed_commit HEAD "$2" "$3"
> }
> 
>@@ -34,7 +34,7 @@ test_push_success () {
> # 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 &&
>+	test_must_fail git ${1:+-c push.default="$1"} push &&
> 	git --git-dir=repo1 log --no-walk --format='%h %s' --all >actual &&
> 	test_cmp expect actual
> }
>@@ -172,4 +172,32 @@ test_pushdefault_workflow success simple master
>triangular
> # master is updated (parent2 does not have foo)
> test_pushdefault_workflow success matching master triangular
> 
>+# default tests, when no push-default is specified. This
>+# should behave the same as "simple" in non-triangular
>+# settings, and as "current" otherwise.
>+
>+test_expect_success 'default behavior allows "simple" push' '
>+	test_config branch.master.remote parent1 &&
>+	test_config branch.master.merge refs/heads/master &&
>+	test_config remote.pushdefault parent1 &&
>+	test_commit default-master-master &&
>+	test_push_success "" master
>+'
>+
>+test_expect_success 'default behavior rejects non-simple push' '
>+	test_config branch.master.remote parent1 &&
>+	test_config branch.master.merge refs/heads/foo &&
>+	test_config remote.pushdefault parent1 &&
>+	test_commit default-master-foo &&
>+	test_push_failure ""
>+'
>+
>+test_expect_success 'default triangular behavior acts like "current"'
>'
>+	test_config branch.master.remote parent1 &&
>+	test_config branch.master.merge refs/heads/foo &&
>+	test_config remote.pushdefault parent2 &&
>+	test_commit default-triangular &&
>+	test_push_success "" master repo2
>+'
>+
> test_done
Yeah, I've gone down pretty much exactly the same avenues of investigation, but had to suspend it to go out to dinner. I wanted to try and completely grok the whole history of this particular bit of behavior before suggesting a patch. So far my guess is that junio just got a bit mixed up with working on top of ram's changes and didn't catch that the check wouldn't work in the implicit case, but I wanted to look into it a bit more.
-- 
Adam Williamson
Fedora QA Community Monkey
IRC: adamw | Twitter: AdamW_Fedora | XMPP: adamw AT happyassassin DOT net
http://www.happyassassin.net
Adam Williamson· Nov 27, 2014, 17:12 UTC · re: Jeff King · lore

Re: 'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

On Wed, 2014-11-26 at 22:43 -0500, Jeff King wrote:
Show 26 quoted lines
> On Wed, Nov 26, 2014 at 02:29:28PM -0800, Adam Williamson wrote:
> 
> > Hi, folks. Ran into an unfortunate issue with git which helped me mess
> > up a Fedora package repo today :/
> > 
> > The problem can be reproduced thus:
> > 
> > 1. Create an empty repo, clone it
> > 2. Push its master branch with something in it (just to get started)
> > 3. git branch --track moo origin/master
> > 4. git checkout moo
> > 5. echo moo >> moo && git commit -a -m "create moo"
> > 6. git push
> > ** BUG HAPPENS - CHANGES ARE PUSHED TO origin/master **
> > 7. git config --local push.default simple
> > 8. echo moo2 >> moo && git commit -a -m "update moo"
> > 9. git push
> > ** PUSH IS CORRECTLY REJECTED **
> > 
> > In both those cases, the push behaviour is supposed to be 'simple' - at
> > step 6 it's *implicitly* set to 'simple' (according to the
> > documentation), while at step 9 it's *explicitly* set to 'simple'. At
> > step 6, a warning is printed to the console:
> 
> Ugh. Yeah, this never worked properly, even in the original v2.0.0
> release. Worse, our tests did not notice it at all.  Patch is below.

I've got a pile of Fedora stuff to do today so I don't know if I'll get time to dig any further into the history of this and see if there are any hidden wrinkles, but on the face of it, Jeff's patch looks good to me. I'll try and find a moment to test it at least, but the approach makes sense and is what I would've gone for too.

It might also be worth improving the warn_unspecified_push_default_msg[] text to mention the name matching behaviour? At present it doesn't clearly explain this (you could argue it's *sort of* implied, but I doubt many people will read it that way - I didn't), you have to follow the chain into 'git help config' to find the description. Something like:

   "Since Git 2.0, Git defaults to the more conservative 'simple'\n"
   "behavior, which only pushes the current branch to the corresponding\n"
   "remote branch that 'git pull' uses to update the current branch, \n"
   "if the names of those two branches match.\n"
-- 
Adam Williamson
Fedora QA Community Monkey
IRC: adamw | Twitter: AdamW_Fedora | XMPP: adamw AT happyassassin . net
http://www.happyassassin.net
Jeff King· Nov 28, 2014, 04:55 UTC · re: Adam Williamson · lore

Re: 'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

On Thu, Nov 27, 2014 at 09:12:27AM -0800, Adam Williamson wrote:
Show 11 quoted lines
> It might also be worth improving the warn_unspecified_push_default_msg[]
> text to mention the name matching behaviour? At present it doesn't
> clearly explain this (you could argue it's *sort of* implied, but I
> doubt many people will read it that way - I didn't), you have to follow
> the chain into 'git help config' to find the description. Something
> like:
> 
>    "Since Git 2.0, Git defaults to the more conservative 'simple'\n"
>    "behavior, which only pushes the current branch to the corresponding\n"
>    "remote branch that 'git pull' uses to update the current branch, \n"
>    "if the names of those two branches match.\n"
Yeah, I agree that is more clear.

There is some other magic with "simple", too, around triangular workflows. Describing it in detail would probably be too verbose in this message, but we do refer to the description of push.default, which is probably enough. Technically this new bit you are adding here is covered there, too. But since we can improve the description by adding such a small amount of text in this case, it seems like a reasonable tradeoff.

I suppose we could also customize the message based on the triangular and non-triangular cases. I dunno.

-Peff
Junio C Hamano· Dec 1, 2014, 02:21 UTC · re: Jeff King · lore

Re: 'simple' push check that branch name matches does not work if push.default is unset (and hence implicitly simple)

Jeff King <peff@peff.net> writes:
Show 10 quoted lines
> There is some other magic with "simple", too, around triangular
> workflows. Describing it in detail would probably be too verbose in this
> message, but we do refer to the description of push.default, which is
> probably enough.  Technically this new bit you are adding here is
> covered there, too. But since we can improve the description by adding
> such a small amount of text in this case, it seems like a reasonable
> tradeoff.
>
> I suppose we could also customize the message based on the triangular
> and non-triangular cases. I dunno.

Yeah, I vaguely recall suggesting to polish advice message further to help users along a similar line, but probably that fell in the cracks.

Thanks for a quick fix.

← back to recent threads