threads / patch / 51416

patchtransport-helper: enforce atomic in push_refs_with_push

Subject: [PATCH] transport-helper: enforce atomic in push_refs_with_push

## tl;dr

45 messages between Jul 2, 2019 and Jul 27, 2019. Diffs are folded; open one to read it.

replies: 44people: 8as markdown or json

Emily Shaffer· Jul 2, 2019, 00:53 UTC · lore

Teach transport-helper how to notice if skipping a ref during push would violate atomicity on the client side. We notice that a ref would be rejected, and choose not to send it, but don't notice that if the client has asked for --atomic we are violating atomicity if all the other pushes we are sending would succeed. Asking the server end to uphold atomicity wouldn't work here as the server doesn't have any idea that we tried to update a ref that's broken.

The added test-case is a succinct way to reproduce this issue that fails today. The same steps work fine when we aren't using a transport-helper to get to the upstream, i.e. when we've added a local repository as a remote:

  git remote add ~/upstream upstream
Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
---
 t/t5541-http-push-smart.sh | 58 ++++++++++++++++++++++++++++++++++++++
 transport-helper.c         |  6 ++++
 transport.c                | 15 +++++++++-
 3 files changed, 78 insertions(+), 1 deletion(-)
Show changes to 3 files +78 −1

t/t5541-http-push-smart.sh, transport-helper.c, transport.c

diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 8ef8763e06..b57f6d480f 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -177,6 +177,64 @@ test_expect_success 'push (chunked)' '
 	 test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+test_expect_success 'push --atomic also prevents branch creation' '
+	# Make up/master
+	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
+	git init --bare "$d" &&
+	git --git-dir="$d" config http.receivepack true &&
+	up="$HTTPD_URL"/smart/atomic-branches.git &&
+	test_commit atomic1 &&
+	test_commit atomic2 &&
+	git push "$up" master &&
+	# Make master incompatible with up/master
+	git reset --hard HEAD^ &&
+	# Add a new branch
+	git branch atomic &&
+	# --atomic should roll back creation of up/atomic
+	test_must_fail git push --atomic "$up" master atomic &&
+	git ls-remote "$up" >up-remotes &&
+	test_must_fail grep atomic up-remotes
+'
+
+test_expect_success 'push --atomic shows all failed refs' '
+	# Make up/master, up/allrefs
+	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
+	git init --bare "$d" &&
+	git --git-dir="$d" config http.receivepack true &&
+	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
+	test_commit allrefs1 &&
+	test_commit allrefs2 &&
+	git branch allrefs &&
+	git push "$up" master allrefs &&
+	# Make master and allrefs incompatible with up/master, up/allrefs
+	git checkout allrefs &&
+	git reset --hard HEAD^ &&
+	git checkout master &&
+	git reset --hard HEAD^ &&
+	# --atomic should complain about both master and allrefs
+	test_must_fail git push --atomic "$up" master allrefs >&output &&
+	grep master output &&
+	grep allrefs output
+'
+
+test_expect_success 'push --atomic indicates collateral failures' '
+	# Make up/master, up/collateral
+	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-collateral.git &&
+	git init --bare "$d" &&
+	git --git-dir="$d" config http.receivepack true &&
+	up="$HTTPD_URL"/smart/atomic-collateral.git &&
+	test_commit collateral1 &&
+	test_commit collateral2 &&
+	git branch collateral &&
+	git push "$up" master collateral &&
+	# Make master incompatible with up/master
+	git reset --hard HEAD^ &&
+	# --atomic should mention collateral was OK but failed anyway
+	test_must_fail git push --atomic "$up" master collateral >&output &&
+	grep "master -> master" output &&
+	grep "collateral -> collateral" output
+'
+
 test_expect_success 'push --all can push to empty repo' '
 	d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
 	git init --bare "$d" &&
diff --git a/transport-helper.c b/transport-helper.c
index c7e17ec9cb..6b05a88faf 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -853,6 +853,7 @@ static int push_refs_with_push(struct transport *transport,
 {
 	int force_all = flags & TRANSPORT_PUSH_FORCE;
 	int mirror = flags & TRANSPORT_PUSH_MIRROR;
+	int atomic = flags & TRANSPORT_PUSH_ATOMIC;
 	struct helper_data *data = transport->data;
 	struct strbuf buf = STRBUF_INIT;
 	struct ref *ref;
@@ -872,6 +873,11 @@ static int push_refs_with_push(struct transport *transport,
 		case REF_STATUS_REJECT_NONFASTFORWARD:
 		case REF_STATUS_REJECT_STALE:
 		case REF_STATUS_REJECT_ALREADY_EXISTS:
+			if (atomic) {
+				string_list_clear(&cas_options, 0);
+				return 0;
+			} else
+				continue;
 		case REF_STATUS_UPTODATE:
 			continue;
 		default:
diff --git a/transport.c b/transport.c
index f1fcd2c4b0..f4d6b38f9d 100644
--- a/transport.c
+++ b/transport.c
@@ -1226,10 +1226,23 @@ int transport_push(struct repository *r,
 		err = push_had_errors(remote_refs);
 		ret = push_ret | err;
 
-		if (!quiet || err)
+		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
+			for (struct ref *it = remote_refs; it; it = it->next)
+				switch (it->status) {
+				case REF_STATUS_NONE:
+				case REF_STATUS_UPTODATE:
+				case REF_STATUS_OK:
+					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+				default:
+					continue;
+				}
+		}
+
+		if (!quiet || err) {
 			transport_print_push_status(transport->url, remote_refs,
 					verbose | porcelain, porcelain,
 					reject_reasons);
+		}
 
 		if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
 			set_upstreams(transport, remote_refs, pretend);
-- 
2.22.0.410.gd8fdbe21b5-goog
Johannes Schindelin· Jul 2, 2019, 13:51 UTC · re: Emily Shaffer · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Hi Emily,
On Mon, 1 Jul 2019, Emily Shaffer wrote:
Show 7 quoted lines
> Teach transport-helper how to notice if skipping a ref during push would
> violate atomicity on the client side. We notice that a ref would be
> rejected, and choose not to send it, but don't notice that if the client
> has asked for --atomic we are violating atomicity if all the other
> pushes we are sending would succeed. Asking the server end to uphold
> atomicity wouldn't work here as the server doesn't have any idea that we
> tried to update a ref that's broken.
Makes sense.
Show 27 quoted lines
> The added test-case is a succinct way to reproduce this issue that fails
> today. The same steps work fine when we aren't using a transport-helper
> to get to the upstream, i.e. when we've added a local repository as a
> remote:
>
>   git remote add ~/upstream upstream
>
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> ---
>  t/t5541-http-push-smart.sh | 58 ++++++++++++++++++++++++++++++++++++++
>  transport-helper.c         |  6 ++++
>  transport.c                | 15 +++++++++-
>  3 files changed, 78 insertions(+), 1 deletion(-)
>
> diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
> index 8ef8763e06..b57f6d480f 100755
> --- a/t/t5541-http-push-smart.sh
> +++ b/t/t5541-http-push-smart.sh
> @@ -177,6 +177,64 @@ test_expect_success 'push (chunked)' '
>  	 test $HEAD = $(git rev-parse --verify HEAD))
>  '
>
> +test_expect_success 'push --atomic also prevents branch creation' '
> +	# Make up/master
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
> +	git init --bare "$d" &&
> +	git --git-dir="$d" config http.receivepack true &&
Why not `-C "$d"`? And why not `test_config`?
> +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> +	test_commit atomic1 &&
> +	test_commit atomic2 &&
> +	git push "$up" master &&

It would be more succinct to do a `git clone --bare . "$d"` here, instead of a `git init --bare` and a `git push` no?

Show 8 quoted lines
> +	# Make master incompatible with up/master
> +	git reset --hard HEAD^ &&
> +	# Add a new branch
> +	git branch atomic &&
> +	# --atomic should roll back creation of up/atomic
> +	test_must_fail git push --atomic "$up" master atomic &&
> +	git ls-remote "$up" >up-remotes &&
> +	test_must_fail grep atomic up-remotes
Why not `test_must_fail git -C "$d" rev-parse refs/heads/atomic`?
Show 22 quoted lines
> +'
> +
> +test_expect_success 'push --atomic shows all failed refs' '
> +	# Make up/master, up/allrefs
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
> +	git init --bare "$d" &&
> +	git --git-dir="$d" config http.receivepack true &&
> +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
> +	test_commit allrefs1 &&
> +	test_commit allrefs2 &&
> +	git branch allrefs &&
> +	git push "$up" master allrefs &&
> +	# Make master and allrefs incompatible with up/master, up/allrefs
> +	git checkout allrefs &&
> +	git reset --hard HEAD^ &&
> +	git checkout master &&
> +	git reset --hard HEAD^ &&
> +	# --atomic should complain about both master and allrefs
> +	test_must_fail git push --atomic "$up" master allrefs >&output &&
> +	grep master output &&
> +	grep allrefs output
> +'

I have the impression that the setup these two new test cases perform are _very_ similar, making it most likely that a combined test case would be more succinct, yet still complete and easily readable.

Show 18 quoted lines
> +
> +test_expect_success 'push --atomic indicates collateral failures' '
> +	# Make up/master, up/collateral
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-collateral.git &&
> +	git init --bare "$d" &&
> +	git --git-dir="$d" config http.receivepack true &&
> +	up="$HTTPD_URL"/smart/atomic-collateral.git &&
> +	test_commit collateral1 &&
> +	test_commit collateral2 &&
> +	git branch collateral &&
> +	git push "$up" master collateral &&
> +	# Make master incompatible with up/master
> +	git reset --hard HEAD^ &&
> +	# --atomic should mention collateral was OK but failed anyway
> +	test_must_fail git push --atomic "$up" master collateral >&output &&
> +	grep "master -> master" output &&
> +	grep "collateral -> collateral" output
> +'
Same here.
Show 38 quoted lines
> +
>  test_expect_success 'push --all can push to empty repo' '
>  	d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
>  	git init --bare "$d" &&
> diff --git a/transport-helper.c b/transport-helper.c
> index c7e17ec9cb..6b05a88faf 100644
> --- a/transport-helper.c
> +++ b/transport-helper.c
> @@ -853,6 +853,7 @@ static int push_refs_with_push(struct transport *transport,
>  {
>  	int force_all = flags & TRANSPORT_PUSH_FORCE;
>  	int mirror = flags & TRANSPORT_PUSH_MIRROR;
> +	int atomic = flags & TRANSPORT_PUSH_ATOMIC;
>  	struct helper_data *data = transport->data;
>  	struct strbuf buf = STRBUF_INIT;
>  	struct ref *ref;
> @@ -872,6 +873,11 @@ static int push_refs_with_push(struct transport *transport,
>  		case REF_STATUS_REJECT_NONFASTFORWARD:
>  		case REF_STATUS_REJECT_STALE:
>  		case REF_STATUS_REJECT_ALREADY_EXISTS:
> +			if (atomic) {
> +				string_list_clear(&cas_options, 0);
> +				return 0;
> +			} else
> +				continue;
>  		case REF_STATUS_UPTODATE:
>  			continue;
>  		default:
> diff --git a/transport.c b/transport.c
> index f1fcd2c4b0..f4d6b38f9d 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -1226,10 +1226,23 @@ int transport_push(struct repository *r,
>  		err = push_had_errors(remote_refs);
>  		ret = push_ret | err;
>
> -		if (!quiet || err)
> +		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
This looks funny. And it does so only...
Show 12 quoted lines
> +			for (struct ref *it = remote_refs; it; it = it->next)
> +				switch (it->status) {
> +				case REF_STATUS_NONE:
> +				case REF_STATUS_UPTODATE:
> +				case REF_STATUS_OK:
> +					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
> +				default:
> +					continue;
> +				}
> +		}
> +
> +		if (!quiet || err) {

... because a curly was introduced around a single-liner. This adds unnecessary noise to the patch.

This easily distracts reviewers like myself from more important questions such as: why was this conditional switch added before this conditional block, does it intend to influence the printed push status? Ah, yes, of course, even if `it->status` is changed, it actually modifies the data to which `remote_refs` points. So yes, this has to be done here.

Show 8 quoted lines
>  			transport_print_push_status(transport->url, remote_refs,
>  					verbose | porcelain, porcelain,
>  					reject_reasons);
> +		}
>
>  		if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
>  			set_upstreams(transport, remote_refs, pretend);
> --

Apart from minor nits, I like it. Thanks, Dscho

> 2.22.0.410.gd8fdbe21b5-goog
>
>
Junio C Hamano· Jul 2, 2019, 18:27 UTC · re: Johannes Schindelin · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 10 quoted lines
>> +	# Make master incompatible with up/master
>> +	git reset --hard HEAD^ &&
>> +	# Add a new branch
>> +	git branch atomic &&
>> +	# --atomic should roll back creation of up/atomic
>> +	test_must_fail git push --atomic "$up" master atomic &&
>> +	git ls-remote "$up" >up-remotes &&
>> +	test_must_fail grep atomic up-remotes
>
> Why not `test_must_fail git -C "$d" rev-parse refs/heads/atomic`?
They check different expectations.

Between making sure that the state observable in the resulting repository matches what should have been left and checking what the command says it did to its output, I agree that the former is a more robust way to test the commands.

We obviously could test both, though ;-)
Emily Shaffer· Jul 3, 2019, 18:56 UTC · re: Johannes Schindelin · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Show 7 quoted lines
> > +test_expect_success 'push --atomic also prevents branch creation' '
> > +	# Make up/master
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
> > +	git init --bare "$d" &&
> > +	git --git-dir="$d" config http.receivepack true &&
> 
> Why not `-C "$d"`?

The example I had found below the new ones used --git-dir, but yeah, there's no reason not to use -C instead. Changing.

> And why not `test_config`?

Done, didn't know about it and it's not used in the test I referred to while writing this one ('push --all can push to empty repo'). Thanks, I learned something new.

Show 8 quoted lines
> 
> > +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> > +	test_commit atomic1 &&
> > +	test_commit atomic2 &&
> > +	git push "$up" master &&
> 
> It would be more succinct to do a `git clone --bare . "$d"` here, instead
> of a `git init --bare` and a `git push` no?

I'm not sure I would say "more succinct." This leaves the test with the same number of lines, but now it says:

  Make some commits
  Name a Git directory
  Clone to the new Git directory
  Do some config on the new Git directory
  Name a remote URL
  Change some commits
  ...
In my opinion, it's more readable the way it is now:
  {Do some setup stuff.}
  Name a Git directory
  Init it
  Config it
  Name the remote URL
  {Do the test stuff.}
  Make some commits
  Push some commits
  Change some commits
  ...

I did add another comment to separate "Make 'up'" and "Make up/master", which I hope expresses my intent in organizing it this way.

Show 11 quoted lines
> 
> > +	# Make master incompatible with up/master
> > +	git reset --hard HEAD^ &&
> > +	# Add a new branch
> > +	git branch atomic &&
> > +	# --atomic should roll back creation of up/atomic
> > +	test_must_fail git push --atomic "$up" master atomic &&
> > +	git ls-remote "$up" >up-remotes &&
> > +	test_must_fail grep atomic up-remotes
> 
> Why not `test_must_fail git -C "$d" rev-parse refs/heads/atomic`?
Sure, changed.
Show 26 quoted lines
> > > +'
> > +
> > +test_expect_success 'push --atomic shows all failed refs' '
> > +	# Make up/master, up/allrefs
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
> > +	git init --bare "$d" &&
> > +	git --git-dir="$d" config http.receivepack true &&
> > +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
> > +	test_commit allrefs1 &&
> > +	test_commit allrefs2 &&
> > +	git branch allrefs &&
> > +	git push "$up" master allrefs &&
> > +	# Make master and allrefs incompatible with up/master, up/allrefs
> > +	git checkout allrefs &&
> > +	git reset --hard HEAD^ &&
> > +	git checkout master &&
> > +	git reset --hard HEAD^ &&
> > +	# --atomic should complain about both master and allrefs
> > +	test_must_fail git push --atomic "$up" master allrefs >&output &&
> > +	grep master output &&
> > +	grep allrefs output
> > +'
> 
> I have the impression that the setup these two new test cases perform are
> _very_ similar, making it most likely that a combined test case would be
> more succinct, yet still complete and easily readable.
(Junio replied to this downthread... I have more to ask too.)
Show 84 quoted lines
> 
> > +
> > +test_expect_success 'push --atomic indicates collateral failures' '
> > +	# Make up/master, up/collateral
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-collateral.git &&
> > +	git init --bare "$d" &&
> > +	git --git-dir="$d" config http.receivepack true &&
> > +	up="$HTTPD_URL"/smart/atomic-collateral.git &&
> > +	test_commit collateral1 &&
> > +	test_commit collateral2 &&
> > +	git branch collateral &&
> > +	git push "$up" master collateral &&
> > +	# Make master incompatible with up/master
> > +	git reset --hard HEAD^ &&
> > +	# --atomic should mention collateral was OK but failed anyway
> > +	test_must_fail git push --atomic "$up" master collateral >&output &&
> > +	grep "master -> master" output &&
> > +	grep "collateral -> collateral" output
> > +'
> 
> Same here.
> 
> > +
> >  test_expect_success 'push --all can push to empty repo' '
> >  	d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
> >  	git init --bare "$d" &&
> > diff --git a/transport-helper.c b/transport-helper.c
> > index c7e17ec9cb..6b05a88faf 100644
> > --- a/transport-helper.c
> > +++ b/transport-helper.c
> > @@ -853,6 +853,7 @@ static int push_refs_with_push(struct transport *transport,
> >  {
> >  	int force_all = flags & TRANSPORT_PUSH_FORCE;
> >  	int mirror = flags & TRANSPORT_PUSH_MIRROR;
> > +	int atomic = flags & TRANSPORT_PUSH_ATOMIC;
> >  	struct helper_data *data = transport->data;
> >  	struct strbuf buf = STRBUF_INIT;
> >  	struct ref *ref;
> > @@ -872,6 +873,11 @@ static int push_refs_with_push(struct transport *transport,
> >  		case REF_STATUS_REJECT_NONFASTFORWARD:
> >  		case REF_STATUS_REJECT_STALE:
> >  		case REF_STATUS_REJECT_ALREADY_EXISTS:
> > +			if (atomic) {
> > +				string_list_clear(&cas_options, 0);
> > +				return 0;
> > +			} else
> > +				continue;
> >  		case REF_STATUS_UPTODATE:
> >  			continue;
> >  		default:
> > diff --git a/transport.c b/transport.c
> > index f1fcd2c4b0..f4d6b38f9d 100644
> > --- a/transport.c
> > +++ b/transport.c
> > @@ -1226,10 +1226,23 @@ int transport_push(struct repository *r,
> >  		err = push_had_errors(remote_refs);
> >  		ret = push_ret | err;
> >
> > -		if (!quiet || err)
> > +		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
> 
> This looks funny. And it does so only...
> 
> > +			for (struct ref *it = remote_refs; it; it = it->next)
> > +				switch (it->status) {
> > +				case REF_STATUS_NONE:
> > +				case REF_STATUS_UPTODATE:
> > +				case REF_STATUS_OK:
> > +					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
> > +				default:
> > +					continue;
> > +				}
> > +		}
> > +
> > +		if (!quiet || err) {
> 
> ... because a curly was introduced around a single-liner. This adds
> unnecessary noise to the patch.
> 
> This easily distracts reviewers like myself from more important questions
> such as: why was this conditional switch added before this conditional
> block, does it intend to influence the printed push status? Ah, yes, of
> course, even if `it->status` is changed, it actually modifies the data
> to which `remote_refs` points. So yes, this has to be done here.

Oops, I thought I had omitted the new braces when I was staging the changes. Really sorry for the distraction. You're right that it makes the diff look weird.

Show 16 quoted lines
> 
> >  			transport_print_push_status(transport->url, remote_refs,
> >  					verbose | porcelain, porcelain,
> >  					reject_reasons);
> > +		}
> >
> >  		if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
> >  			set_upstreams(transport, remote_refs, pretend);
> > --
> 
> Apart from minor nits, I like it. Thanks,
> Dscho
> 
> > 2.22.0.410.gd8fdbe21b5-goog
> >
> >
Emily Shaffer· Jul 3, 2019, 19:01 UTC · re: Emily Shaffer · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Show 28 quoted lines
> > > > +'
> > > +
> > > +test_expect_success 'push --atomic shows all failed refs' '
> > > +	# Make up/master, up/allrefs
> > > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
> > > +	git init --bare "$d" &&
> > > +	git --git-dir="$d" config http.receivepack true &&
> > > +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
> > > +	test_commit allrefs1 &&
> > > +	test_commit allrefs2 &&
> > > +	git branch allrefs &&
> > > +	git push "$up" master allrefs &&
> > > +	# Make master and allrefs incompatible with up/master, up/allrefs
> > > +	git checkout allrefs &&
> > > +	git reset --hard HEAD^ &&
> > > +	git checkout master &&
> > > +	git reset --hard HEAD^ &&
> > > +	# --atomic should complain about both master and allrefs
> > > +	test_must_fail git push --atomic "$up" master allrefs >&output &&
> > > +	grep master output &&
> > > +	grep allrefs output
> > > +'
> > 
> > I have the impression that the setup these two new test cases perform are
> > _very_ similar, making it most likely that a combined test case would be
> > more succinct, yet still complete and easily readable.
> 
> (Junio replied to this downthread... I have more to ask too.)

Oops, I misremembered which comment he had replied to. Yeah, I'll think about this and reword.

Reroll to come shortly. Thanks, all.
 - Emily
Johannes Schindelin· Jul 3, 2019, 19:41 UTC · re: Emily Shaffer · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Hi Emily,
On Wed, 3 Jul 2019, Emily Shaffer wrote:
Show 10 quoted lines
> > > +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> > > +	test_commit atomic1 &&
> > > +	test_commit atomic2 &&
> > > +	git push "$up" master &&
> >
> > It would be more succinct to do a `git clone --bare . "$d"` here, instead
> > of a `git init --bare` and a `git push` no?
>
> I'm not sure I would say "more succinct." This leaves the test with the
> same number of lines,

No, it does not, as `git clone --bare . "$d"` does _both_ the initializing and the object transfer.

It only saves one line, of course, but do keep in mind that anybody running into any kind of regression with your test case needs to understand what it does. And from experience I can tell you that reading any test case longer than 5 lines is quite annoying when you actually only care about fixing the regression, and not so much about the wonderful story the test case tells.

So in a sense, I guess I would even suggest to move as much of the setup for your test cases outside, preferably into an initial `setup` test case that initializes the minimal scenario required by the regression test case.

Thanks, Dscho

Emily Shaffer· Jul 3, 2019, 20:57 UTC · re: Johannes Schindelin · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

On Wed, Jul 03, 2019 at 09:41:46PM +0200, Johannes Schindelin wrote:
Show 24 quoted lines
> Hi Emily,
> 
> On Wed, 3 Jul 2019, Emily Shaffer wrote:
> 
> > > > +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> > > > +	test_commit atomic1 &&
> > > > +	test_commit atomic2 &&
> > > > +	git push "$up" master &&
> > >
> > > It would be more succinct to do a `git clone --bare . "$d"` here, instead
> > > of a `git init --bare` and a `git push` no?
> >
> > I'm not sure I would say "more succinct." This leaves the test with the
> > same number of lines,
> 
> No, it does not, as `git clone --bare . "$d"` does _both_ the initializing
> and the object transfer.
> 
> It only saves one line, of course, but do keep in mind that anybody
> running into any kind of regression with your test case needs to
> understand what it does. And from experience I can tell you that reading
> any test case longer than 5 lines is quite annoying when you actually
> only care about fixing the regression, and not so much about the wonderful
> story the test case tells.

I suppose I'm confused, then, as I understood you were asking me to combine my three test cases into one, which naturally makes the test itself more complex and longer to read. Which do you prefer?

Show 8 quoted lines
> 
> So in a sense, I guess I would even suggest to move as much of the setup
> for your test cases outside, preferably into an initial `setup` test case
> that initializes the minimal scenario required by the regression test
> case.
> 
> Thanks,
> Dscho
Johannes Schindelin· Jul 4, 2019, 08:29 UTC · re: Emily Shaffer · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Hi Emily,
On Wed, 3 Jul 2019, Emily Shaffer wrote:
Show 28 quoted lines
> On Wed, Jul 03, 2019 at 09:41:46PM +0200, Johannes Schindelin wrote:
>
> > On Wed, 3 Jul 2019, Emily Shaffer wrote:
> >
> > > > > +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> > > > > +	test_commit atomic1 &&
> > > > > +	test_commit atomic2 &&
> > > > > +	git push "$up" master &&
> > > >
> > > > It would be more succinct to do a `git clone --bare . "$d"` here,
> > > > instead of a `git init --bare` and a `git push` no?
> > >
> > > I'm not sure I would say "more succinct." This leaves the test with the
> > > same number of lines,
> >
> > No, it does not, as `git clone --bare . "$d"` does _both_ the initializing
> > and the object transfer.
> >
> > It only saves one line, of course, but do keep in mind that anybody
> > running into any kind of regression with your test case needs to
> > understand what it does. And from experience I can tell you that reading
> > any test case longer than 5 lines is quite annoying when you actually
> > only care about fixing the regression, and not so much about the wonderful
> > story the test case tells.
>
> I suppose I'm confused, then, as I understood you were asking me to
> combine my three test cases into one, which naturally makes the test
> itself more complex and longer to read. Which do you prefer?

If I were tasked with developing this further, I would try to move as much of the setup into the initial test case (if there is already a `setup` test case; otherwise I would create one). In fact, I would try to reuse as much of the existing setup test case as possible, and only add commands if really necessary. Then, I would try to combine the three test cases in the patch into a single one, structuring it by white space and using comments to clarify what is happening.

In my mind, even just adding an empty line before the comments like "Make master incompatible with up/master" would make it much easier for me to read, were I to analyze a test breakage.

I have to admit that I have a hard time understanding what the intention of those three test cases is because I get confused: where does the set-up end, where is the code that is expected to fail, where are the expectations tested?

Also, I get confused by how similar the test cases look, and have a hard time discerning what the differences are (i.e. what are the interesting bits, where the entropy comes from).

I could imagine that I would have had an easier time reading something like this:

test_expect_success 'push --atomic' '
	: set up two branches, one which will require a force push &&
	git checkout -b fast-forwarding master &&
	test_commit push-atomic &&
	git checkout -b non-fast-forwarding &&
	: now, initialize the bare repository to push to &&
	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic.git &&
	git clone --bare . $d &&
	: modify the two branches and create a third one &&
	git reset --hard HEAD^ &&
	git checkout fast-forwarding &&
	test_commit no-need-to-force &&
	git branch new-branch &&
	: now the atomic push must fail &&
	test_must_fail git push --atomic "$HTTPD_URL"/smart/atomic.git \
		fast-forwarding non-fast-forwarding new-branch 2>err &&
	: verify that new-branch was not pushed &&
	test_must_fail git -C $d rev-parse --verify refs/heads/new-branch &&
	: fast-forwarding should be mentioned even if it would have been OK &&
	grep fast-forwarding err
'

Of course, everybody has their preferences, and their personal style. I do not want you to imitate my style just to "pacify" me. That's not the point of this example.

The point is that I need some structure to walk along, especially when I am a bit annoyed at a test case that shows that I introduced a regression and all I want is to understand as quickly as possible what I did wrong again so that I can fix it and move on.

The point is that I want a regression test case to _not_ distract me from the essential part, ideally I should be able to ignore all the set-up code and deduce from the command-line of the failing command what is going on.

For example, if the test case fails in the line `grep fast-forwarding err` above, that command-line alone does not tell me anything, it just frustrates me. If there is a comment above that line (which is ideally part of the `-x` trace, that's why I used `:` instead of `#`) that states the intention in what I consider to be clear, simple English, it is a lot easier to figure out what the heck is going wrong.

Of course, it would be even better if we had a function, say, `must_contain` that runs that `grep` and shows the file contents and the regular expression if that `grep` failed. That's of course outside of the concern of your patch. But this idea illustrates what I want in a regression test case: I want it to _help_ me figure out things when it breaks.

Ciao, Dscho

P.S.: Please note that the many test cases _I_ introduced into Git's test suite mostly do not conform to what I wrote above. I learned _quite_ a lot of how I want regression test cases to look like in the past six months, not from writing them, but from analyzing literally hundreds of Azure Pipelines builds. And your question forced me to think about my learnings to formulate the above (hopefully clear) explanation of what I want in a regression test, so: thank you.

Emily Shaffer· Jul 9, 2019, 20:23 UTC · re: Johannes Schindelin · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Firstly, sorry for the delay, I wasn't working for national holiday from the 4th til yesterday.

Show 50 quoted lines
> If I were tasked with developing this further, I would try to move as much
> of the setup into the initial test case (if there is already a `setup`
> test case; otherwise I would create one). In fact, I would try to reuse as
> much of the existing setup test case as possible, and only add commands if
> really necessary. Then, I would try to combine the three test cases in the
> patch into a single one, structuring it by white space and using comments
> to clarify what is happening.
> 
> In my mind, even just adding an empty line before the comments like "Make
> master incompatible with up/master" would make it much easier for me to
> read, were I to analyze a test breakage.
> 
> I have to admit that I have a hard time understanding what the intention
> of those three test cases is because I get confused: where does the set-up
> end, where is the code that is expected to fail, where are the
> expectations tested?
> 
> Also, I get confused by how similar the test cases look, and have a hard
> time discerning what the differences are (i.e. what are the interesting
> bits, where the entropy comes from).
> 
> I could imagine that I would have had an easier time reading something
> like this:
> 
> test_expect_success 'push --atomic' '
> 	: set up two branches, one which will require a force push &&
> 	git checkout -b fast-forwarding master &&
> 	test_commit push-atomic &&
> 	git checkout -b non-fast-forwarding &&
> 
> 	: now, initialize the bare repository to push to &&
> 	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic.git &&
> 	git clone --bare . $d &&
> 
> 	: modify the two branches and create a third one &&
> 	git reset --hard HEAD^ &&
> 	git checkout fast-forwarding &&
> 	test_commit no-need-to-force &&
> 	git branch new-branch &&
> 
> 	: now the atomic push must fail &&
> 	test_must_fail git push --atomic "$HTTPD_URL"/smart/atomic.git \
> 		fast-forwarding non-fast-forwarding new-branch 2>err &&
> 
> 	: verify that new-branch was not pushed &&
> 	test_must_fail git -C $d rev-parse --verify refs/heads/new-branch &&
> 
> 	: fast-forwarding should be mentioned even if it would have been OK &&
> 	grep fast-forwarding err
> '

After I read your initial comment this is close to what my rewrite started to look like; I suppose we're only the same page after all :)

Show 39 quoted lines
> 
> Of course, everybody has their preferences, and their personal style. I do
> not want you to imitate my style just to "pacify" me. That's not the point
> of this example.
> 
> The point is that I need some structure to walk along, especially when I
> am a bit annoyed at a test case that shows that I introduced a regression
> and all I want is to understand as quickly as possible what I did wrong
> again so that I can fix it and move on.
> 
> The point is that I want a regression test case to _not_ distract me from
> the essential part, ideally I should be able to ignore all the set-up code
> and deduce from the command-line of the failing command what is going on.
> 
> For example, if the test case fails in the line `grep fast-forwarding err`
> above, that command-line alone does not tell me anything, it just
> frustrates me. If there is a comment above that line (which is ideally
> part of the `-x` trace, that's why I used `:` instead of `#`) that states
> the intention in what I consider to be clear, simple English, it is a lot
> easier to figure out what the heck is going wrong.
> 
> Of course, it would be even better if we had a function, say,
> `must_contain` that runs that `grep` and shows the file contents and the
> regular expression if that `grep` failed. That's of course outside of the
> concern of your patch. But this idea illustrates what I want in a
> regression test case: I want it to _help_ me figure out things when it
> breaks.
> 
> Ciao,
> Dscho
> 
> P.S.: Please note that the many test cases _I_ introduced into Git's test
> suite mostly do not conform to what I wrote above. I learned _quite_ a
> lot of how I want regression test cases to look like in the past six
> months, not from writing them, but from analyzing literally hundreds of
> Azure Pipelines builds. And your question forced me to think about my
> learnings to formulate the above (hopefully clear) explanation of what I
> want in a regression test, so: thank you.
> 

This writeup is great, and I think your example is very clear and readable. I'll push a reroll with my similar-looking reformat soon. Thanks, Johannes.

 - Emily
Junio C Hamano· Jul 2, 2019, 19:06 UTC · re: Emily Shaffer · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Emily Shaffer <emilyshaffer@google.com> writes:
Show 21 quoted lines
> diff --git a/transport-helper.c b/transport-helper.c
> index c7e17ec9cb..6b05a88faf 100644
> --- a/transport-helper.c
> +++ b/transport-helper.c
> @@ -853,6 +853,7 @@ static int push_refs_with_push(struct transport *transport,
>  {
>  	int force_all = flags & TRANSPORT_PUSH_FORCE;
>  	int mirror = flags & TRANSPORT_PUSH_MIRROR;
> +	int atomic = flags & TRANSPORT_PUSH_ATOMIC;
>  	struct helper_data *data = transport->data;
>  	struct strbuf buf = STRBUF_INIT;
>  	struct ref *ref;
> @@ -872,6 +873,11 @@ static int push_refs_with_push(struct transport *transport,
>  		case REF_STATUS_REJECT_NONFASTFORWARD:
>  		case REF_STATUS_REJECT_STALE:
>  		case REF_STATUS_REJECT_ALREADY_EXISTS:
> +			if (atomic) {
> +				string_list_clear(&cas_options, 0);
> +				return 0;
> +			} else
> +				continue;
Ah, this looks vaguely familiar.  Thanks for resurrecting the topic.

The clearing is merely to avoid leaks, and the primary change to the function is to immediately return 0.

Show 10 quoted lines
>  		case REF_STATUS_UPTODATE:
>  			continue;
>  		default:
> diff --git a/transport.c b/transport.c
> index f1fcd2c4b0..f4d6b38f9d 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -1226,10 +1226,23 @@ int transport_push(struct repository *r,
>  		err = push_had_errors(remote_refs);
>  		ret = push_ret | err;

Here, before reporting the push result, when we are doing ATOMIC, we tweak the result we are going to report to atomic-push-failed.

Show 11 quoted lines
> +		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
> +			for (struct ref *it = remote_refs; it; it = it->next)
> +				switch (it->status) {
> +				case REF_STATUS_NONE:
> +				case REF_STATUS_UPTODATE:
> +				case REF_STATUS_OK:
> +					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
> +				default:
> +					continue;
> +				}
> +		}

This roughly corresponds to what send-pack.c::atomic_push_failure() does. Here, we avoid overwriting a status that already signals a failure. The list of "good" statuses used here match what is used at the end of send_pack.c::send_pack(), which decides the final outcome of "git push" for the native transport.

Looks good.

By the way, I rearranged the patch as I happen to agree with Dscho that the additional {} was unwarranted and made it harder to review. It is clear that we need to tweak the status before reporting.

Show 8 quoted lines
>
> 		if (!quiet || err)
>  			transport_print_push_status(transport->url, remote_refs,
>  					verbose | porcelain, porcelain,
>  					reject_reasons);
>  
>  		if (flags & TRANSPORT_PUSH_SET_UPSTREAM)
>  			set_upstreams(transport, remote_refs, pretend);
Junio C Hamano· Jul 2, 2019, 20:16 UTC · re: Emily Shaffer · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Emily Shaffer <emilyshaffer@google.com> writes:
Show 11 quoted lines
> +		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
> +			for (struct ref *it = remote_refs; it; it = it->next)
> +				switch (it->status) {
> +				case REF_STATUS_NONE:
> +				case REF_STATUS_UPTODATE:
> +				case REF_STATUS_OK:
> +					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
> +				default:
> +					continue;
> +				}
> +		}
Let's write this more like so
		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
			for (struct ref *it = remote_refs; it; it = it->next)
				switch (it->status) {
				case REF_STATUS_NONE:
				case REF_STATUS_UPTODATE:
				case REF_STATUS_OK:
					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
					break;
				default:
					break;
				}
		}
to prevent compilers from giving "implicit fallthru" warnings.
Emily Shaffer· Jul 3, 2019, 00:09 UTC · re: Junio C Hamano · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

On Tue, Jul 02, 2019 at 01:16:46PM -0700, Junio C Hamano wrote:
Show 31 quoted lines
> Emily Shaffer <emilyshaffer@google.com> writes:
> 
> > +		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
> > +			for (struct ref *it = remote_refs; it; it = it->next)
> > +				switch (it->status) {
> > +				case REF_STATUS_NONE:
> > +				case REF_STATUS_UPTODATE:
> > +				case REF_STATUS_OK:
> > +					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
> > +				default:
> > +					continue;
> > +				}
> > +		}
> 
> 
> Let's write this more like so
> 
> 		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
> 			for (struct ref *it = remote_refs; it; it = it->next)
> 				switch (it->status) {
> 				case REF_STATUS_NONE:
> 				case REF_STATUS_UPTODATE:
> 				case REF_STATUS_OK:
> 					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
> 					break;
> 				default:
> 					break;
> 				}
> 		}
> 
> to prevent compilers from giving "implicit fallthru" warnings.
Done, thanks.
Junio C Hamano· Jul 2, 2019, 21:37 UTC · re: Emily Shaffer · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

Emily Shaffer <emilyshaffer@google.com> writes:
Show 47 quoted lines
> Teach transport-helper how to notice if skipping a ref during push would
> violate atomicity on the client side. We notice that a ref would be
> rejected, and choose not to send it, but don't notice that if the client
> has asked for --atomic we are violating atomicity if all the other
> pushes we are sending would succeed. Asking the server end to uphold
> atomicity wouldn't work here as the server doesn't have any idea that we
> tried to update a ref that's broken.
>
> The added test-case is a succinct way to reproduce this issue that fails
> today. The same steps work fine when we aren't using a transport-helper
> to get to the upstream, i.e. when we've added a local repository as a
> remote:
>
>   git remote add ~/upstream upstream
>
> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> ---
>  t/t5541-http-push-smart.sh | 58 ++++++++++++++++++++++++++++++++++++++
>  transport-helper.c         |  6 ++++
>  transport.c                | 15 +++++++++-
>  3 files changed, 78 insertions(+), 1 deletion(-)
>
> diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
> index 8ef8763e06..b57f6d480f 100755
> --- a/t/t5541-http-push-smart.sh
> +++ b/t/t5541-http-push-smart.sh
> @@ -177,6 +177,64 @@ test_expect_success 'push (chunked)' '
>  	 test $HEAD = $(git rev-parse --verify HEAD))
>  '
>  
> +test_expect_success 'push --atomic also prevents branch creation' '
> +	# Make up/master
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
> +	git init --bare "$d" &&
> +	git --git-dir="$d" config http.receivepack true &&
> +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> +	test_commit atomic1 &&
> +	test_commit atomic2 &&
> +	git push "$up" master &&
> +	# Make master incompatible with up/master
> +	git reset --hard HEAD^ &&
> +	# Add a new branch
> +	git branch atomic &&
> +	# --atomic should roll back creation of up/atomic
> +	test_must_fail git push --atomic "$up" master atomic &&
> +	git ls-remote "$up" >up-remotes &&
> +	test_must_fail grep atomic up-remotes

Don't use test_must_fail on non-git things. We are not in the business of catching segfaulting system programs.

Show 19 quoted lines
> +'
> +
> +test_expect_success 'push --atomic shows all failed refs' '
> +	# Make up/master, up/allrefs
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
> +	git init --bare "$d" &&
> +	git --git-dir="$d" config http.receivepack true &&
> +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
> +	test_commit allrefs1 &&
> +	test_commit allrefs2 &&
> +	git branch allrefs &&
> +	git push "$up" master allrefs &&
> +	# Make master and allrefs incompatible with up/master, up/allrefs
> +	git checkout allrefs &&
> +	git reset --hard HEAD^ &&
> +	git checkout master &&
> +	git reset --hard HEAD^ &&
> +	# --atomic should complain about both master and allrefs
> +	test_must_fail git push --atomic "$up" master allrefs >&output &&

Don't rely on ">&output", which is an unnecessary bash-ism here. It breaks test run under shells like dash.

	>output 2>&1
should be OK.
Show 18 quoted lines
> +	grep master output &&
> +	grep allrefs output
> +'
> +
> +test_expect_success 'push --atomic indicates collateral failures' '
> +	# Make up/master, up/collateral
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-collateral.git &&
> +	git init --bare "$d" &&
> +	git --git-dir="$d" config http.receivepack true &&
> +	up="$HTTPD_URL"/smart/atomic-collateral.git &&
> +	test_commit collateral1 &&
> +	test_commit collateral2 &&
> +	git branch collateral &&
> +	git push "$up" master collateral &&
> +	# Make master incompatible with up/master
> +	git reset --hard HEAD^ &&
> +	# --atomic should mention collateral was OK but failed anyway
> +	test_must_fail git push --atomic "$up" master collateral >&output &&
Ditto.
> +	grep "master -> master" output &&
> +	grep "collateral -> collateral" output
> +'
> +
Emily Shaffer· Jul 3, 2019, 00:08 UTC · re: Junio C Hamano · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

On Tue, Jul 02, 2019 at 02:37:42PM -0700, Junio C Hamano wrote:
Show 21 quoted lines
> >  
> > +test_expect_success 'push --atomic also prevents branch creation' '
> > +	# Make up/master
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
> > +	git init --bare "$d" &&
> > +	git --git-dir="$d" config http.receivepack true &&
> > +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> > +	test_commit atomic1 &&
> > +	test_commit atomic2 &&
> > +	git push "$up" master &&
> > +	# Make master incompatible with up/master
> > +	git reset --hard HEAD^ &&
> > +	# Add a new branch
> > +	git branch atomic &&
> > +	# --atomic should roll back creation of up/atomic
> > +	test_must_fail git push --atomic "$up" master atomic &&
> > +	git ls-remote "$up" >up-remotes &&
> > +	test_must_fail grep atomic up-remotes
> 
> Don't use test_must_fail on non-git things.  We are not in the
> business of catching segfaulting system programs.
Done:
  ! grep atomic up-remotes
Show 27 quoted lines
> 
> > +'
> > +
> > +test_expect_success 'push --atomic shows all failed refs' '
> > +	# Make up/master, up/allrefs
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
> > +	git init --bare "$d" &&
> > +	git --git-dir="$d" config http.receivepack true &&
> > +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
> > +	test_commit allrefs1 &&
> > +	test_commit allrefs2 &&
> > +	git branch allrefs &&
> > +	git push "$up" master allrefs &&
> > +	# Make master and allrefs incompatible with up/master, up/allrefs
> > +	git checkout allrefs &&
> > +	git reset --hard HEAD^ &&
> > +	git checkout master &&
> > +	git reset --hard HEAD^ &&
> > +	# --atomic should complain about both master and allrefs
> > +	test_must_fail git push --atomic "$up" master allrefs >&output &&
> 
> Don't rely on ">&output", which is an unnecessary bash-ism here.  It
> breaks test run under shells like dash.
> 
> 	>output 2>&1
> 
> should be OK.
Done.
Show 21 quoted lines
> 
> > +	grep master output &&
> > +	grep allrefs output
> > +'
> > +
> > +test_expect_success 'push --atomic indicates collateral failures' '
> > +	# Make up/master, up/collateral
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-collateral.git &&
> > +	git init --bare "$d" &&
> > +	git --git-dir="$d" config http.receivepack true &&
> > +	up="$HTTPD_URL"/smart/atomic-collateral.git &&
> > +	test_commit collateral1 &&
> > +	test_commit collateral2 &&
> > +	git branch collateral &&
> > +	git push "$up" master collateral &&
> > +	# Make master incompatible with up/master
> > +	git reset --hard HEAD^ &&
> > +	# --atomic should mention collateral was OK but failed anyway
> > +	test_must_fail git push --atomic "$up" master collateral >&output &&
> 
> Ditto.
Done.
 - Emily
SZEDER Gábor· Jul 3, 2019, 09:10 UTC · re: Junio C Hamano · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

On Tue, Jul 02, 2019 at 02:37:42PM -0700, Junio C Hamano wrote:
Show 24 quoted lines
> > +test_expect_success 'push --atomic shows all failed refs' '
> > +	# Make up/master, up/allrefs
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
> > +	git init --bare "$d" &&
> > +	git --git-dir="$d" config http.receivepack true &&
> > +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
> > +	test_commit allrefs1 &&
> > +	test_commit allrefs2 &&
> > +	git branch allrefs &&
> > +	git push "$up" master allrefs &&
> > +	# Make master and allrefs incompatible with up/master, up/allrefs
> > +	git checkout allrefs &&
> > +	git reset --hard HEAD^ &&
> > +	git checkout master &&
> > +	git reset --hard HEAD^ &&
> > +	# --atomic should complain about both master and allrefs
> > +	test_must_fail git push --atomic "$up" master allrefs >&output &&
> 
> Don't rely on ">&output", which is an unnecessary bash-ism here.  It
> breaks test run under shells like dash.
> 
> 	>output 2>&1
> 
> should be OK.

'2>output' would be a tad better, because those refs should be printed to stderr.

Junio C Hamano· Jul 3, 2019, 18:13 UTC · re: SZEDER Gábor · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

SZEDER Gábor <szeder.dev@gmail.com> writes:
Show 28 quoted lines
> On Tue, Jul 02, 2019 at 02:37:42PM -0700, Junio C Hamano wrote:
>> > +test_expect_success 'push --atomic shows all failed refs' '
>> > +	# Make up/master, up/allrefs
>> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
>> > +	git init --bare "$d" &&
>> > +	git --git-dir="$d" config http.receivepack true &&
>> > +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
>> > +	test_commit allrefs1 &&
>> > +	test_commit allrefs2 &&
>> > +	git branch allrefs &&
>> > +	git push "$up" master allrefs &&
>> > +	# Make master and allrefs incompatible with up/master, up/allrefs
>> > +	git checkout allrefs &&
>> > +	git reset --hard HEAD^ &&
>> > +	git checkout master &&
>> > +	git reset --hard HEAD^ &&
>> > +	# --atomic should complain about both master and allrefs
>> > +	test_must_fail git push --atomic "$up" master allrefs >&output &&
>> 
>> Don't rely on ">&output", which is an unnecessary bash-ism here.  It
>> breaks test run under shells like dash.
>> 
>> 	>output 2>&1
>> 
>> should be OK.
>
> '2>output' would be a tad better, because those refs should be printed
> to stderr.

Yeah; there are many existing uses of ">output 2>&1" in the same script and I was following the suit. There also are 2>err and I agree that it is more appropriate in this case.

Emily Shaffer· Jul 3, 2019, 18:58 UTC · re: Junio C Hamano · lore

Re: [PATCH] transport-helper: enforce atomic in push_refs_with_push

On Wed, Jul 03, 2019 at 11:13:45AM -0700, Junio C Hamano wrote:
Show 34 quoted lines
> SZEDER Gábor <szeder.dev@gmail.com> writes:
> 
> > On Tue, Jul 02, 2019 at 02:37:42PM -0700, Junio C Hamano wrote:
> >> > +test_expect_success 'push --atomic shows all failed refs' '
> >> > +	# Make up/master, up/allrefs
> >> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-failed-refs.git &&
> >> > +	git init --bare "$d" &&
> >> > +	git --git-dir="$d" config http.receivepack true &&
> >> > +	up="$HTTPD_URL"/smart/atomic-failed-refs.git &&
> >> > +	test_commit allrefs1 &&
> >> > +	test_commit allrefs2 &&
> >> > +	git branch allrefs &&
> >> > +	git push "$up" master allrefs &&
> >> > +	# Make master and allrefs incompatible with up/master, up/allrefs
> >> > +	git checkout allrefs &&
> >> > +	git reset --hard HEAD^ &&
> >> > +	git checkout master &&
> >> > +	git reset --hard HEAD^ &&
> >> > +	# --atomic should complain about both master and allrefs
> >> > +	test_must_fail git push --atomic "$up" master allrefs >&output &&
> >> 
> >> Don't rely on ">&output", which is an unnecessary bash-ism here.  It
> >> breaks test run under shells like dash.
> >> 
> >> 	>output 2>&1
> >> 
> >> should be OK.
> >
> > '2>output' would be a tad better, because those refs should be printed
> > to stderr.
> 
> Yeah; there are many existing uses of ">output 2>&1" in the same
> script and I was following the suit.  There also are 2>err and I
> agree that it is more appropriate in this case.
Oh, this is a good point. I'll change it, thanks both.
Emily Shaffer· Jul 9, 2019, 21:10 UTC · re: Emily Shaffer · lore

[PATCH v2] transport-helper: enforce atomic in push_refs_with_push

Teach transport-helper how to notice if skipping a ref during push would violate atomicity on the client side. We notice that a ref would be rejected, and choose not to send it, but don't notice that if the client has asked for --atomic we are violating atomicity if all the other pushes we are sending would succeed. Asking the server end to uphold atomicity wouldn't work here as the server doesn't have any idea that we tried to update a ref that's broken.

The added test-case is a succinct way to reproduce this issue that fails today. The same steps work fine when we aren't using a transport-helper to get to the upstream, i.e. when we've added a local repository as a remote:

  git remote add ~/upstream upstream
Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
---
Since v2, I combined the test cases into one. Unfortunately the new test
case is checking a few things at once, but I hope the comments make it
clear enough. Johannes, I still left the setup at the top; for me in the
tradeoff between "one less line" and "setup happens first", the latter
wins.

I also removed the "try pushing two bad branches at the same time" case; I don't think it's really covered by atomicity. So now we check that if we push one bad ref, collateral damage in the form of 1) a new branch and 2) a perfectly good push of another branch are both rejected and reported correctly.

Also, got rid of the accidental new curly braces around unrelated part of the code.

Thanks all.
 - Emily
 t/t5541-http-push-smart.sh | 39 ++++++++++++++++++++++++++++++++++++++
 transport-helper.c         |  6 ++++++
 transport.c                | 13 +++++++++++++
 3 files changed, 58 insertions(+)
Show changes to 3 files +58 −0

t/t5541-http-push-smart.sh, transport-helper.c, transport.c

diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 8ef8763e06..7f9ae1ef3f 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -177,6 +177,45 @@ test_expect_success 'push (chunked)' '
 	 test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
+	# Setup upstream repo - empty for now
+	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
+	git init --bare "$d" &&
+	test_config -C "$d" http.receivepack true &&
+	up="$HTTPD_URL"/smart/atomic-branches.git &&
+
+	# Tell up about two branches for now
+	test_commit atomic1 &&
+	test_commit atomic2 &&
+	git branch collateral &&
+	git push "$up" master collateral &&
+
+	# collateral is a valid push, but should be failed by atomic push
+	git checkout collateral &&
+	test_commit collateral1 &&
+
+	# Make master incompatible with upstream to provoke atomic
+	git checkout master &&
+	git reset --hard HEAD^ &&
+
+	# Add a new branch which should be failed by atomic push. This is a
+	# regression case.
+	git branch atomic &&
+
+	# --atomic should cause entire push to be rejected
+	test_must_fail git push --atomic "$up" master atomic collateral 2>output &&
+
+	# the new branch should not have been created upstream
+	test_must_fail git -C "$d" rev-parse refs/heads/atomic &&
+
+	# the failed refs should be indicated
+	grep "master -> master" output | grep rejected &&
+
+	# the collateral failure refs should be indicated
+	grep "atomic -> atomic" output | grep "atomic push failed" &&
+	grep "collateral -> collateral" output | grep "atomic push failed"
+'
+
 test_expect_success 'push --all can push to empty repo' '
 	d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
 	git init --bare "$d" &&
diff --git a/transport-helper.c b/transport-helper.c
index c7e17ec9cb..6b05a88faf 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -853,6 +853,7 @@ static int push_refs_with_push(struct transport *transport,
 {
 	int force_all = flags & TRANSPORT_PUSH_FORCE;
 	int mirror = flags & TRANSPORT_PUSH_MIRROR;
+	int atomic = flags & TRANSPORT_PUSH_ATOMIC;
 	struct helper_data *data = transport->data;
 	struct strbuf buf = STRBUF_INIT;
 	struct ref *ref;
@@ -872,6 +873,11 @@ static int push_refs_with_push(struct transport *transport,
 		case REF_STATUS_REJECT_NONFASTFORWARD:
 		case REF_STATUS_REJECT_STALE:
 		case REF_STATUS_REJECT_ALREADY_EXISTS:
+			if (atomic) {
+				string_list_clear(&cas_options, 0);
+				return 0;
+			} else
+				continue;
 		case REF_STATUS_UPTODATE:
 			continue;
 		default:
diff --git a/transport.c b/transport.c
index f1fcd2c4b0..d768bc275e 100644
--- a/transport.c
+++ b/transport.c
@@ -1226,6 +1226,19 @@ int transport_push(struct repository *r,
 		err = push_had_errors(remote_refs);
 		ret = push_ret | err;
 
+		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
+			for (struct ref *it = remote_refs; it; it = it->next)
+				switch (it->status) {
+				case REF_STATUS_NONE:
+				case REF_STATUS_UPTODATE:
+				case REF_STATUS_OK:
+					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+					break;
+				default:
+					break;
+				}
+		}
+
 		if (!quiet || err)
 			transport_print_push_status(transport->url, remote_refs,
 					verbose | porcelain, porcelain,
-- 
2.22.0.410.gd8fdbe21b5-goog
Junio C Hamano· Jul 10, 2019, 17:44 UTC · re: Emily Shaffer · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

Emily Shaffer <emilyshaffer@google.com> writes:
Show 8 quoted lines
> +test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
> +	# Setup upstream repo - empty for now
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
> +	git init --bare "$d" &&
> +	test_config -C "$d" http.receivepack true &&
> +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> +
> +	# Tell up about two branches for now
-ECANTPARSE "Tell up" part.
> +	test_commit atomic1 &&
> +	test_commit atomic2 &&
> +	git branch collateral &&
> +	git push "$up" master collateral &&

OK, so an initially empty directory $d that appears to network clients as $up now has two branches, 'master' and 'collateral', both pointing at the same history that ends with two commits, atomic2 whose parent is atomic1.

Show 7 quoted lines
> +	# collateral is a valid push, but should be failed by atomic push
> +	git checkout collateral &&
> +	test_commit collateral1 &&
> +
> +	# Make master incompatible with upstream to provoke atomic
> +	git checkout master &&
> +	git reset --hard HEAD^ &&
collateral grows, master rewinds.
> +	# Add a new branch which should be failed by atomic push. This is a
> +	# regression case.
> +	git branch atomic &&
Another branch atomic is added
> +	# --atomic should cause entire push to be rejected
> +	test_must_fail git push --atomic "$up" master atomic collateral 2>output &&

Attempt to push all three: collateral alone would be OK, so is atomic, but because master rewinds, we expect none of the three to go through.

> +	# the new branch should not have been created upstream
> +	test_must_fail git -C "$d" rev-parse refs/heads/atomic &&

The new branch should not have been created; if this rev-parse succeeded, it would be a bug.

Up to point, I have no possible improvements to offer ;-) Very well done.

> +	# the failed refs should be indicated
> +	grep "master -> master" output | grep rejected &&

I'd rather see the effect, i.e. what the command did that can be observed externally, than the report, i.e. what the command claims to have done, if it is equally straight-forward to verify either.

That can be done by making sure that the output from "git -C "$d" rev-parse refs/heads/master" match output from "git rev-parse atomic2", no? That ensures 'master' in the receiving end stayed the same.

> +	# the collateral failure refs should be indicated
> +	grep "atomic -> atomic" output | grep "atomic push failed" &&
> +	grep "collateral -> collateral" output | grep "atomic push failed"
Likewise for the other two.  
FWIW, these three can further lose a process each, i.e.
	grep "^ ! .*rejected.* master -> master" output

even if we for some reason do not want to check the effect and take the claim by the command being tested at the face value (which I do not think is a good idea).

Thanks.
Junio C Hamano· Jul 10, 2019, 17:53 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

Junio C Hamano <gitster@pobox.com> writes:
Show 5 quoted lines
>> +	# the new branch should not have been created upstream
>> +	test_must_fail git -C "$d" rev-parse refs/heads/atomic &&
>
> The new branch should not have been created; if this rev-parse
> succeeded, it would be a bug.

One thing I forgot. If refs/heads/atomic did not exist, but say refs/tags/refs/heads/atomic did, the rev-parse would succeed, which is a rather unfortunate source of confusion.

Of course, we know we have never touched "$d" to cause such a funny tag, so rev-parse is good enough in practice, but

    git -C "$d" show-ref --verify refs/heads/atomic

would not dwim (its --verify mode was invented specifically for rectifying this issue with rev-parse, if I recall correctly), and is more appropriate best-practice version to write here, especially if we anticipate that future developers and Git users would treat this line as an example to mimic.

> Up to point, I have no possible improvements to offer ;-)
> Very well done.
So, I lied, but still the tests looked quite well done.
Emily Shaffer· Jul 11, 2019, 21:14 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Wed, Jul 10, 2019 at 10:53:30AM -0700, Junio C Hamano wrote:
Show 27 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
> 
> >> +	# the new branch should not have been created upstream
> >> +	test_must_fail git -C "$d" rev-parse refs/heads/atomic &&
> >
> > The new branch should not have been created; if this rev-parse
> > succeeded, it would be a bug.
> 
> One thing I forgot.  If refs/heads/atomic did not exist, but say
> refs/tags/refs/heads/atomic did, the rev-parse would succeed, which
> is a rather unfortunate source of confusion.
> 
> Of course, we know we have never touched "$d" to cause such a funny
> tag, so rev-parse is good enough in practice, but
> 
>     git -C "$d" show-ref --verify refs/heads/atomic
> 
> would not dwim (its --verify mode was invented specifically for
> rectifying this issue with rev-parse, if I recall correctly), and is
> more appropriate best-practice version to write here, especially if
> we anticipate that future developers and Git users would treat this
> line as an example to mimic.
> 
> > Up to point, I have no possible improvements to offer ;-)
> > Very well done.
> 
> So, I lied, but still the tests looked quite well done.
Oh, that's very interesting! Thanks for pointing it out. :)
Reroll coming in a few. Thanks, Junio.
 - Emily
Emily Shaffer· Jul 11, 2019, 20:57 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Wed, Jul 10, 2019 at 10:44:22AM -0700, Junio C Hamano wrote:
Show 12 quoted lines
> Emily Shaffer <emilyshaffer@google.com> writes:
> 
> > +test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
> > +	# Setup upstream repo - empty for now
> > +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
> > +	git init --bare "$d" &&
> > +	test_config -C "$d" http.receivepack true &&
> > +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> > +
> > +	# Tell up about two branches for now
> 
> -ECANTPARSE "Tell up" part.
s/up/"$&"/ - thanks.
[snip]
> Up to point, I have no possible improvements to offer ;-)
> Very well done.
This is nice to hear, thanks! :)
Show 6 quoted lines
> > +	# the failed refs should be indicated
> > +	grep "master -> master" output | grep rejected &&
> 
> I'd rather see the effect, i.e. what the command did that can be
> observed externally, than the report, i.e. what the command claims
> to have done, if it is equally straight-forward to verify either.

Hmm. I'd like to argue that part of the requirement is to show the user what happened; for example, in an earlier iteration I was not successfully reporting the "collateral damage" refs to the user, even though they were not being pushed. To that end, I'd rather check both.

Show 5 quoted lines
> 
> That can be done by making sure that the output from "git -C "$d"
> rev-parse refs/heads/master" match output from "git rev-parse
> atomic2", no?  That ensures 'master' in the receiving end stayed the
> same.
Sure, I agree.
Show 14 quoted lines
> 
> > +	# the collateral failure refs should be indicated
> > +	grep "atomic -> atomic" output | grep "atomic push failed" &&
> > +	grep "collateral -> collateral" output | grep "atomic push failed"
> 
> Likewise for the other two.  
> 
> FWIW, these three can further lose a process each, i.e.
> 
> 	grep "^ ! .*rejected.* master -> master" output
> 
> even if we for some reason do not want to check the effect and take
> the claim by the command being tested at the face value (which I do
> not think is a good idea).

Will swap, wasn't sure on preference between regex or process count. Thanks.

 - Emily
Junio C Hamano· Jul 11, 2019, 21:13 UTC · re: Emily Shaffer · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

Emily Shaffer <emilyshaffer@google.com> writes:
> Hmm. I'd like to argue that part of the requirement is to show the user
> what happened; for example, in an earlier iteration I was not
> successfully reporting the "collateral damage" refs to the user, even
> though they were not being pushed. To that end, I'd rather check both.

If we can afford to check both, making sure that we behave correctly and report what we did correctly would of course be the best.

;-)
Emily Shaffer· Jul 11, 2019, 21:19 UTC · re: Emily Shaffer · lore

[PATCH v3] transport-helper: enforce atomic in push_refs_with_push

Teach transport-helper how to notice if skipping a ref during push would violate atomicity on the client side. We notice that a ref would be rejected, and choose not to send it, but don't notice that if the client has asked for --atomic we are violating atomicity if all the other pushes we are sending would succeed. Asking the server end to uphold atomicity wouldn't work here as the server doesn't have any idea that we tried to update a ref that's broken.

The added test-case is a succinct way to reproduce this issue that fails today. The same steps work fine when we aren't using a transport-helper to get to the upstream, i.e. when we've added a local repository as a remote:

  git remote add ~/upstream upstream
Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
---
Since the previous version, made a couple changes based on Junio's
comments on the testcase.
- Use show-ref instead of rev-parse to avoid a corner case where a tag
  is named the same thing as the branch we hope not to create
- Reduce number of grep processes by using a longer regex instead
- Check the real contents of the upstream repo instead of just relying
  on the client side's reporting
  - The check on the client side remains, however, to ensure the user is
    made aware of why their perfectly good ref failed to push.
Thanks, all.
 t/t5541-http-push-smart.sh | 49 ++++++++++++++++++++++++++++++++++++++
 transport-helper.c         |  6 +++++
 transport.c                | 13 ++++++++++
 3 files changed, 68 insertions(+)
Show changes to 3 files +68 −0

t/t5541-http-push-smart.sh, transport-helper.c, transport.c

diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 8ef8763e06..92bac43257 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -177,6 +177,55 @@ test_expect_success 'push (chunked)' '
 	 test $HEAD = $(git rev-parse --verify HEAD))
 '
 
+test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
+	# Setup upstream repo - empty for now
+	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
+	git init --bare "$d" &&
+	test_config -C "$d" http.receivepack true &&
+	up="$HTTPD_URL"/smart/atomic-branches.git &&
+
+	# Tell "$up" about two branches for now
+	test_commit atomic1 &&
+	test_commit atomic2 &&
+	git branch collateral &&
+	git push "$up" master collateral &&
+
+	# collateral is a valid push, but should be failed by atomic push
+	git checkout collateral &&
+	test_commit collateral1 &&
+
+	# Make master incompatible with upstream to provoke atomic
+	git checkout master &&
+	git reset --hard HEAD^ &&
+
+	# Add a new branch which should be failed by atomic push. This is a
+	# regression case.
+	git branch atomic &&
+
+	# --atomic should cause entire push to be rejected
+	test_must_fail git push --atomic "$up" master atomic collateral 2>output &&
+
+	# the new branch should not have been created upstream
+	test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
+
+	# upstream should still reflect atomic2, the last thing we pushed
+	# successfully
+	git rev-parse atomic2 >expected &&
+	# on master...
+	git -C "$d" rev-parse refs/heads/master >actual &&
+	test_cmp expected actual &&
+	# ...and collateral.
+	git -C "$d" rev-parse refs/heads/collateral >actual &&
+	test_cmp expected actual &&
+
+	# the failed refs should be indicated to the user
+	grep "^ ! .*rejected.* master -> master" output &&
+
+	# the collateral failure refs should be indicated to the user
+	grep "^ ! .*rejected.* atomic -> atomic .*atomic push failed" output &&
+	grep "^ ! .*rejected.* collateral -> collateral .*atomic push failed" output
+'
+
 test_expect_success 'push --all can push to empty repo' '
 	d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
 	git init --bare "$d" &&
diff --git a/transport-helper.c b/transport-helper.c
index c7e17ec9cb..6b05a88faf 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -853,6 +853,7 @@ static int push_refs_with_push(struct transport *transport,
 {
 	int force_all = flags & TRANSPORT_PUSH_FORCE;
 	int mirror = flags & TRANSPORT_PUSH_MIRROR;
+	int atomic = flags & TRANSPORT_PUSH_ATOMIC;
 	struct helper_data *data = transport->data;
 	struct strbuf buf = STRBUF_INIT;
 	struct ref *ref;
@@ -872,6 +873,11 @@ static int push_refs_with_push(struct transport *transport,
 		case REF_STATUS_REJECT_NONFASTFORWARD:
 		case REF_STATUS_REJECT_STALE:
 		case REF_STATUS_REJECT_ALREADY_EXISTS:
+			if (atomic) {
+				string_list_clear(&cas_options, 0);
+				return 0;
+			} else
+				continue;
 		case REF_STATUS_UPTODATE:
 			continue;
 		default:
diff --git a/transport.c b/transport.c
index f1fcd2c4b0..d768bc275e 100644
--- a/transport.c
+++ b/transport.c
@@ -1226,6 +1226,19 @@ int transport_push(struct repository *r,
 		err = push_had_errors(remote_refs);
 		ret = push_ret | err;
 
+		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
+			for (struct ref *it = remote_refs; it; it = it->next)
+				switch (it->status) {
+				case REF_STATUS_NONE:
+				case REF_STATUS_UPTODATE:
+				case REF_STATUS_OK:
+					it->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+					break;
+				default:
+					break;
+				}
+		}
+
 		if (!quiet || err)
 			transport_print_push_status(transport->url, remote_refs,
 					verbose | porcelain, porcelain,
-- 
2.22.0.410.gd8fdbe21b5-goog
Junio C Hamano· Jul 12, 2019, 16:25 UTC · re: Emily Shaffer · lore

Re: [PATCH v3] transport-helper: enforce atomic in push_refs_with_push

Emily Shaffer <emilyshaffer@google.com> writes:
Show 8 quoted lines
> +test_expect_success 'push --atomic also prevents branch creation, reports collateral' '
> +	# Setup upstream repo - empty for now
> +	d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
> +	git init --bare "$d" &&
> +	test_config -C "$d" http.receivepack true &&
> +	up="$HTTPD_URL"/smart/atomic-branches.git &&
> +
> +	# Tell "$up" about two branches for now
Ahh, that's what you meant ;-)  Makes sense.
Carlo Arenas· Jul 16, 2019, 07:10 UTC · re: Emily Shaffer · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Tue, Jul 9, 2019 at 2:16 PM Emily Shaffer <emilyshaffer@google.com> wrote:
Show 11 quoted lines
>
> diff --git a/transport.c b/transport.c
> index f1fcd2c4b0..d768bc275e 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -1226,6 +1226,19 @@ int transport_push(struct repository *r,
>                 err = push_had_errors(remote_refs);
>                 ret = push_ret | err;
>
> +               if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
> +                       for (struct ref *it = remote_refs; it; it = it->next)

moving "struct ref it" out of the loop, allows for building with ancient compilers that don't support C90 (even if only by default) as I found out while building pu in a Centos 6 box

Carlo
Junio C Hamano· Jul 16, 2019, 16:53 UTC · re: Carlo Arenas · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

Carlo Arenas <carenas@gmail.com> writes:
Show 16 quoted lines
> On Tue, Jul 9, 2019 at 2:16 PM Emily Shaffer <emilyshaffer@google.com> wrote:
>>
>> diff --git a/transport.c b/transport.c
>> index f1fcd2c4b0..d768bc275e 100644
>> --- a/transport.c
>> +++ b/transport.c
>> @@ -1226,6 +1226,19 @@ int transport_push(struct repository *r,
>>                 err = push_had_errors(remote_refs);
>>                 ret = push_ret | err;
>>
>> +               if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
>> +                       for (struct ref *it = remote_refs; it; it = it->next)
>
> moving "struct ref it" out of the loop, allows for building with ancient
> compilers that don't support C90 (even if only by default) as I found
> out while building pu in a Centos 6 box

Does everything else compile OK with your rather old compiler on Centos 6? I was historically rather pedantic to stick to C89 but for past several years we've been experimenting with a bit more modern features of the language to see if anybody screams (namely, designated initializers in structs and arrays, and trailing comma in enum definition) but I think we still reject variable definition in for loop control (we saw and rewrote another patch that tried to use it late last year).

Apparently, this one slipped our review process.
Thanks.

cf. https://public-inbox.org/git/xmqqwopgqsws.fsf@gitster-ct.c.googlers.com/ cf. https://public-inbox.org/git/xmqqmuuz9jih.fsf@gitster-ct.c.googlers.com/

 
Junio C Hamano· Jul 16, 2019, 17:21 UTC · re: Junio C Hamano · lore

[RFC/PATCH] CodingGuidelines: spell out post-C89 rules

Even though we have been sticking to C89, there are a few handy features we borrow from more recent C language in our codebase after trying them in weather balloons and saw that nobody screamed.

Spell them out.

While at it, extend the existing variable declaration rule a bit to read better with the newly spelled out rule for the for loop.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/CodingGuidelines | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
Show changes to Documentation/CodingGuidelines +17 −3
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 1169ff6c8e..53903b14c8 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -195,10 +195,24 @@ For C programs:
    by e.g. "echo DEVELOPER=1 >>config.mak".
 
  - We try to support a wide range of C compilers to compile Git with,
-   including old ones. That means that you should not use C99
-   initializers, even if a lot of compilers grok it.
+   including old ones. That means that you should not use certain C99
+   features, even if your compiler groks it.  There are a few
+   exceptions:
 
- - Variables have to be declared at the beginning of the block.
+   . since early 2012 with e1327023ea, we have been using an enum
+     definition whose last element is followed by a comma.
+
+   . since mid 2017 with cbc0f81d and 512f41cf, we have been using
+     designated initializers for struct and array.
+
+   These used to be forbidden, but we have not heard breakage report,
+   so they are assumed to be safe.
+
+ - Variables have to be declared at the beginning of the block, before
+   the first statement (i.e. -Wdeclaration-after-statement).
+
+ - Declaring a variable in the for loop "for (int i = 0; i < 10; i++)"
+   is still not allowed in this codebase.
 
  - NULL pointers shall be written as NULL, not as 0.
 
Jonathan Nieder· Jul 17, 2019, 00:55 UTC · re: Junio C Hamano · lore

Re: [RFC/PATCH] CodingGuidelines: spell out post-C89 rules

Junio C Hamano wrote:
Show 5 quoted lines
> Even though we have been sticking to C89, there are a few handy
> features we borrow from more recent C language in our codebase after
> trying them in weather balloons and saw that nobody screamed.
>
> Spell them out.

Thanks for this. It gives a place to advertise future weather balloons, too.

[...]
Show 14 quoted lines
> --- a/Documentation/CodingGuidelines
> +++ b/Documentation/CodingGuidelines
> @@ -195,10 +195,24 @@ For C programs:
>     by e.g. "echo DEVELOPER=1 >>config.mak".
>  
>   - We try to support a wide range of C compilers to compile Git with,
> -   including old ones. That means that you should not use C99
> -   initializers, even if a lot of compilers grok it.
> +   including old ones. That means that you should not use certain C99
> +   features, even if your compiler groks it.  There are a few
> +   exceptions:
> +
> +   . since early 2012 with e1327023ea, we have been using an enum
> +     definition whose last element is followed by a comma.

This is an interesting one: it's super convenient, but we have received patches every 10 years or so to remove the trailing comma --- e.g. https://public-inbox.org/git/20100311163235.GC7877@thor.il.thewrittenword.com/

I *think* these were motivated by wanting to be able to build Git with old compilers with pedantic warnings on, and certainly the last seven years of silence on the subject suggests it's okay. Should we be even more prescriptive and say that the last element should always be followed by a comma, for ease of later patching?

> +
> +   . since mid 2017 with cbc0f81d and 512f41cf, we have been using
> +     designated initializers for struct and array.

Can this include an example for the benefit of readers that don't know what a designated initializer is? E.g.

      . since mid 2017 with cb0f81d and 512f41cf, we have been using
        designated initializers for struct members ("{ .alloc = 1 }")
	and array members ("[5] = 0").
> +
> +   These used to be forbidden, but we have not heard breakage report,
> +   so they are assumed to be safe.
nit: missing article "any" before "breakage reports".
Show 7 quoted lines
>  
> - - Variables have to be declared at the beginning of the block.
> + - Variables have to be declared at the beginning of the block, before
> +   the first statement (i.e. -Wdeclaration-after-statement).
> +
> + - Declaring a variable in the for loop "for (int i = 0; i < 10; i++)"
> +   is still not allowed in this codebase.
Nice.
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Thanks.
Junio C Hamano· Jul 17, 2019, 16:03 UTC · re: Jonathan Nieder · lore

Re: [RFC/PATCH] CodingGuidelines: spell out post-C89 rules

Jonathan Nieder <jrnieder@gmail.com> writes:
Show 22 quoted lines
> Junio C Hamano wrote:
>
>> Even though we have been sticking to C89, there are a few handy
>> features we borrow from more recent C language in our codebase after
>> trying them in weather balloons and saw that nobody screamed.
>>
>> Spell them out.
>
> Thanks for this.  It gives a place to advertise future weather balloons,
> too.
>
> [...]
>> --- a/Documentation/CodingGuidelines
>> +++ b/Documentation/CodingGuidelines
>> @@ -195,10 +195,24 @@ For C programs:
>>     by e.g. "echo DEVELOPER=1 >>config.mak".
>>  
>>   - We try to support a wide range of C compilers to compile Git with,
>> -   including old ones. That means that you should not use C99
>> -   initializers, even if a lot of compilers grok it.
>> +   including old ones. That means that you should not use certain C99
>> +   features, even if your compiler groks it.  There are a few
s/it/them/ I think.
Show 8 quoted lines
>> +   exceptions:
>> +
>> +   . since early 2012 with e1327023ea, we have been using an enum
>> +     definition whose last element is followed by a comma.
>
> ...  Should we be even
> more prescriptive and say that the last element should always be
> followed by a comma, for ease of later patching?

That is an interesting tangent. We have been taking advantage of the trailing comma for array initializers (e.g. options[] fed to parse_options() API)---we would gain similar convenience with it.

Show 6 quoted lines
>> +
>> +   . since mid 2017 with cbc0f81d and 512f41cf, we have been using
>> +     designated initializers for struct and array.
>
> Can this include an example for the benefit of readers that don't know
> what a designated initializer is?

I thought about it, but decided against it, because those who really want to are already given the commit object names to learn from, and in larger context.

But if we are to split this into two bullet points (one for struct, one for array), I am OK to add an example to it while at it. Let me see how it looks like.

Show 5 quoted lines
>> +
>> +   These used to be forbidden, but we have not heard breakage report,
>> +   so they are assumed to be safe.
>
> nit: missing article "any" before "breakage reports".
Thanks.
-- >8 --
    
Even though we have been sticking to C89, there are a few handy
features we borrow from more recent C language in our codebase after
trying them in weather balloons and saw that nobody screamed.
Spell them out.

While at it, extend the existing variable declaration rule a bit to read better with the newly spelled out rule for the for loop.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/CodingGuidelines | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)
 Documentation/CodingGuidelines | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)
Show changes to Documentation/CodingGuidelines +23 −3
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 32210a4386..bd4b7905d0 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -195,10 +195,30 @@ For C programs:
    by e.g. "echo DEVELOPER=1 >>config.mak".
 
  - We try to support a wide range of C compilers to compile Git with,
-   including old ones. That means that you should not use C99
-   initializers, even if a lot of compilers grok it.
+   including old ones.  You should not use features from newer C
+   standard, even if your compiler groks them.  
 
- - Variables have to be declared at the beginning of the block.
+   There are a few exceptions to this guideline:
+
+   . since early 2012 with e1327023ea, we have been using an enum
+     definition whose last element is followed by a comma.  This, like
+     an array initializer that ends with a trailing comma, can be used
+     to reduce the patch noise when adding a new identifer at the end.
+
+   . since mid 2017 with cbc0f81d, we have been using designated
+     initializers for struct (e.g. "struct t v = { .val = 'a' };").
+
+   . since mid 2017 with 512f41cf, we have been using designated
+     initializers for array (e.g. "int array[10] = { [5] = 2 }").
+
+   These used to be forbidden, but we have not heard any breakage
+   report, and they are assumed to be safe.
+
+ - Variables have to be declared at the beginning of the block, before
+   the first statement (i.e. -Wdeclaration-after-statement).
+
+ - Declaring a variable in the for loop "for (int i = 0; i < 10; i++)"
+   is still not allowed in this codebase.
 
  - NULL pointers shall be written as NULL, not as 0.
 
Jonathan Nieder· Jul 19, 2019, 01:15 UTC · re: Junio C Hamano · lore

Re: [RFC/PATCH] CodingGuidelines: spell out post-C89 rules

Junio C Hamano wrote:
Show 14 quoted lines
> -- >8 --
> Even though we have been sticking to C89, there are a few handy
> features we borrow from more recent C language in our codebase after
> trying them in weather balloons and saw that nobody screamed.
>
> Spell them out.
>
> While at it, extend the existing variable declaration rule a bit to
> read better with the newly spelled out rule for the for loop.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  Documentation/CodingGuidelines | 25 ++++++++++++++++++++++---
>  1 file changed, 22 insertions(+), 3 deletions(-)
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Thanks.
Bryan Turner· Jul 17, 2019, 01:09 UTC · re: Junio C Hamano · lore

Re: [RFC/PATCH] CodingGuidelines: spell out post-C89 rules

On Tue, Jul 16, 2019 at 10:21 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 32 quoted lines
>
> Even though we have been sticking to C89, there are a few handy
> features we borrow from more recent C language in our codebase after
> trying them in weather balloons and saw that nobody screamed.
>
> Spell them out.
>
> While at it, extend the existing variable declaration rule a bit to
> read better with the newly spelled out rule for the for loop.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  Documentation/CodingGuidelines | 20 +++++++++++++++++---
>  1 file changed, 17 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
> index 1169ff6c8e..53903b14c8 100644
> --- a/Documentation/CodingGuidelines
> +++ b/Documentation/CodingGuidelines
> @@ -195,10 +195,24 @@ For C programs:
>     by e.g. "echo DEVELOPER=1 >>config.mak".
>
>   - We try to support a wide range of C compilers to compile Git with,
> -   including old ones. That means that you should not use C99
> -   initializers, even if a lot of compilers grok it.
> +   including old ones. That means that you should not use certain C99
> +   features, even if your compiler groks it.  There are a few
> +   exceptions:
>
> - - Variables have to be declared at the beginning of the block.
> +   . since early 2012 with e1327023ea, we have been using an enum
> +     definition whose last element is followed by a comma.
Is there a significance to the leading . here versus a leading - below?
Show 6 quoted lines
> +
> +   . since mid 2017 with cbc0f81d and 512f41cf, we have been using
> +     designated initializers for struct and array.
> +
> +   These used to be forbidden, but we have not heard breakage report,
> +   so they are assumed to be safe.

With the placement here, is it possible that someone might read the “These used to be forbidden” as applying to the items that follow after it, rather than the items that preceded it? Put a different way, could there be some value in having some additional verbiage here that indicates something along the lines of “Aside from those exceptions, other C99 features are not allowed. Some common examples are:”

Just a thought. (Pardon the suggestion from the peanut gallery!)
Show 10 quoted lines
> +
> + - Variables have to be declared at the beginning of the block, before
> +   the first statement (i.e. -Wdeclaration-after-statement).
> +
> + - Declaring a variable in the for loop "for (int i = 0; i < 10; i++)"
> +   is still not allowed in this codebase.
>
>   - NULL pointers shall be written as NULL, not as 0.
>
>
Junio C Hamano· Jul 17, 2019, 16:05 UTC · re: Bryan Turner · lore

Re: [RFC/PATCH] CodingGuidelines: spell out post-C89 rules

Bryan Turner <bturner@atlassian.com> writes:
Show 34 quoted lines
> On Tue, Jul 16, 2019 at 10:21 AM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Even though we have been sticking to C89, there are a few handy
>> features we borrow from more recent C language in our codebase after
>> trying them in weather balloons and saw that nobody screamed.
>>
>> Spell them out.
>>
>> While at it, extend the existing variable declaration rule a bit to
>> read better with the newly spelled out rule for the for loop.
>>
>> Signed-off-by: Junio C Hamano <gitster@pobox.com>
>> ---
>>  Documentation/CodingGuidelines | 20 +++++++++++++++++---
>>  1 file changed, 17 insertions(+), 3 deletions(-)
>>
>> diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
>> index 1169ff6c8e..53903b14c8 100644
>> --- a/Documentation/CodingGuidelines
>> +++ b/Documentation/CodingGuidelines
>> @@ -195,10 +195,24 @@ For C programs:
>>     by e.g. "echo DEVELOPER=1 >>config.mak".
>>
>>   - We try to support a wide range of C compilers to compile Git with,
>> -   including old ones. That means that you should not use C99
>> -   initializers, even if a lot of compilers grok it.
>> +   including old ones. That means that you should not use certain C99
>> +   features, even if your compiler groks it.  There are a few
>> +   exceptions:
>>
>> +   . since early 2012 with e1327023ea, we have been using an enum
>> +     definition whose last element is followed by a comma.
>
> Is there a significance to the leading . here versus a leading - below?
Absolutely.
    - Item 1's description
    - Item 2's description
      . subitem a of 2
      . subitem b of 2
      These two subitems are exceptions.
    - Item 3's description
was what I meant.
Carlo Arenas· Jul 16, 2019, 18:00 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Tue, Jul 16, 2019 at 9:54 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> Does everything else compile OK with your rather old compiler on
> Centos 6?

yes, they were a few -Wmaybe-uninitialized warnings but they were most likely false positives.

gcc 4.4.7 aborts the build (even without -Werror) with the following message: transport.c: In function 'transport_push': transport.c:1232: error: 'for' loop initial declarations are only allowed in C99 mode transport.c:1232: note: use option -std=c99 or -std=gnu99 to compile your code make: *** [transport.o] Error 1

Carlo
Junio C Hamano· Jul 16, 2019, 20:28 UTC · re: Junio C Hamano · lore

[PATCH] transport-helper: avoid var decl in for () loop control

We do allow a few selected C99 constructs in our codebase these days, but this is not among them (yet).

Reported-by: Carlo Arenas <carenas@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 transport.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Show changes to transport.c +2 −1
diff --git a/transport.c b/transport.c
index d768bc275e..453de8f704 100644
--- a/transport.c
+++ b/transport.c
@@ -1227,7 +1227,8 @@ int transport_push(struct repository *r,
 		ret = push_ret | err;
 
 		if ((flags & TRANSPORT_PUSH_ATOMIC) && err) {
-			for (struct ref *it = remote_refs; it; it = it->next)
+			struct ref *it;
+			for (it = remote_refs; it; it = it->next)
 				switch (it->status) {
 				case REF_STATUS_NONE:
 				case REF_STATUS_UPTODATE:
Jonathan Nieder· Jul 17, 2019, 00:42 UTC · re: Junio C Hamano · lore

Re: [PATCH] transport-helper: avoid var decl in for () loop control

Junio C Hamano wrote:
Show 8 quoted lines
> We do allow a few selected C99 constructs in our codebase these
> days, but this is not among them (yet).
>
> Reported-by: Carlo Arenas <carenas@gmail.com>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  transport.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
Yes, gcc 4.8 fails to build without this:
 transport.c:1234:4: error: ‘for’ loop initial declarations are only allowed in C99 mode
     for (struct ref *it = remote_refs; it; it = it->next)
     ^
 transport.c:1234:4: note: use option -std=c99 or -std=gnu99 to compile your code

Arguably it would be nice to use -std=gnu99 for better consistency between gcc versions, but it's moot here: avoiding the declaration in for loop initializer is more consistent with -Wdeclaration-after-statement anyway.

Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Thanks.
SZEDER Gábor· Jul 18, 2019, 15:22 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Tue, Jul 16, 2019 at 09:53:59AM -0700, Junio C Hamano wrote:
> Carlo Arenas <carenas@gmail.com> writes:
Show 5 quoted lines
> >> +                       for (struct ref *it = remote_refs; it; it = it->next)
> >
> > moving "struct ref it" out of the loop, allows for building with ancient
> > compilers that don't support C90 (even if only by default) as I found
> > out while building pu in a Centos 6 box
Show 5 quoted lines
> but I think we still reject variable definition in
> for loop control (we saw and rewrote another patch that tried to use
> it late last year).
> 
> Apparently, this one slipped our review process.

I expected that this will eventually happen after Travis CI's default Linux image recently changed from Ubuntu 14.04 to 16.04; explanation in the commit message below.

With that patch issues like this could be caught earlier, while they are only in 'pu' but not yet in 'next'. But do we really want to do that, is that the right tradeoff? Dunno... Adding a dedicated CI job just to check that there are no 'for' loop initial declarations seems kind of excessive, even if it only builds but doesn't run the test suite. And I don't know whether there are any other undesired ("too new") constructs that GCC 4.8 would catch but later compilers quietly accept.

  --- >8 ---
Subject: [PATCH] travis-ci: build with GCC 4.8 as well

C99 'for' loop initial declaration, i.e. 'for (int i = 0; i < n; i++)', is not allowed in Git's codebase yet, to maintain compatibility with some older compilers.

Our Travis CI builds used to catch 'for' loop initial declarations, because the GETTEXT_POISON job has always built Git with the default 'cc', which in Travis CI's previous default Linux image (based on Ubuntu 14.04 Trusty) is GCC 4.8, and that GCC version errors out on this construct (not only with DEVELOPER=1, but with our default CFLAGS as well). Alas, that's not the case anymore, becase after 14.04's EOL Travis CI's current default Linux image is based on Ubuntu 16.04 Xenial [1] and its default 'cc' is now GCC 5.4, which, just like all later GCC and Clang versions, simply accepts this construct, even if we don't explicitly specify '-std=c99'.

Ideally we would adjust our CFLAGS used with DEVELOPER=1 to catch this undesired construct already when contributors build Git on their own machines. Unfortunately, however, there seems to be no compiler option that would catch only this particular construct without choking on many other things, e.g. while a later compiler with '-std=c90' and/or '-ansi' does catch this construct, it can't build Git because of several screenfulls of other errors.

Add the 'linux-gcc-4.8' job to Travis CI, in order to build Git with GCC 4.8, and thus to timely catch any 'for' loop initial declarations. To catch those it's sufficient to only build Git with GCC 4.8, so don't run the test suite in this job, because 'make test' takes rather long [2], and it's already run five times in other jobs, so we wouldn't get our time's worth.

[1] The Azure Pipelines builds have been using Ubuntu 16.04 images
    from the start, so I belive they never caught 'for' loop initial
    declarations.
[2] On Travis CI 'make test' alone would take about 9 minutes in this
    new job (without running httpd, Subversion, and P4 tests).  For
    comparison, starting the job and building Git with GCC 4.8 takes
    only about 2 minutes.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
---
 .travis.yml               |  4 ++++
 ci/run-build-and-tests.sh | 17 +++++++++++++----
 2 files changed, 17 insertions(+), 4 deletions(-)
Show changes to 2 files +17 −4

.travis.yml, ci/run-build-and-tests.sh

diff --git a/.travis.yml b/.travis.yml
index ffb1bc46f2..fc5730b085 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,6 +21,10 @@ matrix:
       compiler:
       addons:
       before_install:
+    - env: jobname=linux-gcc-4.8
+      os: linux
+      dist: trusty
+      compiler:
     - env: jobname=Linux32
       os: linux
       compiler:
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index cdd2913440..ff0ef7f08e 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -11,9 +11,9 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
 esac
 
 make
-make test
-if test "$jobname" = "linux-gcc"
-then
+case "$jobname" in
+linux-gcc)
+	make test
 	export GIT_TEST_SPLIT_INDEX=yes
 	export GIT_TEST_FULL_IN_PACK_ARRAY=true
 	export GIT_TEST_OE_SIZE=10
@@ -21,7 +21,16 @@ then
 	export GIT_TEST_COMMIT_GRAPH=1
 	export GIT_TEST_MULTI_PACK_INDEX=1
 	make test
-fi
+	;;
+linux-gcc-4.8)
+	# Don't run the tests; we only care about whether Git can be
+	# built with GCC 4.8, as it errors out on some undesired (C99)
+	# constructs that newer compilers seem to quietly accept.
+	;;
+*)
+	make test
+	;;
+esac
 
 check_unignored_build_artifacts
 
-- 
2.22.0.810.g50207c7d84
Junio C Hamano· Jul 18, 2019, 16:12 UTC · re: SZEDER Gábor · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

SZEDER Gábor <szeder.dev@gmail.com> writes:
> With that patch issues like this could be caught earlier, while they
> are only in 'pu' but not yet in 'next'.  But do we really want to do
> that, is that the right tradeoff?

I am sort of in favor of having at least one build with an older compiler without "-std=c99", like the set-up you are proposing. I got an impression from an earlier message that Jonathan's preference is the opposite. I'd prefer to hear opinions from others, too.

The main reason why we accept other new features like trailing comma in enum def and designated initializers while rejecting for loop init is because there apparently are those who build Git with compilers that accept & reject these combinations of features and who care enough to report compilation failure from their build. And apparently gcc4.8 can serve as a representative "old" compiler, so...

SZEDER Gábor· Jul 18, 2019, 23:46 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Thu, Jul 18, 2019 at 09:12:52AM -0700, Junio C Hamano wrote:
Show 8 quoted lines
> SZEDER Gábor <szeder.dev@gmail.com> writes:
> 
> > With that patch issues like this could be caught earlier, while they
> > are only in 'pu' but not yet in 'next'.  But do we really want to do
> > that, is that the right tradeoff?
> 
> I am sort of in favor of having at least one build with an older
> compiler without "-std=c99", like the set-up you are proposing.

Alternatively, we could sort-of restore the old state of the GETTEXT_POISON job, i.e. build Git with GCC 4.8 in that job. "Sort-of", because we don't necessarily have to go back to the Ubuntu 14.04 based Linux image, because GCC 4.8 is packaged in 16.04 as well, so it's just an 'apt-get install gcc-4.8' away.

Eric Sunshine· Jul 18, 2019, 16:29 UTC · re: SZEDER Gábor · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Thu, Jul 18, 2019 at 11:22 AM SZEDER Gábor <szeder.dev@gmail.com> wrote:
Show 7 quoted lines
> C99 'for' loop initial declaration, i.e. 'for (int i = 0; i < n; i++)',
> is not allowed in Git's codebase yet, to maintain compatibility with
> some older compilers.
> [...]
> [1] The Azure Pipelines builds have been using Ubuntu 16.04 images
>     from the start, so I belive they never caught 'for' loop initial
>     declarations.
s/belive/believe/
Show 6 quoted lines
> [2] On Travis CI 'make test' alone would take about 9 minutes in this
>     new job (without running httpd, Subversion, and P4 tests).  For
>     comparison, starting the job and building Git with GCC 4.8 takes
>     only about 2 minutes.
>
> Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Jonathan Nieder· Jul 19, 2019, 01:31 UTC · re: SZEDER Gábor · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

SZEDER Gábor wrote:
Show 12 quoted lines
> I expected that this will eventually happen after Travis CI's default
> Linux image recently changed from Ubuntu 14.04 to 16.04; explanation
> in the commit message below.
>
> With that patch issues like this could be caught earlier, while they
> are only in 'pu' but not yet in 'next'.  But do we really want to do
> that, is that the right tradeoff?  Dunno...  Adding a dedicated CI job
> just to check that there are no 'for' loop initial declarations seems
> kind of excessive, even if it only builds but doesn't run the test
> suite.  And I don't know whether there are any other undesired ("too
> new") constructs that GCC 4.8 would catch but later compilers quietly
> accept.

This makes sense to me. Not really for the 'for' loop declaration aspect: for that, I'd want some more specialized tool that allows turning on such a check specifically. But more because Ubuntu trusty is still a platform that some people use (though hopefully not for long), so it's helpful as a representative old platform to see if we break the build on it.

[...]
> [2] On Travis CI 'make test' alone would take about 9 minutes in this
>     new job (without running httpd, Subversion, and P4 tests).  For
>     comparison, starting the job and building Git with GCC 4.8 takes
>     only about 2 minutes.

Nice. In an ideal world there would be some kind of "make fasttest" that runs some fast subset of tests. But this seems pretty safe.

Show 5 quoted lines
> Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
> ---
>  .travis.yml               |  4 ++++
>  ci/run-build-and-tests.sh | 17 +++++++++++++----
>  2 files changed, 17 insertions(+), 4 deletions(-)
Thanks.
[...]
Show 30 quoted lines
> --- a/ci/run-build-and-tests.sh
> +++ b/ci/run-build-and-tests.sh
> @@ -11,9 +11,9 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
>  esac
>  
>  make
> -make test
> -if test "$jobname" = "linux-gcc"
> -then
> +case "$jobname" in
> +linux-gcc)
> +	make test
>  	export GIT_TEST_SPLIT_INDEX=yes
>  	export GIT_TEST_FULL_IN_PACK_ARRAY=true
>  	export GIT_TEST_OE_SIZE=10
> @@ -21,7 +21,16 @@ then
>  	export GIT_TEST_COMMIT_GRAPH=1
>  	export GIT_TEST_MULTI_PACK_INDEX=1
>  	make test
> -fi
> +	;;
> +linux-gcc-4.8)
> +	# Don't run the tests; we only care about whether Git can be
> +	# built with GCC 4.8, as it errors out on some undesired (C99)
> +	# constructs that newer compilers seem to quietly accept.
> +	;;
> +*)
> +	make test
> +	;;
> +esac
Does what it says on the tin.
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Carlo Arenas· Jul 19, 2019, 04:49 UTC · re: Jonathan Nieder · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

On Thu, Jul 18, 2019 at 6:31 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
Show 7 quoted lines
>
> This makes sense to me.  Not really for the 'for' loop declaration
> aspect: for that, I'd want some more specialized tool that allows
> turning on such a check specifically.  But more because Ubuntu trusty
> is still a platform that some people use (though hopefully not for
> long), so it's helpful as a representative old platform to see if we
> break the build on it.

FWIW this also breaks Centos 7 using gcc 4.8.5, as well as the one originally reported (Centos 6), and anything else that uses gcc 4 (tested up to 4.9.4)

Carlo
Junio C Hamano· Jul 19, 2019, 19:15 UTC · re: Carlo Arenas · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

Carlo Arenas <carenas@gmail.com> writes:
Show 12 quoted lines
> On Thu, Jul 18, 2019 at 6:31 PM Jonathan Nieder <jrnieder@gmail.com> wrote:
>>
>> This makes sense to me.  Not really for the 'for' loop declaration
>> aspect: for that, I'd want some more specialized tool that allows
>> turning on such a check specifically.  But more because Ubuntu trusty
>> is still a platform that some people use (though hopefully not for
>> long), so it's helpful as a representative old platform to see if we
>> break the build on it.
>
> FWIW this also breaks Centos 7 using gcc 4.8.5, as well as the one
> originally reported (Centos 6), and anything else that uses gcc 4
> (tested up to 4.9.4)
Thanks.
SZEDER Gábor· Jul 27, 2019, 08:43 UTC · re: SZEDER Gábor · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

Junio,
On Thu, Jul 18, 2019 at 05:22:34PM +0200, SZEDER Gábor wrote:
> Subject: [PATCH] travis-ci: build with GCC 4.8 as well

This patch conflicts with topic 'js/trace2-json-schema', and the current conflict resolution in 'pu' is not quite correct.

Show 14 quoted lines
> diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
> index cdd2913440..ff0ef7f08e 100755
> --- a/ci/run-build-and-tests.sh
> +++ b/ci/run-build-and-tests.sh
> @@ -11,9 +11,9 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
>  esac
>  
>  make
> -make test
> -if test "$jobname" = "linux-gcc"
> -then
> +case "$jobname" in
> +linux-gcc)
> +	make test

This 'make test' here is important, but the confict resolution accidentally removed it.

Show 25 quoted lines
>  	export GIT_TEST_SPLIT_INDEX=yes
>  	export GIT_TEST_FULL_IN_PACK_ARRAY=true
>  	export GIT_TEST_OE_SIZE=10
> @@ -21,7 +21,16 @@ then
>  	export GIT_TEST_COMMIT_GRAPH=1
>  	export GIT_TEST_MULTI_PACK_INDEX=1
>  	make test
> -fi
> +	;;
> +linux-gcc-4.8)
> +	# Don't run the tests; we only care about whether Git can be
> +	# built with GCC 4.8, as it errors out on some undesired (C99)
> +	# constructs that newer compilers seem to quietly accept.
> +	;;
> +*)
> +	make test
> +	;;
> +esac
>  
>  check_unignored_build_artifacts
>  
> -- 
> 2.22.0.810.g50207c7d84
> 
> 
Junio C Hamano· Jul 27, 2019, 16:11 UTC · re: SZEDER Gábor · lore

Re: [PATCH v2] transport-helper: enforce atomic in push_refs_with_push

SZEDER Gábor <szeder.dev@gmail.com> writes:
Show 7 quoted lines
> Junio,
>
> On Thu, Jul 18, 2019 at 05:22:34PM +0200, SZEDER Gábor wrote:
>> Subject: [PATCH] travis-ci: build with GCC 4.8 as well
>
> This patch conflicts with topic 'js/trace2-json-schema', and the
> current conflict resolution in 'pu' is not quite correct.
Thanks.

"git diff ...MERGE_HEAD" during a merge of js/trace2-json-schema gives me this patch:

Show changes to ci/run-build-and-tests.sh +14 −0
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index cdd2913440..ec38bf379a 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -14,6 +14,8 @@ make
 make test
 if test "$jobname" = "linux-gcc"
 then
+	make -C t/trace_schema_validator
+	export GIT_TRACE2_EVENT=$(mktemp)
 	export GIT_TEST_SPLIT_INDEX=yes
 	export GIT_TEST_FULL_IN_PACK_ARRAY=true
 	export GIT_TEST_OE_SIZE=10
@@ -21,6 +23,10 @@ then
 	export GIT_TEST_COMMIT_GRAPH=1
 	export GIT_TEST_MULTI_PACK_INDEX=1
 	make test
+	t/trace_schema_validator/trace_schema_validator \
+		--trace2_event_file=${GIT_TRACE2_EVENT} \
+		--schema_file=t/trace_schema_validator/strict_schema.json \
+		--progress=10000
 fi
 
 check_unignored_build_artifacts

i.e. they want to run an extra make in that validator directory,
export another environment variable, and then run the validator
*after* running the normal "make test", in linux-gcc job.

>> diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
>> index cdd2913440..ff0ef7f08e 100755
>> --- a/ci/run-build-and-tests.sh
>> +++ b/ci/run-build-and-tests.sh
>> @@ -11,9 +11,9 @@ windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";;
>>  esac
>>  
>>  make
>> -make test
>> -if test "$jobname" = "linux-gcc"
>> -then
>> +case "$jobname" in
>> +linux-gcc)
>> +	make test
>
> This 'make test' here is important, but the confict resolution
> accidentally removed it.

Right.  Thanks for spotting.

I can see in "git diff pu~2 pu~1" that indeed the first 'make test'
that we want to run without any of these environment variables is
lost in the merge.  We want to run two tests, with or without these
environment variables, and the validator wants to piggyback on the
second one.

Will fix in the meantime, but I was expecting that this "validator
in CI" business to be redone differently, perhaps with a different
validator implementation and either in a separate job or just part
of an existing job but trace enabled only for some subset of the
tests and/or only for new tests specifically written for trace
coverage, so after that happens this may turn into a moot point.

The change the merge brings in to the file now reads like this.

Thanks, again.

diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index ff0ef7f08e..35ff4d3038 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -14,6 +14,9 @@ make
 case "$jobname" in
 linux-gcc)
 	make test
+
+	make -C t/trace_schema_validator
+	export GIT_TRACE2_EVENT=$(mktemp)
 	export GIT_TEST_SPLIT_INDEX=yes
 	export GIT_TEST_FULL_IN_PACK_ARRAY=true
 	export GIT_TEST_OE_SIZE=10
@@ -21,6 +24,11 @@ linux-gcc)
 	export GIT_TEST_COMMIT_GRAPH=1
 	export GIT_TEST_MULTI_PACK_INDEX=1
 	make test
+
+	t/trace_schema_validator/trace_schema_validator \
+		--trace2_event_file=${GIT_TRACE2_EVENT} \
+		--schema_file=t/trace_schema_validator/strict_schema.json \
+		--progress=10000
 	;;
 linux-gcc-4.8)
 	# Don't run the tests; we only care about whether Git can be

← back to recent threads