threads / patch / 46525

patcham: fix signoff when other trailers are present

Subject: [PATCH v1] am: fix signoff when other trailers are present

## tl;dr

8 messages between Aug 7, 2017 and Aug 12, 2017. Diffs are folded; open one to read it.

replies: 7people: 3as markdown or json

Phillip Wood· Aug 7, 2017, 10:29 UTC · lore
From: Phillip Wood <phillip.wood@dunelm.org.uk>

If there was no 'Signed-off-by:' trailer but another trailer such as 'Reported-by:' then 'git am --signoff' would add a blank line between the existing trailers and the added 'Signed-off-by:' line. e.g.

    Rebase accepts '--rerere-autoupdate' as an option but only honors
    it if '-m' is also given. Fix it for a non-interactive rebase by
    passing on the option to 'git am' and 'git cherry-pick'.
    Reported-by: Junio C Hamano <gitster@pobox.com>
    Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>

Fix by using the code provided for this purpose in sequencer.c. Change the tests so that they check the formatting of the 'Signed-off-by:' lines rather than just grepping for them.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
I'm not sure if this should be calling ignore_non_trailer() or not -
git commit does but git cherry-pick does not. This follows commit and
cherry-pick in ignoring the value of trailer.ifExists for the signoff.
I'm a bit surprised they do that - is it correct?
 builtin/am.c  | 26 +------------------
 t/t4150-am.sh | 83 +++++++++++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 64 insertions(+), 45 deletions(-)
Show changes to 2 files +64 −45

builtin/am.c, t/t4150-am.sh

diff --git a/builtin/am.c b/builtin/am.c
index c973bd96dcb5d630d56e935733bfa4530ccd2872..f88d47c9d956f8e9d044cb1a4afa85c80f0c17cb 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1181,34 +1181,10 @@ static void NORETURN die_user_resolve(const struct am_state *state)
  */
 static void am_append_signoff(struct am_state *state)
 {
-	char *cp;
-	struct strbuf mine = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
 
 	strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
-
-	/* our sign-off */
-	strbuf_addf(&mine, "\n%s%s\n",
-		    sign_off_header,
-		    fmt_name(getenv("GIT_COMMITTER_NAME"),
-			     getenv("GIT_COMMITTER_EMAIL")));
-
-	/* Does sb end with it already? */
-	if (mine.len < sb.len &&
-	    !strcmp(mine.buf, sb.buf + sb.len - mine.len))
-		goto exit; /* no need to duplicate */
-
-	/* Does it have any Signed-off-by: in the text */
-	for (cp = sb.buf;
-	     cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL;
-	     cp = strchr(cp, '\n')) {
-		if (sb.buf == cp || cp[-1] == '\n')
-			break;
-	}
-
-	strbuf_addstr(&sb, mine.buf + !!cp);
-exit:
-	strbuf_release(&mine);
+	append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);
 	state->msg = strbuf_detach(&sb, &state->msg_len);
 }
 
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 44807e218d7016f58bd41b89af71104a37f31a8b..73b67b4280b99e0328e201e6b69c3d88b766ea84 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -40,6 +40,8 @@ test_expect_success 'setup: messages' '
 	dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
 	dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
 	feugait nulla facilisi.
+
+	Reported-by: A N Other <a.n.other@example.com>
 	EOF
 
 	cat >failmail <<-\EOF &&
@@ -93,7 +95,7 @@ test_expect_success setup '
 	echo world >>file &&
 	git add file &&
 	test_tick &&
-	git commit -s -F msg &&
+	git commit -F msg &&
 	git tag second &&
 
 	git format-patch --stdout first >patch1 &&
@@ -124,8 +126,6 @@ test_expect_success setup '
 		echo "Date: $GIT_AUTHOR_DATE" &&
 		echo &&
 		sed -e "1,2d" msg &&
-		echo &&
-		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
 		echo "---" &&
 		git diff-tree --no-commit-id --stat -p second
 	} >patch1-stgit.eml &&
@@ -144,8 +144,6 @@ test_expect_success setup '
 		echo "# Parent  $_z40" &&
 		cat msg &&
 		echo &&
-		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
-		echo &&
 		git diff-tree --no-commit-id -p second
 	} >patch1-hg.eml &&
 
@@ -470,13 +468,15 @@ test_expect_success 'am --signoff adds Signed-off-by: line' '
 	git reset --hard &&
 	git checkout -b master2 first &&
 	git am --signoff <patch2 &&
-	printf "%s\n" "$signoff" >expected &&
-	echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected &&
-	git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
-	test_cmp expected actual &&
-	echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
-	git cat-file commit HEAD | grep "Signed-off-by:" >actual &&
-	test_cmp expected actual
+	{
+		printf "third\n\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL"
+	} >expected-log &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
 '
 
 test_expect_success 'am stays in branch' '
@@ -486,17 +486,60 @@ test_expect_success 'am stays in branch' '
 '
 
 test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
-	git format-patch --stdout HEAD^ >patch3 &&
-	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
-	rm -fr .git/rebase-apply &&
-	git reset --hard &&
-	git checkout HEAD^ &&
-	git am --signoff patch4 &&
-	git cat-file commit HEAD >actual &&
-	test $(grep -c "^Signed-off-by:" actual) -eq 1
+	git format-patch --stdout first >patch3 &&
+	git reset --hard first &&
+	git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
+'
+
+test_expect_success 'am --signoff adds Signed-off-by: if another author is preset' '
+	NAME="A N Other" &&
+	EMAIL="a.n.other@example.com" &&
+	{
+		printf "third\n\nSigned-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL"
+	} >expected-log &&
+	git reset --hard first &&
+	GIT_COMMITTER_NAME="$NAME" GIT_COMMITTER_EMAIL="$EMAIL" \
+		git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
+'
+
+test_expect_success 'am --signoff duplicates Signed-off-by: if it is not the last one' '
+	NAME="A N Other" &&
+	EMAIL="a.n.other@example.com" &&
+	{
+		printf "third\n\nSigned-off-by: %s <%s>\n\
+Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\
+Signed-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL"
+	} >expected-log &&
+	git format-patch --stdout first >patch3 &&
+	git reset --hard first &&
+	git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
 '
 
 test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
+	git format-patch --stdout HEAD^ >tmp &&
+	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," tmp >patch4 &&
+	git reset --hard HEAD^ &&
+	git am <patch4 &&
 	git rev-parse HEAD >expected &&
 	git rev-parse master2 >actual &&
 	test_cmp expected actual
-- 
2.13.3
Junio C Hamano· Aug 7, 2017, 17:49 UTC · re: Phillip Wood · lore

Re: [PATCH v1] am: fix signoff when other trailers are present

Phillip Wood <phillip.wood@talktalk.net> writes:
Show 24 quoted lines
> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>
> If there was no 'Signed-off-by:' trailer but another trailer such as
> 'Reported-by:' then 'git am --signoff' would add a blank line between
> the existing trailers and the added 'Signed-off-by:' line. e.g.
>
>     Rebase accepts '--rerere-autoupdate' as an option but only honors
>     it if '-m' is also given. Fix it for a non-interactive rebase by
>     passing on the option to 'git am' and 'git cherry-pick'.
>
>     Reported-by: Junio C Hamano <gitster@pobox.com>
>
>     Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
>
> Fix by using the code provided for this purpose in sequencer.c.
> Change the tests so that they check the formatting of the
> 'Signed-off-by:' lines rather than just grepping for them.
>
> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> ---
> I'm not sure if this should be calling ignore_non_trailer() or not -
> git commit does but git cherry-pick does not. This follows commit and
> cherry-pick in ignoring the value of trailer.ifExists for the signoff.
> I'm a bit surprised they do that - is it correct?

These built-in "sign-off" machinery long predates the "trailer" thing, so I am not surprised if they do not behave the same. I vaguely recall having discussions on this earlier this year, but details escape me.

Asking Jonathan, who did a series that ends at 44dc738a ("sequencer: add newline before adding footers", 2017-04-26), and Christian, who is the original contirbutor to the "trailer" machinery, for input.

Jonathan Tan· Aug 7, 2017, 18:08 UTC · re: Junio C Hamano · lore

Re: [PATCH v1] am: fix signoff when other trailers are present

On Mon, 07 Aug 2017 10:49:28 -0700 Junio C Hamano <gitster@pobox.com> wrote:

Show 35 quoted lines
> Phillip Wood <phillip.wood@talktalk.net> writes:
> 
> > From: Phillip Wood <phillip.wood@dunelm.org.uk>
> >
> > If there was no 'Signed-off-by:' trailer but another trailer such as
> > 'Reported-by:' then 'git am --signoff' would add a blank line between
> > the existing trailers and the added 'Signed-off-by:' line. e.g.
> >
> >     Rebase accepts '--rerere-autoupdate' as an option but only honors
> >     it if '-m' is also given. Fix it for a non-interactive rebase by
> >     passing on the option to 'git am' and 'git cherry-pick'.
> >
> >     Reported-by: Junio C Hamano <gitster@pobox.com>
> >
> >     Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> >
> > Fix by using the code provided for this purpose in sequencer.c.
> > Change the tests so that they check the formatting of the
> > 'Signed-off-by:' lines rather than just grepping for them.
> >
> > Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> > ---
> > I'm not sure if this should be calling ignore_non_trailer() or not -
> > git commit does but git cherry-pick does not. This follows commit and
> > cherry-pick in ignoring the value of trailer.ifExists for the signoff.
> > I'm a bit surprised they do that - is it correct?
> 
> These built-in "sign-off" machinery long predates the "trailer"
> thing, so I am not surprised if they do not behave the same.  I
> vaguely recall having discussions on this earlier this year, but
> details escape me.  
> 
> Asking Jonathan, who did a series that ends at 44dc738a ("sequencer:
> add newline before adding footers", 2017-04-26), and Christian, who
> is the original contirbutor to the "trailer" machinery, for input.

Regarding ignore_non_trailer(), I believe that's because "git commit" wants to tolerate blank lines and comments after the "real" commit message, whereas "git cherry-pick" doesn't need to. As far as I can tell, this "git am" case is similar to "git cherry-pick".

Regarding trailer.ifExists, the then existing behavior was to refrain from writing a new sign-off line only if it would be a duplicate of the last one, regardless of trailer.ifExists (as Junio says, back then, the sign-off mechanism and the trailer mechanism were independent). I preserved that behavior.

Phillip Wood· Aug 8, 2017, 10:19 UTC · re: Jonathan Tan · lore

Re: [PATCH v1] am: fix signoff when other trailers are present

On 07/08/17 19:08, Jonathan Tan wrote:
Show 50 quoted lines
> On Mon, 07 Aug 2017 10:49:28 -0700
> Junio C Hamano <gitster@pobox.com> wrote:
> 
>> Phillip Wood <phillip.wood@talktalk.net> writes:
>>
>>> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>>>
>>> If there was no 'Signed-off-by:' trailer but another trailer such as
>>> 'Reported-by:' then 'git am --signoff' would add a blank line between
>>> the existing trailers and the added 'Signed-off-by:' line. e.g.
>>>
>>>     Rebase accepts '--rerere-autoupdate' as an option but only honors
>>>     it if '-m' is also given. Fix it for a non-interactive rebase by
>>>     passing on the option to 'git am' and 'git cherry-pick'.
>>>
>>>     Reported-by: Junio C Hamano <gitster@pobox.com>
>>>
>>>     Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
>>>
>>> Fix by using the code provided for this purpose in sequencer.c.
>>> Change the tests so that they check the formatting of the
>>> 'Signed-off-by:' lines rather than just grepping for them.
>>>
>>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
>>> ---
>>> I'm not sure if this should be calling ignore_non_trailer() or not -
>>> git commit does but git cherry-pick does not. This follows commit and
>>> cherry-pick in ignoring the value of trailer.ifExists for the signoff.
>>> I'm a bit surprised they do that - is it correct?
>>
>> These built-in "sign-off" machinery long predates the "trailer"
>> thing, so I am not surprised if they do not behave the same.  I
>> vaguely recall having discussions on this earlier this year, but
>> details escape me.  
>>
>> Asking Jonathan, who did a series that ends at 44dc738a ("sequencer:
>> add newline before adding footers", 2017-04-26), and Christian, who
>> is the original contirbutor to the "trailer" machinery, for input.
> 
> Regarding ignore_non_trailer(), I believe that's because "git commit"
> wants to tolerate blank lines and comments after the "real" commit
> message, whereas "git cherry-pick" doesn't need to. As far as I can
> tell, this "git am" case is similar to "git cherry-pick".
> 
> Regarding trailer.ifExists, the then existing behavior was to refrain
> from writing a new sign-off line only if it would be a duplicate of the
> last one, regardless of trailer.ifExists (as Junio says, back then, the
> sign-off mechanism and the trailer mechanism were independent). I
> preserved that behavior.
> 
Hi Jonathan
Thanks for the background. I'll remove the call to ignore_non_trailer()
Phillip Wood· Aug 8, 2017, 10:22 UTC · re: Junio C Hamano · lore

Re: [PATCH v1] am: fix signoff when other trailers are present

On 07/08/17 18:49, Junio C Hamano wrote:
Show 31 quoted lines
> Phillip Wood <phillip.wood@talktalk.net> writes:
> 
>> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>>
>> If there was no 'Signed-off-by:' trailer but another trailer such as
>> 'Reported-by:' then 'git am --signoff' would add a blank line between
>> the existing trailers and the added 'Signed-off-by:' line. e.g.
>>
>>     Rebase accepts '--rerere-autoupdate' as an option but only honors
>>     it if '-m' is also given. Fix it for a non-interactive rebase by
>>     passing on the option to 'git am' and 'git cherry-pick'.
>>
>>     Reported-by: Junio C Hamano <gitster@pobox.com>
>>
>>     Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
>>
>> Fix by using the code provided for this purpose in sequencer.c.
>> Change the tests so that they check the formatting of the
>> 'Signed-off-by:' lines rather than just grepping for them.
>>
>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
>> ---
>> I'm not sure if this should be calling ignore_non_trailer() or not -
>> git commit does but git cherry-pick does not. This follows commit and
>> cherry-pick in ignoring the value of trailer.ifExists for the signoff.
>> I'm a bit surprised they do that - is it correct?
> 
> These built-in "sign-off" machinery long predates the "trailer"
> thing, so I am not surprised if they do not behave the same.  I
> vaguely recall having discussions on this earlier this year, but
> details escape me.  

Ah, that explains it. I did a quick search on public-inbox but didn't turn up any obvious discussion about --signoff and trailer config. If that's the behavior people are used to then lets leave it as it is unless there is a call for it to be changed.

> Asking Jonathan, who did a series that ends at 44dc738a ("sequencer:
> add newline before adding footers", 2017-04-26), and Christian, who
> is the original contirbutor to the "trailer" machinery, for input.
> 
Phillip Wood· Aug 8, 2017, 10:25 UTC · re: Phillip Wood · lore

[PATCH v2] am: fix signoff when other trailers are present

From: Phillip Wood <phillip.wood@dunelm.org.uk>

If there was no 'Signed-off-by:' trailer but another trailer such as 'Reported-by:' then 'git am --signoff' would add a blank line between the existing trailers and the added 'Signed-off-by:' line. e.g.

    Rebase accepts '--rerere-autoupdate' as an option but only honors
    it if '-m' is also given. Fix it for a non-interactive rebase by
    passing on the option to 'git am' and 'git cherry-pick'.
    Reported-by: Junio C Hamano <gitster@pobox.com>
    Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>

Fix by using the code provided for this purpose in sequencer.c. Change the tests so that they check the formatting of the 'Signed-off-by:' lines rather than just grepping for them.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
I've removed the call to ignore_non_trailer(), otherwise this is
unchanged from v1
 builtin/am.c  | 26 +------------------
 t/t4150-am.sh | 83 +++++++++++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 64 insertions(+), 45 deletions(-)
Show changes to 2 files +64 −45

builtin/am.c, t/t4150-am.sh

diff --git a/builtin/am.c b/builtin/am.c
index c973bd96dcb5d630d56e935733bfa4530ccd2872..3aaef59676452fd2e7c6d4a375dc7c95558744c6 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1181,34 +1181,10 @@ static void NORETURN die_user_resolve(const struct am_state *state)
  */
 static void am_append_signoff(struct am_state *state)
 {
-	char *cp;
-	struct strbuf mine = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
 
 	strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
-
-	/* our sign-off */
-	strbuf_addf(&mine, "\n%s%s\n",
-		    sign_off_header,
-		    fmt_name(getenv("GIT_COMMITTER_NAME"),
-			     getenv("GIT_COMMITTER_EMAIL")));
-
-	/* Does sb end with it already? */
-	if (mine.len < sb.len &&
-	    !strcmp(mine.buf, sb.buf + sb.len - mine.len))
-		goto exit; /* no need to duplicate */
-
-	/* Does it have any Signed-off-by: in the text */
-	for (cp = sb.buf;
-	     cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL;
-	     cp = strchr(cp, '\n')) {
-		if (sb.buf == cp || cp[-1] == '\n')
-			break;
-	}
-
-	strbuf_addstr(&sb, mine.buf + !!cp);
-exit:
-	strbuf_release(&mine);
+	append_signoff(&sb, 0, 0);
 	state->msg = strbuf_detach(&sb, &state->msg_len);
 }
 
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 44807e218d7016f58bd41b89af71104a37f31a8b..73b67b4280b99e0328e201e6b69c3d88b766ea84 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -40,6 +40,8 @@ test_expect_success 'setup: messages' '
 	dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
 	dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
 	feugait nulla facilisi.
+
+	Reported-by: A N Other <a.n.other@example.com>
 	EOF
 
 	cat >failmail <<-\EOF &&
@@ -93,7 +95,7 @@ test_expect_success setup '
 	echo world >>file &&
 	git add file &&
 	test_tick &&
-	git commit -s -F msg &&
+	git commit -F msg &&
 	git tag second &&
 
 	git format-patch --stdout first >patch1 &&
@@ -124,8 +126,6 @@ test_expect_success setup '
 		echo "Date: $GIT_AUTHOR_DATE" &&
 		echo &&
 		sed -e "1,2d" msg &&
-		echo &&
-		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
 		echo "---" &&
 		git diff-tree --no-commit-id --stat -p second
 	} >patch1-stgit.eml &&
@@ -144,8 +144,6 @@ test_expect_success setup '
 		echo "# Parent  $_z40" &&
 		cat msg &&
 		echo &&
-		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
-		echo &&
 		git diff-tree --no-commit-id -p second
 	} >patch1-hg.eml &&
 
@@ -470,13 +468,15 @@ test_expect_success 'am --signoff adds Signed-off-by: line' '
 	git reset --hard &&
 	git checkout -b master2 first &&
 	git am --signoff <patch2 &&
-	printf "%s\n" "$signoff" >expected &&
-	echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected &&
-	git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
-	test_cmp expected actual &&
-	echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
-	git cat-file commit HEAD | grep "Signed-off-by:" >actual &&
-	test_cmp expected actual
+	{
+		printf "third\n\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL"
+	} >expected-log &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
 '
 
 test_expect_success 'am stays in branch' '
@@ -486,17 +486,60 @@ test_expect_success 'am stays in branch' '
 '
 
 test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
-	git format-patch --stdout HEAD^ >patch3 &&
-	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
-	rm -fr .git/rebase-apply &&
-	git reset --hard &&
-	git checkout HEAD^ &&
-	git am --signoff patch4 &&
-	git cat-file commit HEAD >actual &&
-	test $(grep -c "^Signed-off-by:" actual) -eq 1
+	git format-patch --stdout first >patch3 &&
+	git reset --hard first &&
+	git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
+'
+
+test_expect_success 'am --signoff adds Signed-off-by: if another author is preset' '
+	NAME="A N Other" &&
+	EMAIL="a.n.other@example.com" &&
+	{
+		printf "third\n\nSigned-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL"
+	} >expected-log &&
+	git reset --hard first &&
+	GIT_COMMITTER_NAME="$NAME" GIT_COMMITTER_EMAIL="$EMAIL" \
+		git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
+'
+
+test_expect_success 'am --signoff duplicates Signed-off-by: if it is not the last one' '
+	NAME="A N Other" &&
+	EMAIL="a.n.other@example.com" &&
+	{
+		printf "third\n\nSigned-off-by: %s <%s>\n\
+Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\
+Signed-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL"
+	} >expected-log &&
+	git format-patch --stdout first >patch3 &&
+	git reset --hard first &&
+	git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
 '
 
 test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
+	git format-patch --stdout HEAD^ >tmp &&
+	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," tmp >patch4 &&
+	git reset --hard HEAD^ &&
+	git am <patch4 &&
 	git rev-parse HEAD >expected &&
 	git rev-parse master2 >actual &&
 	test_cmp expected actual
-- 
2.13.3
Junio C Hamano· Aug 8, 2017, 19:37 UTC · re: Phillip Wood · lore

Re: [PATCH v2] am: fix signoff when other trailers are present

Phillip Wood <phillip.wood@talktalk.net> writes:
Show 17 quoted lines
>  test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
> -	git format-patch --stdout HEAD^ >patch3 &&
> -	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
> -	rm -fr .git/rebase-apply &&
> -	git reset --hard &&
> -	git checkout HEAD^ &&
> -	git am --signoff patch4 &&
> -	git cat-file commit HEAD >actual &&
> -	test $(grep -c "^Signed-off-by:" actual) -eq 1
> +	git format-patch --stdout first >patch3 &&
> +	git reset --hard first &&
> +	git am --signoff <patch3 &&
> +	git log --pretty=%B -2 HEAD >actual &&
> +	test_cmp expected-log actual
> +'
> +
> +test_expect_success 'am --signoff adds Signed-off-by: if another author is preset' '
Present?

I think the motivation for adding this is to make sure that what the previous test checks will be true only when the one we are about to add is already at the end. So perhaps the previous test needs to be retitled from "if already there" to something a bit tighter, e.g. "if already at the end"?

Also, strictly speaking, I think this isn't testing if another author is present---it is specifying what should happen when the last one is not us.

Show 6 quoted lines
> +	NAME="A N Other" &&
> +	EMAIL="a.n.other@example.com" &&
> +	{
> +		printf "third\n\nSigned-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
> +			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
> +			"$NAME" "$EMAIL" &&
A "printf" tip: you can do
	printf "third\n\n"
	printf "S-o-b: %s <%s>\n" A B C D

to get two lines of the latter. That would clarify what the next test does which wants to add three of them.

Show 43 quoted lines
> +		cat msg &&
> +		printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
> +			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
> +			"$NAME" "$EMAIL"
> +	} >expected-log &&
> +	git reset --hard first &&
> +	GIT_COMMITTER_NAME="$NAME" GIT_COMMITTER_EMAIL="$EMAIL" \
> +		git am --signoff <patch3 &&
> +	git log --pretty=%B -2 HEAD >actual &&
> +	test_cmp expected-log actual
> +'
> +
> +test_expect_success 'am --signoff duplicates Signed-off-by: if it is not the last one' '
> +	NAME="A N Other" &&
> +	EMAIL="a.n.other@example.com" &&
> +	{
> +		printf "third\n\nSigned-off-by: %s <%s>\n\
> +Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \
> +			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
> +			"$NAME" "$EMAIL" \
> +			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" &&
> +		cat msg &&
> +		printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\
> +Signed-off-by: %s <%s>\n\n" \
> +			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
> +			"$NAME" "$EMAIL" \
> +			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL"
> +	} >expected-log &&
> +	git format-patch --stdout first >patch3 &&
> +	git reset --hard first &&
> +	git am --signoff <patch3 &&
> +	git log --pretty=%B -2 HEAD >actual &&
> +	test_cmp expected-log actual
>  '
>  
>  test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
> +	git format-patch --stdout HEAD^ >tmp &&
> +	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," tmp >patch4 &&
> +	git reset --hard HEAD^ &&
> +	git am <patch4 &&
>  	git rev-parse HEAD >expected &&
>  	git rev-parse master2 >actual &&
>  	test_cmp expected actual
Phillip Wood· Aug 12, 2017, 15:55 UTC · re: Phillip Wood · lore

[PATCH v3] am: fix signoff when other trailers are present

From: Phillip Wood <phillip.wood@dunelm.org.uk>

If there was no 'Signed-off-by:' trailer but another trailer such as 'Reported-by:' then 'git am --signoff' would add a blank line between the existing trailers and the added 'Signed-off-by:' line. e.g.

    Rebase accepts '--rerere-autoupdate' as an option but only honors
    it if '-m' is also given. Fix it for a non-interactive rebase by
    passing on the option to 'git am' and 'git cherry-pick'.
    Reported-by: Junio C Hamano <gitster@pobox.com>
    Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>

Fix by using the code provided for this purpose in sequencer.c. Change the tests so that they check the formatting of the 'Signed-off-by:' lines rather than just grepping for them.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
I've changed the test names and cleaned up the printf in the tests as
Junio suggested. Otherwise this is unchanged from the previous version.
 builtin/am.c  | 26 +----------------
 t/t4150-am.sh | 89 +++++++++++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 69 insertions(+), 46 deletions(-)
Show changes to 2 files +69 −46

builtin/am.c, t/t4150-am.sh

diff --git a/builtin/am.c b/builtin/am.c
index c973bd96dcb5d630d56e935733bfa4530ccd2872..3aaef59676452fd2e7c6d4a375dc7c95558744c6 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1181,34 +1181,10 @@ static void NORETURN die_user_resolve(const struct am_state *state)
  */
 static void am_append_signoff(struct am_state *state)
 {
-	char *cp;
-	struct strbuf mine = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
 
 	strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
-
-	/* our sign-off */
-	strbuf_addf(&mine, "\n%s%s\n",
-		    sign_off_header,
-		    fmt_name(getenv("GIT_COMMITTER_NAME"),
-			     getenv("GIT_COMMITTER_EMAIL")));
-
-	/* Does sb end with it already? */
-	if (mine.len < sb.len &&
-	    !strcmp(mine.buf, sb.buf + sb.len - mine.len))
-		goto exit; /* no need to duplicate */
-
-	/* Does it have any Signed-off-by: in the text */
-	for (cp = sb.buf;
-	     cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL;
-	     cp = strchr(cp, '\n')) {
-		if (sb.buf == cp || cp[-1] == '\n')
-			break;
-	}
-
-	strbuf_addstr(&sb, mine.buf + !!cp);
-exit:
-	strbuf_release(&mine);
+	append_signoff(&sb, 0, 0);
 	state->msg = strbuf_detach(&sb, &state->msg_len);
 }
 
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 44807e218d7016f58bd41b89af71104a37f31a8b..ae23adfb7548a4a28aaa52e689094f727f0f4cf2 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -40,6 +40,8 @@ test_expect_success 'setup: messages' '
 	dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
 	dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
 	feugait nulla facilisi.
+
+	Reported-by: A N Other <a.n.other@example.com>
 	EOF
 
 	cat >failmail <<-\EOF &&
@@ -93,7 +95,7 @@ test_expect_success setup '
 	echo world >>file &&
 	git add file &&
 	test_tick &&
-	git commit -s -F msg &&
+	git commit -F msg &&
 	git tag second &&
 
 	git format-patch --stdout first >patch1 &&
@@ -124,8 +126,6 @@ test_expect_success setup '
 		echo "Date: $GIT_AUTHOR_DATE" &&
 		echo &&
 		sed -e "1,2d" msg &&
-		echo &&
-		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
 		echo "---" &&
 		git diff-tree --no-commit-id --stat -p second
 	} >patch1-stgit.eml &&
@@ -144,8 +144,6 @@ test_expect_success setup '
 		echo "# Parent  $_z40" &&
 		cat msg &&
 		echo &&
-		echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" &&
-		echo &&
 		git diff-tree --no-commit-id -p second
 	} >patch1-hg.eml &&
 
@@ -470,13 +468,15 @@ test_expect_success 'am --signoff adds Signed-off-by: line' '
 	git reset --hard &&
 	git checkout -b master2 first &&
 	git am --signoff <patch2 &&
-	printf "%s\n" "$signoff" >expected &&
-	echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected &&
-	git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
-	test_cmp expected actual &&
-	echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
-	git cat-file commit HEAD | grep "Signed-off-by:" >actual &&
-	test_cmp expected actual
+	{
+		printf "third\n\nSigned-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\n\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL"
+	} >expected-log &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
 '
 
 test_expect_success 'am stays in branch' '
@@ -485,18 +485,65 @@ test_expect_success 'am stays in branch' '
 	test_cmp expected actual
 '
 
-test_expect_success 'am --signoff does not add Signed-off-by: line if already there' '
-	git format-patch --stdout HEAD^ >patch3 &&
-	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
-	rm -fr .git/rebase-apply &&
-	git reset --hard &&
-	git checkout HEAD^ &&
-	git am --signoff patch4 &&
-	git cat-file commit HEAD >actual &&
-	test $(grep -c "^Signed-off-by:" actual) -eq 1
+test_expect_success 'am --signoff does not add Signed-off-by: line if it matches the last trailer' '
+	git format-patch --stdout first >patch3 &&
+	git reset --hard first &&
+	git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
+'
+
+test_expect_success 'am --signoff adds Signed-off-by: if it does not match the last trailer' '
+	NAME="A N Other" &&
+	EMAIL="a.n.other@example.com" &&
+	{
+		printf "third\n\n" &&
+		printf "Signed-off-by: %s <%s>\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" &&
+		printf "\n" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" &&
+		printf "\n"
+	} >expected-log &&
+	git reset --hard first &&
+	GIT_COMMITTER_NAME="$NAME" GIT_COMMITTER_EMAIL="$EMAIL" \
+		git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
+'
+
+test_expect_success 'am --signoff duplicates Signed-off-by: if it does not match the last trailer' '
+	NAME="A N Other" &&
+	EMAIL="a.n.other@example.com" &&
+	{
+		printf "third\n\n" &&
+		printf "Signed-off-by: %s <%s>\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" &&
+		printf "\n" &&
+		cat msg &&
+		printf "Signed-off-by: %s <%s>\n" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \
+			"$NAME" "$EMAIL" \
+			"$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL"
+		printf "\n"
+	} >expected-log &&
+	git format-patch --stdout first >patch3 &&
+	git reset --hard first &&
+	git am --signoff <patch3 &&
+	git log --pretty=%B -2 HEAD >actual &&
+	test_cmp expected-log actual
 '
 
 test_expect_success 'am without --keep removes Re: and [PATCH] stuff' '
+	git format-patch --stdout HEAD^ >tmp &&
+	sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," tmp >patch4 &&
+	git reset --hard HEAD^ &&
+	git am <patch4 &&
 	git rev-parse HEAD >expected &&
 	git rev-parse master2 >actual &&
 	test_cmp expected actual
-- 
2.13.3

← back to recent threads