git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 0/7] teach branch-specific options for format-patch
@ 2019-05-05 16:24 Denton Liu
  2019-05-05 16:24 ` [PATCH 1/7] t4014: clean up style Denton Liu
                   ` (8 more replies)
  0 siblings, 9 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

Currently, format-patch only accepts branch.<name>.description as a
branch-specific configuration variable. However, there are many other
options which would be useful to have on a branch-by-branch basis,
namely cover letter subject and To: and Cc: headers.

Teach format-patch to recognise these branch-specific configuration
options.

Note that this patchset[1] was created using these new configuration
options:

	[branch "submitted/fix-revisions-txt"]
		coverSubject = "cleanup revisions.txt"
		cc = "Andreas Heiduk <asheiduk@gmail.com>"
		cc = "Duy Nguyen <pclouds@gmail.com>"
		cc = "Junio C Hamano <gitster@pobox.com>"

So this is a live example of this patchset working in practice.

[1]: https://public-inbox.org/git/cover.1557072286.git.liu.denton@gmail.com/

Denton Liu (7):
  t4014: clean up style
  Doc: add more detail for git-format-patch
  branch.c: extract read_branch_config function
  format-patch: make cover letter subject configurable
  format-patch: move extra_headers logic later
  string-list: create string_list_append_all
  format-patch: read branch-specific To: and Cc: headers

 Documentation/config/branch.txt    |  10 +
 Documentation/git-format-patch.txt |  33 +-
 branch.c                           |  14 +-
 branch.h                           |   5 +
 builtin/log.c                      | 147 ++++--
 string-list.c                      |   9 +
 string-list.h                      |   7 +
 t/t4014-format-patch.sh            | 708 +++++++++++++++++------------
 t/t9902-completion.sh              |   5 +-
 9 files changed, 590 insertions(+), 348 deletions(-)

-- 
2.21.0.1049.geb646f7864


^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH 1/7] t4014: clean up style
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
@ 2019-05-05 16:24 ` Denton Liu
  2019-05-05 16:24 ` [PATCH 2/7] Doc: add more detail for git-format-patch Denton Liu
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

In Git's tests, there is typically no space between the redirection
operator and the filename. Remove these spaces.

Since output is silenced when running without `-v` and debugging
output is useful with `-v`, remove redirections to /dev/null.

Change here-docs from `<<\EOF` to `<<-\EOF` so that they can be indented
along with the rest of the test case.

Finally, refactor to remove Git commands upstream of pipe. This way, if
an invocation of a Git command fails, the return code won't be lost.
Keep upstream non-Git commands since we have to assume a base level of
sanity.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)

diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 909c743c13..d05cd256c7 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -34,7 +34,8 @@ test_expect_success setup '
 	git commit -m "Side changes #3 with \\n backslash-n in it." &&
 
 	git checkout master &&
-	git diff-tree -p C2 | git apply --index &&
+	git diff-tree -p C2 >patch &&
+	git apply --index <patch &&
 	test_tick &&
 	git commit -m "Master accepts moral equivalent of #2"
 
@@ -77,7 +78,7 @@ test_expect_success "format-patch doesn't consider merge commits" '
 	git checkout -b merger master &&
 	test_tick &&
 	git merge --no-ff slave &&
-	cnt=$(git format-patch -3 --stdout | grep "^From " | wc -l) &&
+	cnt=$(git format-patch -3 --stdout >patch && grep "^From " patch | wc -l) &&
 	test $cnt = 3
 '
 
@@ -85,21 +86,22 @@ test_expect_success "format-patch result applies" '
 
 	git checkout -b rebuild-0 master &&
 	git am -3 patch0 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success "format-patch --ignore-if-in-upstream result applies" '
 
 	git checkout -b rebuild-1 master &&
 	git am -3 patch1 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success 'commit did not screw up the log message' '
 
-	git cat-file commit side | grep "^Side .* with .* backslash-n"
+	git cat-file commit side >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -112,7 +114,8 @@ test_expect_success 'format-patch did not screw up the log message' '
 
 test_expect_success 'replay did not screw up the log message' '
 
-	git cat-file commit rebuild-1 | grep "^Side .* with .* backslash-n"
+	git cat-file commit rebuild-1 >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -122,8 +125,8 @@ test_expect_success 'extra headers' '
 " &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>
 " &&
-	git format-patch --stdout master..side > patch2 &&
-	sed -e "/^\$/q" patch2 > hdrs2 &&
+	git format-patch --stdout master..side >patch2 &&
+	sed -e "/^\$/q" patch2 >hdrs2 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "To: S E Cipient <scipient@example.com>" &&
-	git format-patch --stdout master..side > patch4 &&
-	sed -e "/^\$/q" patch4 > hdrs4 &&
+	git format-patch --stdout master..side >patch4 &&
+	sed -e "/^\$/q" patch4 >hdrs4 &&
 	grep "^To: R E Cipient <rcipient@example.com>,\$" hdrs4 &&
 	grep "^ *S E Cipient <scipient@example.com>\$" hdrs4
 '
@@ -153,72 +156,82 @@ test_expect_success 'extra headers with multiple To:s' '
 test_expect_success 'additional command line cc (ascii)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch5
+	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" patch5
+	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch7 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs7 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch8
+	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch8
+	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch8
+	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
 # check_patch <patch>: Verify that <patch> looks like a half-sane
@@ -231,52 +244,52 @@ check_patch () {
 
 test_expect_success 'format.from=false' '
 
-	git -c format.from=false format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
+	git -c format.from=false format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
 	check_patch patch &&
-	! grep "^From: C O Mitter <committer@example.com>\$" patch
+	! grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from=true' '
 
-	git -c format.from=true format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: C O Mitter <committer@example.com>\$" patch
+	git -c format.from=true format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
@@ -284,31 +297,31 @@ test_expect_success '--no-to and --to replaces config.to' '
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <someone@out.there>\$" patch11 &&
-	grep "^To: Someone Else <else@out.there>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
 '
 
 test_expect_success 'multiple files' '
@@ -338,7 +351,7 @@ test_expect_success 'reroll count (-v)' '
 check_threading () {
 	expect="$1" &&
 	shift &&
-	(git format-patch --stdout "$@"; echo $? > status.out) |
+	(git format-patch --stdout "$@"; echo $? >status.out) |
 	# Prints everything between the Message-ID and In-Reply-To,
 	# and replaces all Message-ID-lookalikes by a sequence number
 	perl -ne '
@@ -353,12 +366,12 @@ check_threading () {
 			print;
 		}
 		print "---\n" if /^From /i;
-	' > actual &&
+	' >actual &&
 	test 0 = "$(cat status.out)" &&
 	test_cmp "$expect" actual
 }
 
-cat >> expect.no-threading <<EOF
+cat >>expect.no-threading <<EOF
 ---
 ---
 ---
@@ -369,7 +382,7 @@ test_expect_success 'no threading' '
 	check_threading expect.no-threading master
 '
 
-cat > expect.thread <<EOF
+cat >expect.thread <<EOF
 ---
 Message-Id: <0>
 ---
@@ -386,7 +399,7 @@ test_expect_success 'thread' '
 	check_threading expect.thread --thread master
 '
 
-cat > expect.in-reply-to <<EOF
+cat >expect.in-reply-to <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -406,7 +419,7 @@ test_expect_success 'thread in-reply-to' '
 		--thread master
 '
 
-cat > expect.cover-letter <<EOF
+cat >expect.cover-letter <<EOF
 ---
 Message-Id: <0>
 ---
@@ -427,7 +440,7 @@ test_expect_success 'thread cover-letter' '
 	check_threading expect.cover-letter --cover-letter --thread master
 '
 
-cat > expect.cl-irt <<EOF
+cat >expect.cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -459,7 +472,7 @@ test_expect_success 'thread explicit shallow' '
 		--in-reply-to="<test.message>" --thread=shallow master
 '
 
-cat > expect.deep <<EOF
+cat >expect.deep <<EOF
 ---
 Message-Id: <0>
 ---
@@ -477,7 +490,7 @@ test_expect_success 'thread deep' '
 	check_threading expect.deep --thread=deep master
 '
 
-cat > expect.deep-irt <<EOF
+cat >expect.deep-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -500,7 +513,7 @@ test_expect_success 'thread deep in-reply-to' '
 		--in-reply-to="<test.message>" master
 '
 
-cat > expect.deep-cl <<EOF
+cat >expect.deep-cl <<EOF
 ---
 Message-Id: <0>
 ---
@@ -524,7 +537,7 @@ test_expect_success 'thread deep cover-letter' '
 	check_threading expect.deep-cl --cover-letter --thread=deep master
 '
 
-cat > expect.deep-cl-irt <<EOF
+cat >expect.deep-cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -600,7 +613,7 @@ test_expect_success 'cover-letter inherits diff options' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
   This is an excessively long subject line for a message due to the
     habit some projects have of not having a short, one-line subject at
     the start of the commit message, but rather sticking a whole
@@ -613,12 +626,12 @@ EOF
 test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 
 	git format-patch --cover-letter -2 &&
-	sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
+	sed -e "1,/A U Thor/d" -e "/^\$/q" <0000-cover-letter.patch >output &&
 	test_cmp expect output
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 index $before..$after 100644
 --- a/file
 +++ b/file
@@ -640,7 +653,7 @@ test_expect_success 'format-patch respects -U' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 
 diff --git a/file b/file
 index $before..$after 100644
@@ -656,7 +669,7 @@ EOF
 test_expect_success 'format-patch -p suppresses stat' '
 
 	git format-patch -p -2 &&
-	sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+	sed -e "1,/^\$/d" -e "/^+5/q" <0001-This-is-an-excessively-long-subject-line-for-a-messa.patch >output &&
 	test_cmp expect output
 
 '
@@ -711,7 +724,7 @@ test_expect_success 'format-patch from a subdirectory (3)' '
 '
 
 test_expect_success 'format-patch --in-reply-to' '
-	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" >patch8 &&
 	grep "^In-Reply-To: <baz@foo.bar>" patch8 &&
 	grep "^References: <baz@foo.bar>" patch8
 '
@@ -732,20 +745,20 @@ test_expect_success 'format-patch --notes --signoff' '
 	sed "1,/^---$/d" out | grep "test message"
 '
 
-echo "fatal: --name-only does not make sense" > expect.name-only
-echo "fatal: --name-status does not make sense" > expect.name-status
-echo "fatal: --check does not make sense" > expect.check
+echo "fatal: --name-only does not make sense" >expect.name-only
+echo "fatal: --name-status does not make sense" >expect.name-status
+echo "fatal: --check does not make sense" >expect.check
 
 test_expect_success 'options no longer allowed for format-patch' '
-	test_must_fail git format-patch --name-only 2> output &&
+	test_must_fail git format-patch --name-only 2>output &&
 	test_i18ncmp expect.name-only output &&
-	test_must_fail git format-patch --name-status 2> output &&
+	test_must_fail git format-patch --name-status 2>output &&
 	test_i18ncmp expect.name-status output &&
-	test_must_fail git format-patch --check 2> output &&
+	test_must_fail git format-patch --check 2>output &&
 	test_i18ncmp expect.check output'
 
 test_expect_success 'format-patch --numstat should produce a patch' '
-	git format-patch --numstat --stdout master..side > output &&
+	git format-patch --numstat --stdout master..side >output &&
 	test 5 = $(grep "^diff --git a/" output | wc -l)'
 
 test_expect_success 'format-patch -- <path>' '
@@ -757,20 +770,22 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' '
 	git format-patch --ignore-if-in-upstream HEAD
 '
 
-git_version="$(git --version | sed "s/.* //")"
+git_version="$(git --version >version && sed "s/.* //" version)"
 
 signature() {
 	printf "%s\n%s\n\n" "-- " "${1:-$git_version}"
 }
 
 test_expect_success 'format-patch default signature' '
-	git format-patch --stdout -1 | tail -n 3 >output &&
+	git format-patch --stdout -1 >patch &&
+	tail -n 3 patch >output &&
 	signature >expect &&
 	test_cmp expect output
 '
 
 test_expect_success 'format-patch --signature' '
-	git format-patch --stdout --signature="my sig" -1 | tail -n 3 >output &&
+	git format-patch --stdout --signature="my sig" -1 >patch &&
+	tail -n 3 patch >output &&
 	signature "my sig" >expect &&
 	test_cmp expect output
 '
@@ -1161,282 +1176,282 @@ append_signoff()
 
 test_expect_success 'signoff: commit with no body' '
 	append_signoff </dev/null >actual &&
-	cat <<\EOF | sed "s/EOL$//" >expected &&
-4:Subject: [PATCH] EOL
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject' '
 	echo subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject that does not end with NL' '
 	printf subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs and no trailing NL' '
 	printf "subject\n\nbody" | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: misc conforming footer elements' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
-Tested-by: Some One <someone@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-15:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <someone@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	15:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff-alike' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-Fooled-by-me: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-I want to mention about Signed-off-by: here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:I want to mention about Signed-off-by: here.
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	I want to mention about Signed-off-by: here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:I want to mention about Signed-off-by: here.
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff (2)' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-My unfortunate
-Signed-off-by: example happens to be wrapped here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:Signed-off-by: example happens to be wrapped here.
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	My unfortunate
+	Signed-off-by: example happens to be wrapped here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:Signed-off-by: example happens to be wrapped here.
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Signed-off-by: my@house
-Signed-off-by: your@house
+	Signed-off-by: my@house
+	Signed-off-by: your@house
 
-A lot of houses.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: my@house
-10:Signed-off-by: your@house
-11:
-13:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	A lot of houses.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: my@house
+	10:Signed-off-by: your@house
+	11:
+	13:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
 	printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff NOT at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	12:Signed-off-by: my@house
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: tolerate garbage in conforming footer' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Tested-by: my@house
-Some Trash
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: respect trailer config' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual &&
 
 	test_config trailer.Myfooter.ifexists add &&
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Reviewed-id: Noone
-Tested-by: my@house
-Change-id: Ideadbeef
-Signed-off-by: C O Mitter <committer@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <committer@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
@@ -1452,42 +1467,42 @@ test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (2)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter rebuild-1~2..rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (3)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter ^master rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (4)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master.. >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (5)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 HEAD >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (6)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter with nothing' '
@@ -1541,7 +1556,8 @@ test_expect_success 'format-patch format.outputDirectory option' '
 	test_config format.outputDirectory patches &&
 	rm -fr patches &&
 	git format-patch master..side &&
-	test $(git rev-list master..side | wc -l) -eq $(ls patches | wc -l)
+	git rev-list master..side >list &&
+	test_line_count = $(ls patches | wc -l) list
 '
 
 test_expect_success 'format-patch -o overrides format.outputDirectory' '
@@ -1554,13 +1570,21 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
 
 test_expect_success 'format-patch --base' '
 	git checkout side &&
-	git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual1 &&
-	git format-patch --stdout --base=HEAD~3 HEAD~.. | tail -n 7 >actual2 &&
+	git format-patch --stdout --base=HEAD~3 -1 >patch &&
+	tail -n 7 patch >actual1 &&
+	git format-patch --stdout --base=HEAD~3 HEAD~.. >patch &&
+	tail -n 7 patch >actual2 &&
 	echo >expected &&
 	echo "base-commit: $(git rev-parse HEAD~3)" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	signature >> expected &&
+	git show --patch HEAD~2 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	git show --patch HEAD~1 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	signature >>expected &&
 	test_cmp expected actual1 &&
 	test_cmp expected actual2
 '
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH 2/7] Doc: add more detail for git-format-patch
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
  2019-05-05 16:24 ` [PATCH 1/7] t4014: clean up style Denton Liu
@ 2019-05-05 16:24 ` Denton Liu
  2019-05-05 16:24 ` [PATCH 3/7] branch.c: extract read_branch_config function Denton Liu
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

In git-format-patch.txt, we were missing some key user information.
First of all, using the `--to` and `--cc` options don't override
`format.to` and `format.cc` variables, respectively. They add on to each
other. Document this.

In addition, document the special value of `--base=auto`.

Finally, while we're at it, surround option arguments with <>.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/git-format-patch.txt | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..7b71d4e2ed 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -17,9 +17,9 @@ SYNOPSIS
 		   [--signature-file=<file>]
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
-		   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
-		   [--rfc] [--subject-prefix=Subject-Prefix]
+		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
 		   [--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
@@ -158,7 +158,7 @@ Beware that the default for 'git send-email' is to thread emails
 itself.  If you want `git format-patch` to take care of threading, you
 will want to ensure that threading is disabled for `git send-email`.
 
---in-reply-to=Message-Id::
+--in-reply-to=<Message-Id>::
 	Make the first mail (or all the mails with `--no-thread`) appear as a
 	reply to the given Message-Id, which avoids breaking threads to
 	provide a new patch series.
@@ -192,13 +192,17 @@ will want to ensure that threading is disabled for `git send-email`.
 
 --to=<email>::
 	Add a `To:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
 --cc=<email>::
 	Add a `Cc:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -309,7 +313,8 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 --base=<commit>::
 	Record the base tree information to identify the state the
 	patch series applies to.  See the BASE TREE INFORMATION section
-	below for details.
+	below for details. If <commit> is equal to "auto", a base commit
+	is automatically chosen.
 
 --root::
 	Treat the revision argument as a <revision range>, even if it
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH 3/7] branch.c: extract read_branch_config function
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
  2019-05-05 16:24 ` [PATCH 1/7] t4014: clean up style Denton Liu
  2019-05-05 16:24 ` [PATCH 2/7] Doc: add more detail for git-format-patch Denton Liu
@ 2019-05-05 16:24 ` Denton Liu
  2019-05-05 16:24 ` [PATCH 4/7] format-patch: make cover letter subject configurable Denton Liu
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

In the future, we'll need to use `read_branch_config` as a generic base
for other branch-config reading functions. Extract it from
`read_branch_desc` so that it can be reused later.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 branch.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/branch.c b/branch.c
index 28b81a7e02..4b49976924 100644
--- a/branch.c
+++ b/branch.c
@@ -162,11 +162,11 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 	free(tracking.src);
 }
 
-int read_branch_desc(struct strbuf *buf, const char *branch_name)
+static int read_branch_config(struct strbuf *buf, const char *branch_name, const char *key)
 {
 	char *v = NULL;
 	struct strbuf name = STRBUF_INIT;
-	strbuf_addf(&name, "branch.%s.description", branch_name);
+	strbuf_addf(&name, "branch.%s.%s", branch_name, key);
 	if (git_config_get_string(name.buf, &v)) {
 		strbuf_release(&name);
 		return -1;
@@ -177,6 +177,11 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
 	return 0;
 }
 
+int read_branch_desc(struct strbuf *buf, const char *branch_name)
+{
+	return read_branch_config(buf, branch_name, "description");
+}
+
 /*
  * Check if 'name' can be a valid name for a branch; die otherwise.
  * Return 1 if the named branch already exists; return 0 otherwise.
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH 4/7] format-patch: make cover letter subject configurable
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
                   ` (2 preceding siblings ...)
  2019-05-05 16:24 ` [PATCH 3/7] branch.c: extract read_branch_config function Denton Liu
@ 2019-05-05 16:24 ` Denton Liu
  2019-05-05 16:24 ` [PATCH 5/7] format-patch: move extra_headers logic later Denton Liu
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

We used to populate the subject of the cover letter generated by
git-format-patch with "*** SUBJECT HERE ***". However, if a user submits
multiple patchsets, they may want to keep a consistent subject between
rerolls.

If git-format-patch is run on a branch that has
`branch.<name>.coverSubject` defined, make the cover letter's subject be
that value instead of the generic "*** SUBJECT HERE ***".

In addition, add the `--cover-subject` option to override this setting.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/branch.txt    |  4 ++++
 Documentation/git-format-patch.txt | 12 ++++++++++++
 branch.c                           |  5 +++++
 branch.h                           |  5 +++++
 builtin/log.c                      | 26 +++++++++++++++++++-------
 t/t4014-format-patch.sh            | 20 ++++++++++++++++++++
 t/t9902-completion.sh              |  5 ++++-
 7 files changed, 69 insertions(+), 8 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 019d60ede2..2bff738982 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -100,3 +100,7 @@ branch.<name>.description::
 	`git branch --edit-description`. Branch description is
 	automatically added in the format-patch cover letter or
 	request-pull summary.
+
+branch.<name>.coverSubject::
+	When format-patch generates a cover letter, use the specified
+	subject for the cover letter instead of the generic template.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 7b71d4e2ed..af7883acbe 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -19,6 +19,7 @@ SYNOPSIS
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
+		   [--cover-subject=<subject>]
 		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
@@ -170,6 +171,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--cover-subject=<subject>::
+	Instead of using the default "*** SUBJECT HERE ***" subject for
+	the cover letter, use the given <subject>.
+
 --subject-prefix=<Subject-Prefix>::
 	Instead of the standard '[PATCH]' prefix in the subject
 	line, instead use '[<Subject-Prefix>]'. This
@@ -346,6 +351,13 @@ attachments, and sign off patches with configuration variables.
 	coverletter = auto
 ------------
 
+In addition, for a specific branch, you can specify a custom cover
+letter subject.
+
+------------
+[branch "branch-name"]
+	coverSubject = "subject for branch-name only"
+------------
 
 DISCUSSION
 ----------
diff --git a/branch.c b/branch.c
index 4b49976924..40d30b8fa7 100644
--- a/branch.c
+++ b/branch.c
@@ -182,6 +182,11 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name)
 	return read_branch_config(buf, branch_name, "description");
 }
 
+int read_branch_subject(struct strbuf *buf, const char *branch_name)
+{
+	return read_branch_config(buf, branch_name, "coversubject");
+}
+
 /*
  * Check if 'name' can be a valid name for a branch; die otherwise.
  * Return 1 if the named branch already exists; return 0 otherwise.
diff --git a/branch.h b/branch.h
index 29c1afa4d0..6a8936bbc8 100644
--- a/branch.h
+++ b/branch.h
@@ -79,6 +79,11 @@ extern int install_branch_config(int flag, const char *local, const char *origin
  */
 extern int read_branch_desc(struct strbuf *, const char *branch_name);
 
+/*
+ * Read branch subject
+ */
+extern int read_branch_subject(struct strbuf *, const char *branch_name);
+
 /*
  * Check if a branch is checked out in the main worktree or any linked
  * worktree and die (with a message describing its checkout location) if
diff --git a/builtin/log.c b/builtin/log.c
index ab859f5904..6f19326aea 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1034,13 +1034,14 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
 			      const char *branch_name,
+			      const char *subject,
 			      int quiet)
 {
 	const char *committer;
-	const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-	const char *msg;
+	const char *body = "*** BLURB HERE ***";
 	struct shortlog log;
 	struct strbuf sb = STRBUF_INIT;
+	struct strbuf subject_sb = STRBUF_INIT;
 	int i;
 	const char *encoding = "UTF-8";
 	int need_8bit_cte = 0;
@@ -1068,17 +1069,24 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 	if (!branch_name)
 		branch_name = find_branch_name(rev);
 
-	msg = body;
+	if (!subject) {
+		if (branch_name && *branch_name && !read_branch_subject(&subject_sb, branch_name))
+			subject = subject_sb.buf;
+		else
+			subject = "*** SUBJECT HERE ***";
+	}
+
 	pp.fmt = CMIT_FMT_EMAIL;
 	pp.date_mode.type = DATE_RFC2822;
 	pp.rev = rev;
 	pp.print_email_subject = 1;
 	pp_user_info(&pp, NULL, &sb, committer, encoding);
-	pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
-	pp_remainder(&pp, &msg, &sb, 0);
+	pp_title_line(&pp, &subject, &sb, encoding, need_8bit_cte);
+	pp_remainder(&pp, &body, &sb, 0);
 	add_branch_description(&sb, branch_name);
 	fprintf(rev->diffopt.file, "%s\n", sb.buf);
 
+	strbuf_release(&subject_sb);
 	strbuf_release(&sb);
 
 	shortlog_init(&log);
@@ -1512,6 +1520,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	int no_binary_diff = 0;
 	int zero_commit = 0;
 	struct commit *origin = NULL;
+	const char *cover_subject = NULL;
 	const char *in_reply_to = NULL;
 	struct patch_ids ids;
 	struct strbuf buf = STRBUF_INIT;
@@ -1554,6 +1563,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "rfc", &rev, NULL,
 			    N_("Use [RFC PATCH] instead of [PATCH]"),
 			    PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+		OPT_STRING(0, "cover-subject", &cover_subject, N_("subject"),
+			    N_("the subject for the cover letter")),
 		{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
 			    N_("Use [<prefix>] instead of [PATCH]"),
 			    PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1617,8 +1628,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	extra_to.strdup_strings = 1;
 	extra_cc.strdup_strings = 1;
 	init_log_defaults();
-	git_config(git_format_config, NULL);
 	repo_init_revisions(the_repository, &rev, prefix);
+
+	git_config(git_format_config, NULL);
 	rev.commit_format = CMIT_FMT_EMAIL;
 	rev.expand_tabs_in_log_default = 0;
 	rev.verbose_header = 1;
@@ -1893,7 +1905,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		if (thread)
 			gen_message_id(&rev, "cover");
 		make_cover_letter(&rev, use_stdout,
-				  origin, nr, list, branch_name, quiet);
+				  origin, nr, list, branch_name, cover_subject, quiet);
 		print_bases(&bases, rev.diffopt.file);
 		print_signature(rev.diffopt.file);
 		total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index d05cd256c7..1cf4dfbce2 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1463,6 +1463,26 @@ test_expect_success 'format patch ignores color.ui' '
 	test_cmp expect actual
 '
 
+test_expect_success 'cover letter with config subject' '
+	test_config branch.rebuild-1.coverSubject "config subject" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "Subject: \[PATCH 0/2\] config subject" actual
+'
+
+test_expect_success 'cover letter with command-line subject' '
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-subject "command-line subject" master >actual &&
+	grep "Subject: \[PATCH 0/2\] command-line subject" actual
+'
+
+test_expect_success 'cover letter with command-line subject overrides config' '
+	test_config branch.rebuild-1.coverSubject "config subject" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-subject "command-line subject" master >actual &&
+	grep "Subject: \[PATCH 0/2\] command-line subject" actual
+'
+
 test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index f5e21bf970..0da92179da 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1542,7 +1542,10 @@ test_expect_success 'complete tree filename with metacharacters' '
 '
 
 test_expect_success PERL 'send-email' '
-	test_completion "git send-email --cov" "--cover-letter " &&
+	test_completion "git send-email --cov" <<-\EOF &&
+	--cover-letter Z
+	--cover-subject=Z
+	EOF
 	test_completion "git send-email ma" "master "
 '
 
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH 5/7] format-patch: move extra_headers logic later
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
                   ` (3 preceding siblings ...)
  2019-05-05 16:24 ` [PATCH 4/7] format-patch: make cover letter subject configurable Denton Liu
@ 2019-05-05 16:24 ` Denton Liu
  2019-05-05 16:24 ` [PATCH 6/7] string-list: create string_list_append_all Denton Liu
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

In a future patch, we need to perform the addition of To: and Cc:
to extra_headers after the branch_name logic. Simply transpose this
logic later in the function so that this happens. (This patch is best
viewed with `git diff --color-moved`.)

Note that this logic only depends on the `git_config` and
`repo_init_revisions` and is depended on by the patch creation logic
which is directly below it so this move is effectively a no-op as
no dependencies being reordered.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/builtin/log.c b/builtin/log.c
index 6f19326aea..685e319078 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1665,35 +1665,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		rev.subject_prefix = strbuf_detach(&sprefix, NULL);
 	}
 
-	for (i = 0; i < extra_hdr.nr; i++) {
-		strbuf_addstr(&buf, extra_hdr.items[i].string);
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_to.nr)
-		strbuf_addstr(&buf, "To: ");
-	for (i = 0; i < extra_to.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_to.items[i].string);
-		if (i + 1 < extra_to.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_cc.nr)
-		strbuf_addstr(&buf, "Cc: ");
-	for (i = 0; i < extra_cc.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_cc.items[i].string);
-		if (i + 1 < extra_cc.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	rev.extra_headers = strbuf_detach(&buf, NULL);
-
 	if (from) {
 		if (split_ident_line(&rev.from_ident, from, strlen(from)))
 			die(_("invalid ident line: %s"), from);
@@ -1796,6 +1767,35 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	for (i = 0; i < extra_hdr.nr; i++) {
+		strbuf_addstr(&buf, extra_hdr.items[i].string);
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_to.nr)
+		strbuf_addstr(&buf, "To: ");
+	for (i = 0; i < extra_to.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_to.items[i].string);
+		if (i + 1 < extra_to.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_cc.nr)
+		strbuf_addstr(&buf, "Cc: ");
+	for (i = 0; i < extra_cc.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_cc.items[i].string);
+		if (i + 1 < extra_cc.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	rev.extra_headers = strbuf_detach(&buf, NULL);
+
 	/*
 	 * We cannot move this anywhere earlier because we do want to
 	 * know if --root was given explicitly from the command line.
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH 6/7] string-list: create string_list_append_all
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
                   ` (4 preceding siblings ...)
  2019-05-05 16:24 ` [PATCH 5/7] format-patch: move extra_headers logic later Denton Liu
@ 2019-05-05 16:24 ` Denton Liu
  2019-05-05 16:24 ` [PATCH 7/7] format-patch: read branch-specific To: and Cc: headers Denton Liu
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

In a future patch, we'll need to take one string_list and append it to
the end of another. Create the `string_list_append_all` function which
does this.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)

diff --git a/string-list.c b/string-list.c
index a917955fbd..e63d58fbd2 100644
--- a/string-list.c
+++ b/string-list.c
@@ -215,6 +215,15 @@ struct string_list_item *string_list_append(struct string_list *list,
 			list->strdup_strings ? xstrdup(string) : (char *)string);
 }
 
+void string_list_append_all(struct string_list *list,
+			    const struct string_list *append_list)
+{
+	struct string_list_item *item;
+	ALLOC_GROW(list->items, list->nr + append_list->nr, list->alloc);
+	for_each_string_list_item(item, append_list)
+		string_list_append(list, item->string);
+}
+
 /*
  * Encapsulate the compare function pointer because ISO C99 forbids
  * casting from void * to a function pointer and vice versa.
diff --git a/string-list.h b/string-list.h
index 18c718c12c..32e0c4b47f 100644
--- a/string-list.h
+++ b/string-list.h
@@ -208,6 +208,13 @@ struct string_list_item *string_list_append(struct string_list *list, const char
  */
 struct string_list_item *string_list_append_nodup(struct string_list *list, char *string);
 
+/**
+ * Add all strings in append_list to list.  If list->strdup_string is
+ * set, then each string is copied; otherwise the new string_list_entry
+ * refers to the entry in the append_list.
+ */
+void string_list_append_all(struct string_list *list, const struct string_list *append_list);
+
 /**
  * Sort the list's entries by string value in `strcmp()` order.
  */
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH 7/7] format-patch: read branch-specific To: and Cc: headers
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
                   ` (5 preceding siblings ...)
  2019-05-05 16:24 ` [PATCH 6/7] string-list: create string_list_append_all Denton Liu
@ 2019-05-05 16:24 ` Denton Liu
  2019-05-07  8:56 ` [PATCH 0/7] teach branch-specific options for format-patch Junio C Hamano
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
  8 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-05 16:24 UTC (permalink / raw)
  To: Git Mailing List

If a user wishes to keep track of whom to Cc: on individual patchsets,
they must manually keep track of each recipient and fill it in with the
`--cc` option on git-format-patch each time. However, on the Git mailing
list, Cc:'s are typically never dropped. As a result, it would be nice
to have a method to keep track of recipients on a per-branch basis.

Currently, git-format-patch gets its To: headers from the `--to` options
and the `format.to` config variable. The Cc: header is derived
similarly.

In addition to the above, read To: and Cc: headers from
`branch.<name>.to` and `branch.<name>.cc` so that users can have
branch-specific configuration options.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/branch.txt    |   6 ++
 Documentation/git-format-patch.txt |  10 +--
 builtin/log.c                      |  63 +++++++++++++++--
 t/t4014-format-patch.sh            | 108 +++++++++++++++++++++++------
 4 files changed, 159 insertions(+), 28 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index 2bff738982..22b9bf3d0d 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -104,3 +104,9 @@ branch.<name>.description::
 branch.<name>.coverSubject::
 	When format-patch generates a cover letter, use the specified
 	subject for the cover letter instead of the generic template.
+
+branch.<name>.to::
+branch.<name>.cc::
+	Additional recipients to include in a patch to be submitted
+	by mail.  See the --to and --cc options in
+	linkgit:git-format-patch[1].
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index af7883acbe..1c972f683a 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -199,7 +199,7 @@ will want to ensure that threading is disabled for `git send-email`.
 	Add a `To:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.to` configurations.
+	`format.to` and `branch.<name>.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
@@ -207,7 +207,7 @@ will want to ensure that threading is disabled for `git send-email`.
 	Add a `Cc:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.cc` configurations.
+	`format.cc` and `branch.<name>.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -335,7 +335,7 @@ CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
 defaults for the subject prefix and file suffix, number patches when
-outputting more than one patch, add "To" or "Cc:" headers, configure
+outputting more than one patch, add "To:" or "Cc:" headers, configure
 attachments, and sign off patches with configuration variables.
 
 ------------
@@ -352,11 +352,13 @@ attachments, and sign off patches with configuration variables.
 ------------
 
 In addition, for a specific branch, you can specify a custom cover
-letter subject.
+letter subject, and add additional "To:" or "Cc:" headers.
 
 ------------
 [branch "branch-name"]
 	coverSubject = "subject for branch-name only"
+	to = <email>
+	cc = <email>
 ------------
 
 DISCUSSION
diff --git a/builtin/log.c b/builtin/log.c
index 685e319078..6825d95c5f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -738,6 +738,8 @@ static char *default_attach = NULL;
 static struct string_list extra_hdr = STRING_LIST_INIT_NODUP;
 static struct string_list extra_to = STRING_LIST_INIT_NODUP;
 static struct string_list extra_cc = STRING_LIST_INIT_NODUP;
+int to_cleared;
+int cc_cleared;
 
 static void add_header(const char *value)
 {
@@ -1030,6 +1032,55 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static void add_branch_headers(struct rev_info *rev, const char *branch_name)
+{
+	struct strbuf buf = STRBUF_INIT;
+	const struct string_list *values;
+
+	if (!branch_name)
+		branch_name = find_branch_name(rev);
+
+	if (!branch_name || !*branch_name)
+		return;
+
+	/*
+	 * HACK: We only use branch-specific recipients iff the list has not
+	 * been cleared by an earlier --no-{to,cc} option on the command-line.
+	 *
+	 * When we get format.{to,cc} options, they can be cleared by
+	 * --no-{to,cc} options since the `git_config` call comes before the
+	 *  `parse_options` call.
+	 *
+	 *  However, in the case of branch.<name>.{to,cc}, this function needs
+	 *  to be called after `setup_revisions`, which must be called after
+	 *  `parse_options`. However, in order for the --no-{to,cc} logic to
+	 *  clear the extra_{to,cc} string_list, this function should actually
+	 *  be called _before_ `parse_options`. As a result, we have a circular
+	 *  dependency.
+	 *
+	 *  The {to,cc}_cleared flag lets us workaround this by just no
+	 *  including branch-specific recipients iff --no-{to,cc} has been
+	 *  specified on the command-line.
+	 */
+
+	if (!to_cleared) {
+		strbuf_addf(&buf, "branch.%s.to", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_to, values);
+	}
+
+	if (!cc_cleared) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "branch.%s.cc", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_cc, values);
+	}
+
+	strbuf_release(&buf);
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
@@ -1289,18 +1340,20 @@ static int header_callback(const struct option *opt, const char *arg, int unset)
 
 static int to_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		to_cleared = 1;
 		string_list_clear(&extra_to, 0);
-	else
+	} else
 		string_list_append(&extra_to, arg);
 	return 0;
 }
 
 static int cc_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		cc_cleared = 1;
 		string_list_clear(&extra_cc, 0);
-	else
+	} else
 		string_list_append(&extra_cc, arg);
 	return 0;
 }
@@ -1767,6 +1820,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	add_branch_headers(&rev, branch_name);
+
 	for (i = 0; i < extra_hdr.nr; i++) {
 		strbuf_addstr(&buf, extra_hdr.items[i].string);
 		strbuf_addch(&buf, '\n');
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 1cf4dfbce2..6578e6b433 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -234,6 +234,65 @@ test_expect_failure 'configuration To: header (rfc2047)' '
 	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
+test_expect_success 'branch-specific configuration To: header (ascii)' '
+
+	test_unconfig format.to &&
+	git config branch.side.to "R E Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc822)' '
+
+	git config branch.side.to "R. E. Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc2047)' '
+
+	git config branch.side.to "R Ä Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_success 'all recipients included from all sources' '
+
+	git config format.to "Format To1 <formatto1@example.com>" &&
+	git config --add format.to "Format To2 <formatto2@example.com>" &&
+	git config format.cc "Format Cc1 <formatcc1@example.com>" &&
+	git config --add format.cc "Format Cc2 <formatcc2@example.com>" &&
+	git config branch.side.to "Branch To1 <branchto1@example.com>" &&
+	git config --add branch.side.to "Branch To2 <branchto2@example.com>" &&
+	git config branch.side.cc "Branch Cc1 <branchcc1@example.com>" &&
+	git config --add branch.side.cc "Branch Cc2 <branchcc2@example.com>" &&
+	cat <<-\EOF >expect &&
+	To: Format To1 <formatto1@example.com>,
+	    Format To2 <formatto2@example.com>,
+	    Command-line To1 <commandlineto1@example.com>,
+	    Command-line To2 <commandlineto2@example.com>,
+	    Branch To1 <branchto1@example.com>,
+	    Branch To2 <branchto2@example.com>
+	Cc: Format Cc1 <formatcc1@example.com>,
+	    Format Cc2 <formatcc2@example.com>,
+	    Command-line Cc1 <commandlinecc1@example.com>,
+	    Command-line Cc2 <commandlinecc2@example.com>,
+	    Branch Cc1 <branchcc1@example.com>,
+	    Branch Cc2 <branchcc2@example.com>
+
+	EOF
+	git format-patch --stdout \
+		--to="Command-line To1 <commandlineto1@example.com>" \
+		--to="Command-line To2 <commandlineto2@example.com>" \
+		--cc="Command-line Cc1 <commandlinecc1@example.com>" \
+		--cc="Command-line Cc2 <commandlinecc2@example.com>" \
+		master..side | sed -ne "/^To:/,/^$/p;/^$/q" >patch10 &&
+	test_cmp expect patch10
+'
+
 # check_patch <patch>: Verify that <patch> looks like a half-sane
 # patch email to avoid a false positive with !grep
 check_patch () {
@@ -286,42 +345,51 @@ test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side >patch10 &&
-	sed -e "/^\$/q" patch10 >hdrs10 &&
-	check_patch hdrs10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+	git config --replace-all branch.side.to \
+		"B R Anch <branch@example.com>" &&
+	git format-patch --no-to --stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "R E Cipient <rcipient@example.com>" hdrs11 &&
+	! grep "B R Anch <branch@example.com>" hdrs11
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
 
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
+	git config --replace-all branch.side.to \
+		"B R Anch2 <branch2@example.com>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side >patch11 &&
-	sed -e "/^\$/q" patch11 >hdrs11 &&
-	check_patch hdrs11 &&
-	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
-	grep "^To: Someone Else <else@out.there>\$" hdrs11
+		--stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "Someone <someone@out.there>" hdrs12 &&
+	! grep "B R Anch2 <branch2@example.com>" hdrs12 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs12
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side >patch12 &&
-	sed -e "/^\$/q" patch12 >hdrs12 &&
-	check_patch hdrs12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
+	git config --replace-all branch.side.cc \
+		"B R Anch3 <branch3@example.com>" &&
+	git format-patch --no-cc --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "C E Cipient <rcipient@example.com>" hdrs13 &&
+	! grep "B R Anch3 <branch3@example.com>" hdrs13
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side >patch13 &&
-	sed -e "/^\$/q" patch13 >hdrs13 &&
-	check_patch hdrs13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
+	git format-patch --no-add-header --stdout master..side >patch14 &&
+	sed -e "/^\$/q" patch14 >hdrs14 &&
+	check_patch hdrs14 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs14
 '
 
 test_expect_success 'multiple files' '
@@ -957,7 +1025,7 @@ test_expect_success 'format-patch wraps extremely long subject (ascii)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -996,7 +1064,7 @@ test_expect_success 'format-patch wraps extremely long subject (rfc2047)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1005,7 +1073,7 @@ check_author() {
 	git add file &&
 	GIT_AUTHOR_NAME=$1 git commit -m author-check &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^From: /p; /^ /p; /^$/q" <patch >actual &&
+	sed -n "/^From: /p; /^ /p; /^Date:/q" <patch >actual &&
 	test_cmp expect actual
 }
 
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* Re: [PATCH 0/7] teach branch-specific options for format-patch
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
                   ` (6 preceding siblings ...)
  2019-05-05 16:24 ` [PATCH 7/7] format-patch: read branch-specific To: and Cc: headers Denton Liu
@ 2019-05-07  8:56 ` Junio C Hamano
  2019-05-07 14:19   ` Denton Liu
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
  8 siblings, 1 reply; 59+ messages in thread
From: Junio C Hamano @ 2019-05-07  8:56 UTC (permalink / raw)
  To: Denton Liu; +Cc: Git Mailing List

Denton Liu <liu.denton@gmail.com> writes:

> Currently, format-patch only accepts branch.<name>.description as a
> branch-specific configuration variable. However, there are many other
> options which would be useful to have on a branch-by-branch basis,
> namely cover letter subject and To: and Cc: headers.
>
> Teach format-patch to recognise these branch-specific configuration
> options.
>
> Note that this patchset[1] was created using these new configuration
> options:
>
> 	[branch "submitted/fix-revisions-txt"]
> 		coverSubject = "cleanup revisions.txt"
> 		cc = "Andreas Heiduk <asheiduk@gmail.com>"
> 		cc = "Duy Nguyen <pclouds@gmail.com>"
> 		cc = "Junio C Hamano <gitster@pobox.com>"

Do we have format.<something> configuration for these things?

What I am trying to get at is if these are better structured similar
to http options where http.<something> supplies the overall default
for <something>, while http.<destination>.<something> gives a more
destination site specific override of that default.  I.e. format.cc
is used as fallback, while format.<branch>.cc is used to override.

In any case, it smells to me that branch.<branch>.cc does not hint
strongly enough that they are meant to affect format-patch.



^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH 0/7] teach branch-specific options for format-patch
  2019-05-07  8:56 ` [PATCH 0/7] teach branch-specific options for format-patch Junio C Hamano
@ 2019-05-07 14:19   ` Denton Liu
  2019-05-07 15:05     ` Junio C Hamano
  0 siblings, 1 reply; 59+ messages in thread
From: Denton Liu @ 2019-05-07 14:19 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

Hi Junio,

On Tue, May 07, 2019 at 05:56:00PM +0900, Junio C Hamano wrote:
> Denton Liu <liu.denton@gmail.com> writes:
>
> > Currently, format-patch only accepts branch.<name>.description as a
> > branch-specific configuration variable. However, there are many other
> > options which would be useful to have on a branch-by-branch basis,
> > namely cover letter subject and To: and Cc: headers.
> >
> > Teach format-patch to recognise these branch-specific configuration
> > options.
> >
> > Note that this patchset[1] was created using these new configuration
> > options:
> >
> > 	[branch "submitted/fix-revisions-txt"]
> > 		coverSubject = "cleanup revisions.txt"
> > 		cc = "Andreas Heiduk <asheiduk@gmail.com>"
> > 		cc = "Duy Nguyen <pclouds@gmail.com>"
> > 		cc = "Junio C Hamano <gitster@pobox.com>"
>
> Do we have format.<something> configuration for these things?

Currently, we have format.{to,cc} but not format.coverSubject. The
reason why is that for the cover-subject, I didn't think that it would
make a lot of sense to have a general configuration for this since it
varies between branches, just like how branch.<name>.description does
not have a matching format.description.

>
> What I am trying to get at is if these are better structured similar
> to http options where http.<something> supplies the overall default
> for <something>, while http.<destination>.<something> gives a more
> destination site specific override of that default.  I.e. format.cc
> is used as fallback, while format.<branch>.cc is used to override.

The reason why I chose to use branch.<name>.* is because format-patch
currently reads from branch.<name>.description and I wanted to build on
top of that. In addition, I didn't want to scatter branch-specific
configs in two different place (i.e. have a branch.<branchName>.description
alongside a format.<branchName>.coverSubject).

>
> In any case, it smells to me that branch.<branch>.cc does not hint
> strongly enough that they are meant to affect format-patch.
>
>

Would you suggest moving to a format.<branchname>.* approach or would it
make sense to rename the configs to something like
branch.<name>.{emailCoverSubject,emailTo,emailCc}?

Thanks,

Denton

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH 0/7] teach branch-specific options for format-patch
  2019-05-07 14:19   ` Denton Liu
@ 2019-05-07 15:05     ` Junio C Hamano
  2019-05-07 15:21       ` Denton Liu
  0 siblings, 1 reply; 59+ messages in thread
From: Junio C Hamano @ 2019-05-07 15:05 UTC (permalink / raw)
  To: Denton Liu; +Cc: Git Mailing List

Denton Liu <liu.denton@gmail.com> writes:

> The reason why I chose to use branch.<name>.* is because format-patch
> currently reads from branch.<name>.description and I wanted to build on
> top of that. In addition, I didn't want to scatter branch-specific
> configs in two different place (i.e. have a branch.<branchName>.description
> alongside a format.<branchName>.coverSubject).

The "branch.<name>.description" variable lives at a lot higher level
of abstraction than "use this for the default value of option X of
the command Y".  It gives a place for the user to state what the
branch is about.  It makes 100% sense not to have it under format.*
hierarchy, because it is designed to be agnostic to what individual
command uses it for.  It is there to talk about what the branch is
for, and what the variable says about the branch to its users,
i.e. various tools, does not change depending on who is listening to
it.

The format-patch command may use it as a hint for writing the cover
letter message.  Repository browsers may use it while listing the
branches when more descriptive text than the branch name is desired.
Request-pull tool may use it when preparing the branch summary.

The point is that "description" is about "what that *branch* is",
not "what that branch means for the format-patch user".

You cannot compare that with something like "when format-patch
prepares an e-mail, add CC: header to this address", which is very
specific to the single command, i.e. "how command X uses it".  I
think that is the gist of the new variables you are adding.

>> In any case, it smells to me that branch.<branch>.cc does not hint
>> strongly enough that they are meant to affect format-patch.
>
> Would you suggest moving to a format.<branchname>.* approach or would it
> make sense to rename the configs to something like
> branch.<name>.{emailCoverSubject,emailTo,emailCc}?

So if I have to pick between the two, I would probably vote for the
former from the philosophical ground, but operationally, I suspect
that the latter would be much simpler to use.  You could even have
"git branch -d <name>" to get rid of them at the same time.

But as I may have hinted in the message you are responding to, I am
not quite convinced we want these configuration variables in the
first place.  Why should both description and coverSubject need to
exist?  Perhaps we should add a heuristic like "If the branch
description looks like a single line, optionally followed by 'a
blank line and more paragraphs', use the first line as the subject
of the cover letter (and the remainder as the body of the cover
letter) or something?


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH 0/7] teach branch-specific options for format-patch
  2019-05-07 15:05     ` Junio C Hamano
@ 2019-05-07 15:21       ` Denton Liu
  2019-05-07 15:46         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 59+ messages in thread
From: Denton Liu @ 2019-05-07 15:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

On Wed, May 08, 2019 at 12:05:43AM +0900, Junio C Hamano wrote:
> Denton Liu <liu.denton@gmail.com> writes:

[snip]

> >
> > Would you suggest moving to a format.<branchname>.* approach or would it
> > make sense to rename the configs to something like
> > branch.<name>.{emailCoverSubject,emailTo,emailCc}?
> 
> So if I have to pick between the two, I would probably vote for the
> former from the philosophical ground, but operationally, I suspect
> that the latter would be much simpler to use.  You could even have
> "git branch -d <name>" to get rid of them at the same time.
> 
> But as I may have hinted in the message you are responding to, I am
> not quite convinced we want these configuration variables in the
> first place.  Why should both description and coverSubject need to
> exist?  Perhaps we should add a heuristic like "If the branch
> description looks like a single line, optionally followed by 'a
> blank line and more paragraphs', use the first line as the subject
> of the cover letter (and the remainder as the body of the cover
> letter) or something?
> 

I considered doing something like that but I opted not to because it
wouldn't be a backwards compatible change and I didn't want to surprise
any future users with a change like this.

For branch.<branchname>.{to,cc}, I wanted these config options because
the current method for format-patch to handle Cc-lists is just manually
keeping track of the people who have responded and entering them into
the --cc option of format-patch.

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH 0/7] teach branch-specific options for format-patch
  2019-05-07 15:21       ` Denton Liu
@ 2019-05-07 15:46         ` Ævar Arnfjörð Bjarmason
  2019-05-08  1:45           ` Junio C Hamano
  0 siblings, 1 reply; 59+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2019-05-07 15:46 UTC (permalink / raw)
  To: Denton Liu; +Cc: Junio C Hamano, Git Mailing List


On Tue, May 07 2019, Denton Liu wrote:

> On Wed, May 08, 2019 at 12:05:43AM +0900, Junio C Hamano wrote:
>> Denton Liu <liu.denton@gmail.com> writes:
>
> [snip]
>
>> >
>> > Would you suggest moving to a format.<branchname>.* approach or would it
>> > make sense to rename the configs to something like
>> > branch.<name>.{emailCoverSubject,emailTo,emailCc}?
>>
>> So if I have to pick between the two, I would probably vote for the
>> former from the philosophical ground, but operationally, I suspect
>> that the latter would be much simpler to use.  You could even have
>> "git branch -d <name>" to get rid of them at the same time.
>>
>> But as I may have hinted in the message you are responding to, I am
>> not quite convinced we want these configuration variables in the
>> first place.  Why should both description and coverSubject need to
>> exist?  Perhaps we should add a heuristic like "If the branch
>> description looks like a single line, optionally followed by 'a
>> blank line and more paragraphs', use the first line as the subject
>> of the cover letter (and the remainder as the body of the cover
>> letter) or something?
>>
>
> I considered doing something like that but I opted not to because it
> wouldn't be a backwards compatible change and I didn't want to surprise
> any future users with a change like this.
>
> For branch.<branchname>.{to,cc}, I wanted these config options because
> the current method for format-patch to handle Cc-lists is just manually
> keeping track of the people who have responded and entering them into
> the --cc option of format-patch.

This may just be more insanity to implement right now, but perhaps in
addition to "gitdir:" etc. in the IncludeIf config syntax we'd want to
aim for "HEADref" (or some saner name). I.e. allowing to include/enable
arbitrary config based on the ref name.

Chicken & egg problems though...

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH 0/7] teach branch-specific options for format-patch
  2019-05-07 15:46         ` Ævar Arnfjörð Bjarmason
@ 2019-05-08  1:45           ` Junio C Hamano
  2019-05-31  2:00             ` [RFC PATCH] config: learn the "onbranch:" includeIf condition Denton Liu
  0 siblings, 1 reply; 59+ messages in thread
From: Junio C Hamano @ 2019-05-08  1:45 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Denton Liu, Git Mailing List

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> This may just be more insanity to implement right now, but perhaps in
> addition to "gitdir:" etc. in the IncludeIf config syntax we'd want to
> aim for "HEADref" (or some saner name). I.e. allowing to include/enable
> arbitrary config based on the ref name.
>
> Chicken & egg problems though...

The saner name might be

    [IncludeIf "onBranch:my-topic"] path = ...

Would we discard the section when "branch -d my-topic" is done, or
would we keep it for the next creation of the branch with the same
name?

^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH v2 0/6] teach branch-specific options for format-patch
  2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
                   ` (7 preceding siblings ...)
  2019-05-07  8:56 ` [PATCH 0/7] teach branch-specific options for format-patch Junio C Hamano
@ 2019-05-17  0:27 ` Denton Liu
  2019-05-17  0:27   ` [PATCH v2 1/6] t4014: clean up style Denton Liu
                     ` (7 more replies)
  8 siblings, 8 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17  0:27 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

Hi all,

I thought a lot about building the branch-specific include option but I
opted against this because `format.<name>.coverSubject` is a
branch-exclusive config option, whereas it doesn't make sense to have a
`format.coverSubject` option.

Thus, if we pursue this, we *need* to have a `format.<name>.*` section
so let's not deal with the complexity of buliding a generic include
system for now.

Also, I might consider adding a `format.<name>.outputDirectory` option
in the future but let's see what kind of comments this patchset gets.

Changes since v1:

* Used format.<branch-name>.* variables instead of branch.<branch-name>.*


Denton Liu (6):
  t4014: clean up style
  Doc: add more detail for git-format-patch
  format-patch: make cover letter subject configurable
  format-patch: move extra_headers logic later
  string-list: create string_list_append_all
  format-patch: read branch-specific To: and Cc: headers

 Documentation/config/format.txt    |  12 +-
 Documentation/git-format-patch.txt |  33 +-
 builtin/branch.c                   |  16 +-
 builtin/log.c                      | 162 +++++--
 string-list.c                      |   9 +
 string-list.h                      |   7 +
 t/t3200-branch.sh                  |   8 +-
 t/t4014-format-patch.sh            | 708 +++++++++++++++++------------
 t/t9902-completion.sh              |   5 +-
 9 files changed, 608 insertions(+), 352 deletions(-)

Interdiff against v1:
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index d292210cf6..8f4b3faadd 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -100,13 +100,3 @@ branch.<name>.description::
 	`git branch --edit-description`. Branch description is
 	automatically added in the format-patch cover letter or
 	request-pull summary.
-
-branch.<name>.coverSubject::
-	When format-patch generates a cover letter, use the specified
-	subject for the cover letter instead of the generic template.
-
-branch.<name>.to::
-branch.<name>.cc::
-	Additional recipients to include in a patch to be submitted
-	by mail.  See the --to and --cc options in
-	linkgit:git-format-patch[1].
diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index dc77941c48..d387451573 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -28,14 +28,22 @@ format.headers::
 
 format.to::
 format.cc::
+format.<branch-name>.to::
+format.<branch-name>.cc::
 	Additional recipients to include in a patch to be submitted
-	by mail.  See the --to and --cc options in
-	linkgit:git-format-patch[1].
+	by mail.  For the <branch-name> options, the recipients are only
+	included if patches are generated for the given <branch-name>.
+	See the --to and --cc options in linkgit:git-format-patch[1].
 
 format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
 	subject prefix. Use this variable to change that prefix.
 
+format.<branch-name>.coverSubject::
+	When format-patch generates a cover letter for the given
+	<branch-name>, use the specified subject for the cover letter
+	instead of the generic template.
+
 format.signature::
 	The default for format-patch is to output a signature containing
 	the Git version number. Use this variable to change that default.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1c972f683a..4e826010f6 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -199,7 +199,7 @@ will want to ensure that threading is disabled for `git send-email`.
 	Add a `To:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.to` and `branch.<name>.to` configurations.
+	`format.to` and `format.<branch-name>.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
@@ -207,7 +207,7 @@ will want to ensure that threading is disabled for `git send-email`.
 	Add a `Cc:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.cc` and `branch.<name>.cc` configurations.
+	`format.cc` and `format.<branch-name>.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -355,7 +355,7 @@ In addition, for a specific branch, you can specify a custom cover
 letter subject, and add additional "To:" or "Cc:" headers.
 
 ------------
-[branch "branch-name"]
+[format "branch-name"]
 	coverSubject = "subject for branch-name only"
 	to = <email>
 	cc = <email>
diff --git a/branch.c b/branch.c
index 83cd441790..643694542a 100644
--- a/branch.c
+++ b/branch.c
@@ -163,11 +163,11 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 	free(tracking.src);
 }
 
-static int read_branch_config(struct strbuf *buf, const char *branch_name, const char *key)
+int read_branch_desc(struct strbuf *buf, const char *branch_name)
 {
 	char *v = NULL;
 	struct strbuf name = STRBUF_INIT;
-	strbuf_addf(&name, "branch.%s.%s", branch_name, key);
+	strbuf_addf(&name, "branch.%s.description", branch_name);
 	if (git_config_get_string(name.buf, &v)) {
 		strbuf_release(&name);
 		return -1;
@@ -178,16 +178,6 @@ static int read_branch_config(struct strbuf *buf, const char *branch_name, const
 	return 0;
 }
 
-int read_branch_desc(struct strbuf *buf, const char *branch_name)
-{
-	return read_branch_config(buf, branch_name, "description");
-}
-
-int read_branch_subject(struct strbuf *buf, const char *branch_name)
-{
-	return read_branch_config(buf, branch_name, "coversubject");
-}
-
 /*
  * Check if 'name' can be a valid name for a branch; die otherwise.
  * Return 1 if the named branch already exists; return 0 otherwise.
diff --git a/branch.h b/branch.h
index 363a4fae9d..6f38db14e9 100644
--- a/branch.h
+++ b/branch.h
@@ -79,11 +79,6 @@ int install_branch_config(int flag, const char *local, const char *origin, const
  */
 int read_branch_desc(struct strbuf *, const char *branch_name);
 
-/*
- * Read branch subject
- */
-extern int read_branch_subject(struct strbuf *, const char *branch_name);
-
 /*
  * Check if a branch is checked out in the main worktree or any linked
  * worktree and die (with a message describing its checkout location) if
diff --git a/builtin/branch.c b/builtin/branch.c
index d4359b33ac..367e1fc9bc 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -178,12 +178,22 @@ static int check_branch_commit(const char *branchname, const char *refname,
 	return 0;
 }
 
+static const char *branch_specific_config[] = {
+	"branch",
+	"format",
+	NULL
+};
+
 static void delete_branch_config(const char *branchname)
 {
 	struct strbuf buf = STRBUF_INIT;
-	strbuf_addf(&buf, "branch.%s", branchname);
-	if (git_config_rename_section(buf.buf, NULL) < 0)
-		warning(_("Update of config-file failed"));
+	int i;
+	for (i = 0; branch_specific_config[i]; i++) {
+		strbuf_addf(&buf, "%s.%s", branch_specific_config[i], branchname);
+		if (git_config_rename_section(buf.buf, NULL) < 0)
+			warning(_("Update of config-file failed"));
+		strbuf_reset(&buf);
+	}
 	strbuf_release(&buf);
 }
 
diff --git a/builtin/log.c b/builtin/log.c
index 3adc942b8c..e1d793577d 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1032,6 +1032,21 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static int read_branch_subject(struct strbuf *buf, const char *branch_name)
+{
+	char *v = NULL;
+	struct strbuf name = STRBUF_INIT;
+	strbuf_addf(&name, "format.%s.coverSubject", branch_name);
+	if (git_config_get_string(name.buf, &v)) {
+		strbuf_release(&name);
+		return -1;
+	}
+	strbuf_addstr(buf, v);
+	free(v);
+	strbuf_release(&name);
+	return 0;
+}
+
 static void add_branch_headers(struct rev_info *rev, const char *branch_name)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -1064,7 +1079,7 @@ static void add_branch_headers(struct rev_info *rev, const char *branch_name)
 	 */
 
 	if (!to_cleared) {
-		strbuf_addf(&buf, "branch.%s.to", branch_name);
+		strbuf_addf(&buf, "format.%s.to", branch_name);
 		values = git_config_get_value_multi(buf.buf);
 		if (values)
 			string_list_append_all(&extra_to, values);
@@ -1072,7 +1087,7 @@ static void add_branch_headers(struct rev_info *rev, const char *branch_name)
 
 	if (!cc_cleared) {
 		strbuf_reset(&buf);
-		strbuf_addf(&buf, "branch.%s.cc", branch_name);
+		strbuf_addf(&buf, "format.%s.cc", branch_name);
 		values = git_config_get_value_multi(buf.buf);
 		if (values)
 			string_list_append_all(&extra_cc, values);
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index e9ad50b66d..9ae3da888e 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -782,9 +782,15 @@ test_expect_success 'test tracking setup via --track but deeper' '
 '
 
 test_expect_success 'test deleting branch deletes branch config' '
+	git config format.my7.coverSubject "cover subject" &&
+	git config format.my7.to "To Me <to@example.com>" &&
+	git config format.my7.cc "Cc Me <cc@example.com>" &&
 	git branch -d my7 &&
 	test -z "$(git config branch.my7.remote)" &&
-	test -z "$(git config branch.my7.merge)"
+	test -z "$(git config branch.my7.merge)" &&
+	test -z "$(git config format.my7.coverSubject)"
+	test -z "$(git config format.my7.to)" &&
+	test -z "$(git config format.my7.cc)"
 '
 
 test_expect_success 'test deleting branch without config' '
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 5d6c85489e..9c527510c3 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -237,7 +237,7 @@ test_expect_failure 'configuration To: header (rfc2047)' '
 test_expect_success 'branch-specific configuration To: header (ascii)' '
 
 	test_unconfig format.to &&
-	git config branch.side.to "R E Cipient <rcipient@example.com>" &&
+	git config format.side.to "R E Cipient <rcipient@example.com>" &&
 	git format-patch --stdout master..side >patch10 &&
 	sed -e "/^\$/q" patch10 >hdrs10 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
@@ -245,7 +245,7 @@ test_expect_success 'branch-specific configuration To: header (ascii)' '
 
 test_expect_failure 'branch-specific configuration To: header (rfc822)' '
 
-	git config branch.side.to "R. E. Cipient <rcipient@example.com>" &&
+	git config format.side.to "R. E. Cipient <rcipient@example.com>" &&
 	git format-patch --stdout master..side >patch10 &&
 	sed -e "/^\$/q" patch10 >hdrs10 &&
 	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs10
@@ -253,7 +253,7 @@ test_expect_failure 'branch-specific configuration To: header (rfc822)' '
 
 test_expect_failure 'branch-specific configuration To: header (rfc2047)' '
 
-	git config branch.side.to "R Ä Cipient <rcipient@example.com>" &&
+	git config format.side.to "R Ä Cipient <rcipient@example.com>" &&
 	git format-patch --stdout master..side >patch10 &&
 	sed -e "/^\$/q" patch10 >hdrs10 &&
 	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs10
@@ -265,10 +265,10 @@ test_expect_success 'all recipients included from all sources' '
 	git config --add format.to "Format To2 <formatto2@example.com>" &&
 	git config format.cc "Format Cc1 <formatcc1@example.com>" &&
 	git config --add format.cc "Format Cc2 <formatcc2@example.com>" &&
-	git config branch.side.to "Branch To1 <branchto1@example.com>" &&
-	git config --add branch.side.to "Branch To2 <branchto2@example.com>" &&
-	git config branch.side.cc "Branch Cc1 <branchcc1@example.com>" &&
-	git config --add branch.side.cc "Branch Cc2 <branchcc2@example.com>" &&
+	git config format.side.to "Branch To1 <branchto1@example.com>" &&
+	git config --add format.side.to "Branch To2 <branchto2@example.com>" &&
+	git config format.side.cc "Branch Cc1 <branchcc1@example.com>" &&
+	git config --add format.side.cc "Branch Cc2 <branchcc2@example.com>" &&
 	cat <<-\EOF >expect &&
 	To: Format To1 <formatto1@example.com>,
 	    Format To2 <formatto2@example.com>,
@@ -345,7 +345,7 @@ test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git config --replace-all branch.side.to \
+	git config --replace-all format.side.to \
 		"B R Anch <branch@example.com>" &&
 	git format-patch --no-to --stdout master..side >patch11 &&
 	sed -e "/^\$/q" patch11 >hdrs11 &&
@@ -358,7 +358,7 @@ test_expect_success '--no-to and --to replaces config.to' '
 
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
-	git config --replace-all branch.side.to \
+	git config --replace-all format.side.to \
 		"B R Anch2 <branch2@example.com>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
 		--stdout master..side >patch12 &&
@@ -373,7 +373,7 @@ test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git config --replace-all branch.side.cc \
+	git config --replace-all format.side.cc \
 		"B R Anch3 <branch3@example.com>" &&
 	git format-patch --no-cc --stdout master..side >patch13 &&
 	sed -e "/^\$/q" patch13 >hdrs13 &&
@@ -1538,7 +1538,7 @@ test_expect_success 'format patch ignores color.ui' '
 '
 
 test_expect_success 'cover letter with config subject' '
-	test_config branch.rebuild-1.coverSubject "config subject" &&
+	test_config format.rebuild-1.coverSubject "config subject" &&
 	git checkout rebuild-1 &&
 	git format-patch --stdout --cover-letter master >actual &&
 	grep "Subject: \[PATCH 0/2\] config subject" actual
@@ -1551,7 +1551,7 @@ test_expect_success 'cover letter with command-line subject' '
 '
 
 test_expect_success 'cover letter with command-line subject overrides config' '
-	test_config branch.rebuild-1.coverSubject "config subject" &&
+	test_config format.rebuild-1.coverSubject "config subject" &&
 	git checkout rebuild-1 &&
 	git format-patch --stdout --cover-letter --cover-subject "command-line subject" master >actual &&
 	grep "Subject: \[PATCH 0/2\] command-line subject" actual
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v2 1/6] t4014: clean up style
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
@ 2019-05-17  0:27   ` Denton Liu
  2019-05-17  0:27   ` [PATCH v2 2/6] Doc: add more detail for git-format-patch Denton Liu
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17  0:27 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In Git's tests, there is typically no space between the redirection
operator and the filename. Remove these spaces.

Since output is silenced when running without `-v` and debugging
output is useful with `-v`, remove redirections to /dev/null.

Change here-docs from `<<\EOF` to `<<-\EOF` so that they can be indented
along with the rest of the test case.

Finally, refactor to remove Git commands upstream of pipe. This way, if
an invocation of a Git command fails, the return code won't be lost.
Keep upstream non-Git commands since we have to assume a base level of
sanity.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)

diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index b6e2fdbc44..3423f974bc 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -34,7 +34,8 @@ test_expect_success setup '
 	git commit -m "Side changes #3 with \\n backslash-n in it." &&
 
 	git checkout master &&
-	git diff-tree -p C2 | git apply --index &&
+	git diff-tree -p C2 >patch &&
+	git apply --index <patch &&
 	test_tick &&
 	git commit -m "Master accepts moral equivalent of #2"
 
@@ -77,7 +78,7 @@ test_expect_success "format-patch doesn't consider merge commits" '
 	git checkout -b merger master &&
 	test_tick &&
 	git merge --no-ff slave &&
-	cnt=$(git format-patch -3 --stdout | grep "^From " | wc -l) &&
+	cnt=$(git format-patch -3 --stdout >patch && grep "^From " patch | wc -l) &&
 	test $cnt = 3
 '
 
@@ -85,21 +86,22 @@ test_expect_success "format-patch result applies" '
 
 	git checkout -b rebuild-0 master &&
 	git am -3 patch0 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success "format-patch --ignore-if-in-upstream result applies" '
 
 	git checkout -b rebuild-1 master &&
 	git am -3 patch1 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success 'commit did not screw up the log message' '
 
-	git cat-file commit side | grep "^Side .* with .* backslash-n"
+	git cat-file commit side >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -112,7 +114,8 @@ test_expect_success 'format-patch did not screw up the log message' '
 
 test_expect_success 'replay did not screw up the log message' '
 
-	git cat-file commit rebuild-1 | grep "^Side .* with .* backslash-n"
+	git cat-file commit rebuild-1 >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -122,8 +125,8 @@ test_expect_success 'extra headers' '
 " &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>
 " &&
-	git format-patch --stdout master..side > patch2 &&
-	sed -e "/^\$/q" patch2 > hdrs2 &&
+	git format-patch --stdout master..side >patch2 &&
+	sed -e "/^\$/q" patch2 >hdrs2 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "To: S E Cipient <scipient@example.com>" &&
-	git format-patch --stdout master..side > patch4 &&
-	sed -e "/^\$/q" patch4 > hdrs4 &&
+	git format-patch --stdout master..side >patch4 &&
+	sed -e "/^\$/q" patch4 >hdrs4 &&
 	grep "^To: R E Cipient <rcipient@example.com>,\$" hdrs4 &&
 	grep "^ *S E Cipient <scipient@example.com>\$" hdrs4
 '
@@ -153,72 +156,82 @@ test_expect_success 'extra headers with multiple To:s' '
 test_expect_success 'additional command line cc (ascii)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch5
+	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" patch5
+	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch7 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs7 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch8
+	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch8
+	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch8
+	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
 # check_patch <patch>: Verify that <patch> looks like a half-sane
@@ -231,52 +244,52 @@ check_patch () {
 
 test_expect_success 'format.from=false' '
 
-	git -c format.from=false format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
+	git -c format.from=false format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
 	check_patch patch &&
-	! grep "^From: C O Mitter <committer@example.com>\$" patch
+	! grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from=true' '
 
-	git -c format.from=true format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: C O Mitter <committer@example.com>\$" patch
+	git -c format.from=true format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
@@ -284,31 +297,31 @@ test_expect_success '--no-to and --to replaces config.to' '
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <someone@out.there>\$" patch11 &&
-	grep "^To: Someone Else <else@out.there>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
 '
 
 test_expect_success 'multiple files' '
@@ -338,7 +351,7 @@ test_expect_success 'reroll count (-v)' '
 check_threading () {
 	expect="$1" &&
 	shift &&
-	(git format-patch --stdout "$@"; echo $? > status.out) |
+	(git format-patch --stdout "$@"; echo $? >status.out) |
 	# Prints everything between the Message-ID and In-Reply-To,
 	# and replaces all Message-ID-lookalikes by a sequence number
 	perl -ne '
@@ -353,12 +366,12 @@ check_threading () {
 			print;
 		}
 		print "---\n" if /^From /i;
-	' > actual &&
+	' >actual &&
 	test 0 = "$(cat status.out)" &&
 	test_cmp "$expect" actual
 }
 
-cat >> expect.no-threading <<EOF
+cat >>expect.no-threading <<EOF
 ---
 ---
 ---
@@ -369,7 +382,7 @@ test_expect_success 'no threading' '
 	check_threading expect.no-threading master
 '
 
-cat > expect.thread <<EOF
+cat >expect.thread <<EOF
 ---
 Message-Id: <0>
 ---
@@ -386,7 +399,7 @@ test_expect_success 'thread' '
 	check_threading expect.thread --thread master
 '
 
-cat > expect.in-reply-to <<EOF
+cat >expect.in-reply-to <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -406,7 +419,7 @@ test_expect_success 'thread in-reply-to' '
 		--thread master
 '
 
-cat > expect.cover-letter <<EOF
+cat >expect.cover-letter <<EOF
 ---
 Message-Id: <0>
 ---
@@ -427,7 +440,7 @@ test_expect_success 'thread cover-letter' '
 	check_threading expect.cover-letter --cover-letter --thread master
 '
 
-cat > expect.cl-irt <<EOF
+cat >expect.cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -459,7 +472,7 @@ test_expect_success 'thread explicit shallow' '
 		--in-reply-to="<test.message>" --thread=shallow master
 '
 
-cat > expect.deep <<EOF
+cat >expect.deep <<EOF
 ---
 Message-Id: <0>
 ---
@@ -477,7 +490,7 @@ test_expect_success 'thread deep' '
 	check_threading expect.deep --thread=deep master
 '
 
-cat > expect.deep-irt <<EOF
+cat >expect.deep-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -500,7 +513,7 @@ test_expect_success 'thread deep in-reply-to' '
 		--in-reply-to="<test.message>" master
 '
 
-cat > expect.deep-cl <<EOF
+cat >expect.deep-cl <<EOF
 ---
 Message-Id: <0>
 ---
@@ -524,7 +537,7 @@ test_expect_success 'thread deep cover-letter' '
 	check_threading expect.deep-cl --cover-letter --thread=deep master
 '
 
-cat > expect.deep-cl-irt <<EOF
+cat >expect.deep-cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -606,7 +619,7 @@ test_expect_success 'cover-letter inherits diff options' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
   This is an excessively long subject line for a message due to the
     habit some projects have of not having a short, one-line subject at
     the start of the commit message, but rather sticking a whole
@@ -619,12 +632,12 @@ EOF
 test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 
 	git format-patch --cover-letter -2 &&
-	sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
+	sed -e "1,/A U Thor/d" -e "/^\$/q" <0000-cover-letter.patch >output &&
 	test_cmp expect output
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 index $before..$after 100644
 --- a/file
 +++ b/file
@@ -646,7 +659,7 @@ test_expect_success 'format-patch respects -U' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 
 diff --git a/file b/file
 index $before..$after 100644
@@ -662,7 +675,7 @@ EOF
 test_expect_success 'format-patch -p suppresses stat' '
 
 	git format-patch -p -2 &&
-	sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+	sed -e "1,/^\$/d" -e "/^+5/q" <0001-This-is-an-excessively-long-subject-line-for-a-messa.patch >output &&
 	test_cmp expect output
 
 '
@@ -717,7 +730,7 @@ test_expect_success 'format-patch from a subdirectory (3)' '
 '
 
 test_expect_success 'format-patch --in-reply-to' '
-	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" >patch8 &&
 	grep "^In-Reply-To: <baz@foo.bar>" patch8 &&
 	grep "^References: <baz@foo.bar>" patch8
 '
@@ -738,20 +751,20 @@ test_expect_success 'format-patch --notes --signoff' '
 	sed "1,/^---$/d" out | grep "test message"
 '
 
-echo "fatal: --name-only does not make sense" > expect.name-only
-echo "fatal: --name-status does not make sense" > expect.name-status
-echo "fatal: --check does not make sense" > expect.check
+echo "fatal: --name-only does not make sense" >expect.name-only
+echo "fatal: --name-status does not make sense" >expect.name-status
+echo "fatal: --check does not make sense" >expect.check
 
 test_expect_success 'options no longer allowed for format-patch' '
-	test_must_fail git format-patch --name-only 2> output &&
+	test_must_fail git format-patch --name-only 2>output &&
 	test_i18ncmp expect.name-only output &&
-	test_must_fail git format-patch --name-status 2> output &&
+	test_must_fail git format-patch --name-status 2>output &&
 	test_i18ncmp expect.name-status output &&
-	test_must_fail git format-patch --check 2> output &&
+	test_must_fail git format-patch --check 2>output &&
 	test_i18ncmp expect.check output'
 
 test_expect_success 'format-patch --numstat should produce a patch' '
-	git format-patch --numstat --stdout master..side > output &&
+	git format-patch --numstat --stdout master..side >output &&
 	test 5 = $(grep "^diff --git a/" output | wc -l)'
 
 test_expect_success 'format-patch -- <path>' '
@@ -763,20 +776,22 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' '
 	git format-patch --ignore-if-in-upstream HEAD
 '
 
-git_version="$(git --version | sed "s/.* //")"
+git_version="$(git --version >version && sed "s/.* //" version)"
 
 signature() {
 	printf "%s\n%s\n\n" "-- " "${1:-$git_version}"
 }
 
 test_expect_success 'format-patch default signature' '
-	git format-patch --stdout -1 | tail -n 3 >output &&
+	git format-patch --stdout -1 >patch &&
+	tail -n 3 patch >output &&
 	signature >expect &&
 	test_cmp expect output
 '
 
 test_expect_success 'format-patch --signature' '
-	git format-patch --stdout --signature="my sig" -1 | tail -n 3 >output &&
+	git format-patch --stdout --signature="my sig" -1 >patch &&
+	tail -n 3 patch >output &&
 	signature "my sig" >expect &&
 	test_cmp expect output
 '
@@ -1167,282 +1182,282 @@ append_signoff()
 
 test_expect_success 'signoff: commit with no body' '
 	append_signoff </dev/null >actual &&
-	cat <<\EOF | sed "s/EOL$//" >expected &&
-4:Subject: [PATCH] EOL
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject' '
 	echo subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject that does not end with NL' '
 	printf subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs and no trailing NL' '
 	printf "subject\n\nbody" | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: misc conforming footer elements' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
-Tested-by: Some One <someone@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-15:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <someone@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	15:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff-alike' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-Fooled-by-me: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-I want to mention about Signed-off-by: here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:I want to mention about Signed-off-by: here.
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	I want to mention about Signed-off-by: here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:I want to mention about Signed-off-by: here.
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff (2)' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-My unfortunate
-Signed-off-by: example happens to be wrapped here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:Signed-off-by: example happens to be wrapped here.
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	My unfortunate
+	Signed-off-by: example happens to be wrapped here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:Signed-off-by: example happens to be wrapped here.
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Signed-off-by: my@house
-Signed-off-by: your@house
+	Signed-off-by: my@house
+	Signed-off-by: your@house
 
-A lot of houses.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: my@house
-10:Signed-off-by: your@house
-11:
-13:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	A lot of houses.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: my@house
+	10:Signed-off-by: your@house
+	11:
+	13:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
 	printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff NOT at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	12:Signed-off-by: my@house
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: tolerate garbage in conforming footer' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Tested-by: my@house
-Some Trash
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: respect trailer config' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual &&
 
 	test_config trailer.Myfooter.ifexists add &&
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Reviewed-id: Noone
-Tested-by: my@house
-Change-id: Ideadbeef
-Signed-off-by: C O Mitter <committer@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <committer@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
@@ -1458,42 +1473,42 @@ test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (2)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter rebuild-1~2..rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (3)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter ^master rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (4)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master.. >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (5)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 HEAD >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (6)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter with nothing' '
@@ -1547,7 +1562,8 @@ test_expect_success 'format-patch format.outputDirectory option' '
 	test_config format.outputDirectory patches &&
 	rm -fr patches &&
 	git format-patch master..side &&
-	test $(git rev-list master..side | wc -l) -eq $(ls patches | wc -l)
+	git rev-list master..side >list &&
+	test_line_count = $(ls patches | wc -l) list
 '
 
 test_expect_success 'format-patch -o overrides format.outputDirectory' '
@@ -1560,13 +1576,21 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
 
 test_expect_success 'format-patch --base' '
 	git checkout side &&
-	git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual1 &&
-	git format-patch --stdout --base=HEAD~3 HEAD~.. | tail -n 7 >actual2 &&
+	git format-patch --stdout --base=HEAD~3 -1 >patch &&
+	tail -n 7 patch >actual1 &&
+	git format-patch --stdout --base=HEAD~3 HEAD~.. >patch &&
+	tail -n 7 patch >actual2 &&
 	echo >expected &&
 	echo "base-commit: $(git rev-parse HEAD~3)" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	signature >> expected &&
+	git show --patch HEAD~2 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	git show --patch HEAD~1 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	signature >>expected &&
 	test_cmp expected actual1 &&
 	test_cmp expected actual2
 '
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v2 2/6] Doc: add more detail for git-format-patch
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
  2019-05-17  0:27   ` [PATCH v2 1/6] t4014: clean up style Denton Liu
@ 2019-05-17  0:27   ` Denton Liu
  2019-05-17  0:27   ` [PATCH v2 3/6] format-patch: make cover letter subject configurable Denton Liu
                     ` (5 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17  0:27 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In git-format-patch.txt, we were missing some key user information.
First of all, using the `--to` and `--cc` options don't override
`format.to` and `format.cc` variables, respectively. They add on to each
other. Document this.

In addition, document the special value of `--base=auto`.

Finally, while we're at it, surround option arguments with <>.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/git-format-patch.txt | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..7b71d4e2ed 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -17,9 +17,9 @@ SYNOPSIS
 		   [--signature-file=<file>]
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
-		   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
-		   [--rfc] [--subject-prefix=Subject-Prefix]
+		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
 		   [--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
@@ -158,7 +158,7 @@ Beware that the default for 'git send-email' is to thread emails
 itself.  If you want `git format-patch` to take care of threading, you
 will want to ensure that threading is disabled for `git send-email`.
 
---in-reply-to=Message-Id::
+--in-reply-to=<Message-Id>::
 	Make the first mail (or all the mails with `--no-thread`) appear as a
 	reply to the given Message-Id, which avoids breaking threads to
 	provide a new patch series.
@@ -192,13 +192,17 @@ will want to ensure that threading is disabled for `git send-email`.
 
 --to=<email>::
 	Add a `To:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
 --cc=<email>::
 	Add a `Cc:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -309,7 +313,8 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 --base=<commit>::
 	Record the base tree information to identify the state the
 	patch series applies to.  See the BASE TREE INFORMATION section
-	below for details.
+	below for details. If <commit> is equal to "auto", a base commit
+	is automatically chosen.
 
 --root::
 	Treat the revision argument as a <revision range>, even if it
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v2 3/6] format-patch: make cover letter subject configurable
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
  2019-05-17  0:27   ` [PATCH v2 1/6] t4014: clean up style Denton Liu
  2019-05-17  0:27   ` [PATCH v2 2/6] Doc: add more detail for git-format-patch Denton Liu
@ 2019-05-17  0:27   ` Denton Liu
  2019-05-17  0:27   ` [PATCH v2 4/6] format-patch: move extra_headers logic later Denton Liu
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17  0:27 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

We used to populate the subject of the cover letter generated by
git-format-patch with "*** SUBJECT HERE ***". However, if a user submits
multiple patchsets, they may want to keep a consistent subject between
rerolls.

If git-format-patch is run on a branch that has
`format.<branch-name>.coverSubject` defined, make the cover letter's
subject be that value instead of the generic "*** SUBJECT HERE ***".

In addition, add the `--cover-subject` option to override this setting.

Finally, make `git branch -d` also remove any `format.<branch-name>.*`
configs, just like it currently removes all `branch.<branch-name>.*`
configs.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |  5 ++++
 Documentation/git-format-patch.txt | 12 +++++++++
 builtin/branch.c                   | 16 +++++++++---
 builtin/log.c                      | 41 +++++++++++++++++++++++++-----
 t/t3200-branch.sh                  |  4 ++-
 t/t4014-format-patch.sh            | 20 +++++++++++++++
 t/t9902-completion.sh              |  5 +++-
 7 files changed, 91 insertions(+), 12 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index dc77941c48..7d2b3b7331 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -36,6 +36,11 @@ format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
 	subject prefix. Use this variable to change that prefix.
 
+format.<branch-name>.coverSubject::
+	When format-patch generates a cover letter for the given
+	<branch-name>, use the specified subject for the cover letter
+	instead of the generic template.
+
 format.signature::
 	The default for format-patch is to output a signature containing
 	the Git version number. Use this variable to change that default.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 7b71d4e2ed..a43f317877 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -19,6 +19,7 @@ SYNOPSIS
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
+		   [--cover-subject=<subject>]
 		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
@@ -170,6 +171,10 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--cover-subject=<subject>::
+	Instead of using the default "*** SUBJECT HERE ***" subject for
+	the cover letter, use the given <subject>.
+
 --subject-prefix=<Subject-Prefix>::
 	Instead of the standard '[PATCH]' prefix in the subject
 	line, instead use '[<Subject-Prefix>]'. This
@@ -346,6 +351,13 @@ attachments, and sign off patches with configuration variables.
 	coverletter = auto
 ------------
 
+In addition, for a specific branch, you can specify a custom cover
+letter subject.
+
+------------
+[format "branch-name"]
+	coverSubject = "subject for branch-name only"
+------------
 
 DISCUSSION
 ----------
diff --git a/builtin/branch.c b/builtin/branch.c
index d4359b33ac..367e1fc9bc 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -178,12 +178,22 @@ static int check_branch_commit(const char *branchname, const char *refname,
 	return 0;
 }
 
+static const char *branch_specific_config[] = {
+	"branch",
+	"format",
+	NULL
+};
+
 static void delete_branch_config(const char *branchname)
 {
 	struct strbuf buf = STRBUF_INIT;
-	strbuf_addf(&buf, "branch.%s", branchname);
-	if (git_config_rename_section(buf.buf, NULL) < 0)
-		warning(_("Update of config-file failed"));
+	int i;
+	for (i = 0; branch_specific_config[i]; i++) {
+		strbuf_addf(&buf, "%s.%s", branch_specific_config[i], branchname);
+		if (git_config_rename_section(buf.buf, NULL) < 0)
+			warning(_("Update of config-file failed"));
+		strbuf_reset(&buf);
+	}
 	strbuf_release(&buf);
 }
 
diff --git a/builtin/log.c b/builtin/log.c
index e43ee12fb1..2bbe712e7c 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1030,17 +1030,33 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static int read_branch_subject(struct strbuf *buf, const char *branch_name)
+{
+	char *v = NULL;
+	struct strbuf name = STRBUF_INIT;
+	strbuf_addf(&name, "format.%s.coverSubject", branch_name);
+	if (git_config_get_string(name.buf, &v)) {
+		strbuf_release(&name);
+		return -1;
+	}
+	strbuf_addstr(buf, v);
+	free(v);
+	strbuf_release(&name);
+	return 0;
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
 			      const char *branch_name,
+			      const char *subject,
 			      int quiet)
 {
 	const char *committer;
-	const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-	const char *msg;
+	const char *body = "*** BLURB HERE ***";
 	struct shortlog log;
 	struct strbuf sb = STRBUF_INIT;
+	struct strbuf subject_sb = STRBUF_INIT;
 	int i;
 	const char *encoding = "UTF-8";
 	int need_8bit_cte = 0;
@@ -1068,17 +1084,24 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 	if (!branch_name)
 		branch_name = find_branch_name(rev);
 
-	msg = body;
+	if (!subject) {
+		if (branch_name && *branch_name && !read_branch_subject(&subject_sb, branch_name))
+			subject = subject_sb.buf;
+		else
+			subject = "*** SUBJECT HERE ***";
+	}
+
 	pp.fmt = CMIT_FMT_EMAIL;
 	pp.date_mode.type = DATE_RFC2822;
 	pp.rev = rev;
 	pp.print_email_subject = 1;
 	pp_user_info(&pp, NULL, &sb, committer, encoding);
-	pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
-	pp_remainder(&pp, &msg, &sb, 0);
+	pp_title_line(&pp, &subject, &sb, encoding, need_8bit_cte);
+	pp_remainder(&pp, &body, &sb, 0);
 	add_branch_description(&sb, branch_name);
 	fprintf(rev->diffopt.file, "%s\n", sb.buf);
 
+	strbuf_release(&subject_sb);
 	strbuf_release(&sb);
 
 	shortlog_init(&log);
@@ -1512,6 +1535,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	int no_binary_diff = 0;
 	int zero_commit = 0;
 	struct commit *origin = NULL;
+	const char *cover_subject = NULL;
 	const char *in_reply_to = NULL;
 	struct patch_ids ids;
 	struct strbuf buf = STRBUF_INIT;
@@ -1554,6 +1578,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "rfc", &rev, NULL,
 			    N_("Use [RFC PATCH] instead of [PATCH]"),
 			    PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+		OPT_STRING(0, "cover-subject", &cover_subject, N_("subject"),
+			    N_("the subject for the cover letter")),
 		{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
 			    N_("Use [<prefix>] instead of [PATCH]"),
 			    PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1617,8 +1643,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	extra_to.strdup_strings = 1;
 	extra_cc.strdup_strings = 1;
 	init_log_defaults();
-	git_config(git_format_config, NULL);
 	repo_init_revisions(the_repository, &rev, prefix);
+
+	git_config(git_format_config, NULL);
 	rev.commit_format = CMIT_FMT_EMAIL;
 	rev.expand_tabs_in_log_default = 0;
 	rev.verbose_header = 1;
@@ -1893,7 +1920,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		if (thread)
 			gen_message_id(&rev, "cover");
 		make_cover_letter(&rev, use_stdout,
-				  origin, nr, list, branch_name, quiet);
+				  origin, nr, list, branch_name, cover_subject, quiet);
 		print_bases(&bases, rev.diffopt.file);
 		print_signature(rev.diffopt.file);
 		total++;
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index e9ad50b66d..dad23f7d4c 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -782,9 +782,11 @@ test_expect_success 'test tracking setup via --track but deeper' '
 '
 
 test_expect_success 'test deleting branch deletes branch config' '
+	git config format.my7.coverSubject "cover subject" &&
 	git branch -d my7 &&
 	test -z "$(git config branch.my7.remote)" &&
-	test -z "$(git config branch.my7.merge)"
+	test -z "$(git config branch.my7.merge)" &&
+	test -z "$(git config format.my7.coverSubject)"
 '
 
 test_expect_success 'test deleting branch without config' '
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 3423f974bc..9d11992e10 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1469,6 +1469,26 @@ test_expect_success 'format patch ignores color.ui' '
 	test_cmp expect actual
 '
 
+test_expect_success 'cover letter with config subject' '
+	test_config format.rebuild-1.coverSubject "config subject" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "Subject: \[PATCH 0/2\] config subject" actual
+'
+
+test_expect_success 'cover letter with command-line subject' '
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-subject "command-line subject" master >actual &&
+	grep "Subject: \[PATCH 0/2\] command-line subject" actual
+'
+
+test_expect_success 'cover letter with command-line subject overrides config' '
+	test_config format.rebuild-1.coverSubject "config subject" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-subject "command-line subject" master >actual &&
+	grep "Subject: \[PATCH 0/2\] command-line subject" actual
+'
+
 test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 43cf313a1c..a96f1662c7 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1548,7 +1548,10 @@ test_expect_success 'complete tree filename with metacharacters' '
 '
 
 test_expect_success PERL 'send-email' '
-	test_completion "git send-email --cov" "--cover-letter " &&
+	test_completion "git send-email --cov" <<-\EOF &&
+	--cover-letter Z
+	--cover-subject=Z
+	EOF
 	test_completion "git send-email ma" "master "
 '
 
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v2 4/6] format-patch: move extra_headers logic later
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
                     ` (2 preceding siblings ...)
  2019-05-17  0:27   ` [PATCH v2 3/6] format-patch: make cover letter subject configurable Denton Liu
@ 2019-05-17  0:27   ` Denton Liu
  2019-05-17  0:27   ` [PATCH v2 5/6] string-list: create string_list_append_all Denton Liu
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17  0:27 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we need to perform the addition of To: and Cc:
to extra_headers after the branch_name logic. Simply transpose this
logic later in the function so that this happens. (This patch is best
viewed with `git diff --color-moved`.)

Note that this logic only depends on the `git_config` and
`repo_init_revisions` and is depended on by the patch creation logic
which is directly below it so this move is effectively a no-op as
no dependencies being reordered.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/builtin/log.c b/builtin/log.c
index 2bbe712e7c..f66a65fe84 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1680,35 +1680,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		rev.subject_prefix = strbuf_detach(&sprefix, NULL);
 	}
 
-	for (i = 0; i < extra_hdr.nr; i++) {
-		strbuf_addstr(&buf, extra_hdr.items[i].string);
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_to.nr)
-		strbuf_addstr(&buf, "To: ");
-	for (i = 0; i < extra_to.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_to.items[i].string);
-		if (i + 1 < extra_to.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_cc.nr)
-		strbuf_addstr(&buf, "Cc: ");
-	for (i = 0; i < extra_cc.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_cc.items[i].string);
-		if (i + 1 < extra_cc.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	rev.extra_headers = strbuf_detach(&buf, NULL);
-
 	if (from) {
 		if (split_ident_line(&rev.from_ident, from, strlen(from)))
 			die(_("invalid ident line: %s"), from);
@@ -1811,6 +1782,35 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	for (i = 0; i < extra_hdr.nr; i++) {
+		strbuf_addstr(&buf, extra_hdr.items[i].string);
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_to.nr)
+		strbuf_addstr(&buf, "To: ");
+	for (i = 0; i < extra_to.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_to.items[i].string);
+		if (i + 1 < extra_to.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_cc.nr)
+		strbuf_addstr(&buf, "Cc: ");
+	for (i = 0; i < extra_cc.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_cc.items[i].string);
+		if (i + 1 < extra_cc.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	rev.extra_headers = strbuf_detach(&buf, NULL);
+
 	/*
 	 * We cannot move this anywhere earlier because we do want to
 	 * know if --root was given explicitly from the command line.
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v2 5/6] string-list: create string_list_append_all
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
                     ` (3 preceding siblings ...)
  2019-05-17  0:27   ` [PATCH v2 4/6] format-patch: move extra_headers logic later Denton Liu
@ 2019-05-17  0:27   ` Denton Liu
  2019-05-17  0:27   ` [PATCH v2 6/6] format-patch: read branch-specific To: and Cc: headers Denton Liu
                     ` (2 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17  0:27 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we'll need to take one string_list and append it to
the end of another. Create the `string_list_append_all` function which
does this.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)

diff --git a/string-list.c b/string-list.c
index a917955fbd..e63d58fbd2 100644
--- a/string-list.c
+++ b/string-list.c
@@ -215,6 +215,15 @@ struct string_list_item *string_list_append(struct string_list *list,
 			list->strdup_strings ? xstrdup(string) : (char *)string);
 }
 
+void string_list_append_all(struct string_list *list,
+			    const struct string_list *append_list)
+{
+	struct string_list_item *item;
+	ALLOC_GROW(list->items, list->nr + append_list->nr, list->alloc);
+	for_each_string_list_item(item, append_list)
+		string_list_append(list, item->string);
+}
+
 /*
  * Encapsulate the compare function pointer because ISO C99 forbids
  * casting from void * to a function pointer and vice versa.
diff --git a/string-list.h b/string-list.h
index f964399949..8227e00e6a 100644
--- a/string-list.h
+++ b/string-list.h
@@ -208,6 +208,13 @@ struct string_list_item *string_list_append(struct string_list *list, const char
  */
 struct string_list_item *string_list_append_nodup(struct string_list *list, char *string);
 
+/**
+ * Add all strings in append_list to list.  If list->strdup_string is
+ * set, then each string is copied; otherwise the new string_list_entry
+ * refers to the entry in the append_list.
+ */
+void string_list_append_all(struct string_list *list, const struct string_list *append_list);
+
 /**
  * Sort the list's entries by string value in `strcmp()` order.
  */
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v2 6/6] format-patch: read branch-specific To: and Cc: headers
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
                     ` (4 preceding siblings ...)
  2019-05-17  0:27   ` [PATCH v2 5/6] string-list: create string_list_append_all Denton Liu
@ 2019-05-17  0:27   ` Denton Liu
  2019-05-17  4:12   ` [PATCH v2 0/6] teach branch-specific options for format-patch Junio C Hamano
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17  0:27 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

If a user wishes to keep track of whom to Cc: on individual patchsets,
they must manually keep track of each recipient and fill it in with the
`--cc` option on git-format-patch each time. However, on the Git mailing
list, Cc:'s are typically never dropped. As a result, it would be nice
to have a method to keep track of recipients on a per-branch basis.

Currently, git-format-patch gets its To: headers from the `--to` options
and the `format.to` config variable. The Cc: header is derived
similarly.

In addition to the above, read To: and Cc: headers from
`format.<branch-name>.to` and `format.<branch-name>.cc` so that users
can have branch-specific configuration options.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |   7 +-
 Documentation/git-format-patch.txt |  10 +--
 builtin/log.c                      |  63 +++++++++++++++--
 t/t3200-branch.sh                  |   4 ++
 t/t4014-format-patch.sh            | 108 +++++++++++++++++++++++------
 5 files changed, 162 insertions(+), 30 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index 7d2b3b7331..d387451573 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -28,9 +28,12 @@ format.headers::
 
 format.to::
 format.cc::
+format.<branch-name>.to::
+format.<branch-name>.cc::
 	Additional recipients to include in a patch to be submitted
-	by mail.  See the --to and --cc options in
-	linkgit:git-format-patch[1].
+	by mail.  For the <branch-name> options, the recipients are only
+	included if patches are generated for the given <branch-name>.
+	See the --to and --cc options in linkgit:git-format-patch[1].
 
 format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index a43f317877..4e826010f6 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -199,7 +199,7 @@ will want to ensure that threading is disabled for `git send-email`.
 	Add a `To:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.to` configurations.
+	`format.to` and `format.<branch-name>.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
@@ -207,7 +207,7 @@ will want to ensure that threading is disabled for `git send-email`.
 	Add a `Cc:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.cc` configurations.
+	`format.cc` and `format.<branch-name>.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -335,7 +335,7 @@ CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
 defaults for the subject prefix and file suffix, number patches when
-outputting more than one patch, add "To" or "Cc:" headers, configure
+outputting more than one patch, add "To:" or "Cc:" headers, configure
 attachments, and sign off patches with configuration variables.
 
 ------------
@@ -352,11 +352,13 @@ attachments, and sign off patches with configuration variables.
 ------------
 
 In addition, for a specific branch, you can specify a custom cover
-letter subject.
+letter subject, and add additional "To:" or "Cc:" headers.
 
 ------------
 [format "branch-name"]
 	coverSubject = "subject for branch-name only"
+	to = <email>
+	cc = <email>
 ------------
 
 DISCUSSION
diff --git a/builtin/log.c b/builtin/log.c
index f66a65fe84..e1d793577d 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -738,6 +738,8 @@ static char *default_attach = NULL;
 static struct string_list extra_hdr = STRING_LIST_INIT_NODUP;
 static struct string_list extra_to = STRING_LIST_INIT_NODUP;
 static struct string_list extra_cc = STRING_LIST_INIT_NODUP;
+int to_cleared;
+int cc_cleared;
 
 static void add_header(const char *value)
 {
@@ -1045,6 +1047,55 @@ static int read_branch_subject(struct strbuf *buf, const char *branch_name)
 	return 0;
 }
 
+static void add_branch_headers(struct rev_info *rev, const char *branch_name)
+{
+	struct strbuf buf = STRBUF_INIT;
+	const struct string_list *values;
+
+	if (!branch_name)
+		branch_name = find_branch_name(rev);
+
+	if (!branch_name || !*branch_name)
+		return;
+
+	/*
+	 * HACK: We only use branch-specific recipients iff the list has not
+	 * been cleared by an earlier --no-{to,cc} option on the command-line.
+	 *
+	 * When we get format.{to,cc} options, they can be cleared by
+	 * --no-{to,cc} options since the `git_config` call comes before the
+	 *  `parse_options` call.
+	 *
+	 *  However, in the case of branch.<name>.{to,cc}, this function needs
+	 *  to be called after `setup_revisions`, which must be called after
+	 *  `parse_options`. However, in order for the --no-{to,cc} logic to
+	 *  clear the extra_{to,cc} string_list, this function should actually
+	 *  be called _before_ `parse_options`. As a result, we have a circular
+	 *  dependency.
+	 *
+	 *  The {to,cc}_cleared flag lets us workaround this by just no
+	 *  including branch-specific recipients iff --no-{to,cc} has been
+	 *  specified on the command-line.
+	 */
+
+	if (!to_cleared) {
+		strbuf_addf(&buf, "format.%s.to", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_to, values);
+	}
+
+	if (!cc_cleared) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "format.%s.cc", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_cc, values);
+	}
+
+	strbuf_release(&buf);
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
@@ -1304,18 +1355,20 @@ static int header_callback(const struct option *opt, const char *arg, int unset)
 
 static int to_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		to_cleared = 1;
 		string_list_clear(&extra_to, 0);
-	else
+	} else
 		string_list_append(&extra_to, arg);
 	return 0;
 }
 
 static int cc_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		cc_cleared = 1;
 		string_list_clear(&extra_cc, 0);
-	else
+	} else
 		string_list_append(&extra_cc, arg);
 	return 0;
 }
@@ -1782,6 +1835,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	add_branch_headers(&rev, branch_name);
+
 	for (i = 0; i < extra_hdr.nr; i++) {
 		strbuf_addstr(&buf, extra_hdr.items[i].string);
 		strbuf_addch(&buf, '\n');
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index dad23f7d4c..9ae3da888e 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -783,10 +783,14 @@ test_expect_success 'test tracking setup via --track but deeper' '
 
 test_expect_success 'test deleting branch deletes branch config' '
 	git config format.my7.coverSubject "cover subject" &&
+	git config format.my7.to "To Me <to@example.com>" &&
+	git config format.my7.cc "Cc Me <cc@example.com>" &&
 	git branch -d my7 &&
 	test -z "$(git config branch.my7.remote)" &&
 	test -z "$(git config branch.my7.merge)" &&
 	test -z "$(git config format.my7.coverSubject)"
+	test -z "$(git config format.my7.to)" &&
+	test -z "$(git config format.my7.cc)"
 '
 
 test_expect_success 'test deleting branch without config' '
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 9d11992e10..9c527510c3 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -234,6 +234,65 @@ test_expect_failure 'configuration To: header (rfc2047)' '
 	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
+test_expect_success 'branch-specific configuration To: header (ascii)' '
+
+	test_unconfig format.to &&
+	git config format.side.to "R E Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc822)' '
+
+	git config format.side.to "R. E. Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc2047)' '
+
+	git config format.side.to "R Ä Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_success 'all recipients included from all sources' '
+
+	git config format.to "Format To1 <formatto1@example.com>" &&
+	git config --add format.to "Format To2 <formatto2@example.com>" &&
+	git config format.cc "Format Cc1 <formatcc1@example.com>" &&
+	git config --add format.cc "Format Cc2 <formatcc2@example.com>" &&
+	git config format.side.to "Branch To1 <branchto1@example.com>" &&
+	git config --add format.side.to "Branch To2 <branchto2@example.com>" &&
+	git config format.side.cc "Branch Cc1 <branchcc1@example.com>" &&
+	git config --add format.side.cc "Branch Cc2 <branchcc2@example.com>" &&
+	cat <<-\EOF >expect &&
+	To: Format To1 <formatto1@example.com>,
+	    Format To2 <formatto2@example.com>,
+	    Command-line To1 <commandlineto1@example.com>,
+	    Command-line To2 <commandlineto2@example.com>,
+	    Branch To1 <branchto1@example.com>,
+	    Branch To2 <branchto2@example.com>
+	Cc: Format Cc1 <formatcc1@example.com>,
+	    Format Cc2 <formatcc2@example.com>,
+	    Command-line Cc1 <commandlinecc1@example.com>,
+	    Command-line Cc2 <commandlinecc2@example.com>,
+	    Branch Cc1 <branchcc1@example.com>,
+	    Branch Cc2 <branchcc2@example.com>
+
+	EOF
+	git format-patch --stdout \
+		--to="Command-line To1 <commandlineto1@example.com>" \
+		--to="Command-line To2 <commandlineto2@example.com>" \
+		--cc="Command-line Cc1 <commandlinecc1@example.com>" \
+		--cc="Command-line Cc2 <commandlinecc2@example.com>" \
+		master..side | sed -ne "/^To:/,/^$/p;/^$/q" >patch10 &&
+	test_cmp expect patch10
+'
+
 # check_patch <patch>: Verify that <patch> looks like a half-sane
 # patch email to avoid a false positive with !grep
 check_patch () {
@@ -286,42 +345,51 @@ test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side >patch10 &&
-	sed -e "/^\$/q" patch10 >hdrs10 &&
-	check_patch hdrs10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+	git config --replace-all format.side.to \
+		"B R Anch <branch@example.com>" &&
+	git format-patch --no-to --stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "R E Cipient <rcipient@example.com>" hdrs11 &&
+	! grep "B R Anch <branch@example.com>" hdrs11
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
 
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
+	git config --replace-all format.side.to \
+		"B R Anch2 <branch2@example.com>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side >patch11 &&
-	sed -e "/^\$/q" patch11 >hdrs11 &&
-	check_patch hdrs11 &&
-	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
-	grep "^To: Someone Else <else@out.there>\$" hdrs11
+		--stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "Someone <someone@out.there>" hdrs12 &&
+	! grep "B R Anch2 <branch2@example.com>" hdrs12 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs12
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side >patch12 &&
-	sed -e "/^\$/q" patch12 >hdrs12 &&
-	check_patch hdrs12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
+	git config --replace-all format.side.cc \
+		"B R Anch3 <branch3@example.com>" &&
+	git format-patch --no-cc --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "C E Cipient <rcipient@example.com>" hdrs13 &&
+	! grep "B R Anch3 <branch3@example.com>" hdrs13
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side >patch13 &&
-	sed -e "/^\$/q" patch13 >hdrs13 &&
-	check_patch hdrs13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
+	git format-patch --no-add-header --stdout master..side >patch14 &&
+	sed -e "/^\$/q" patch14 >hdrs14 &&
+	check_patch hdrs14 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs14
 '
 
 test_expect_success 'multiple files' '
@@ -963,7 +1031,7 @@ test_expect_success 'format-patch wraps extremely long subject (ascii)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1002,7 +1070,7 @@ test_expect_success 'format-patch wraps extremely long subject (rfc2047)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1011,7 +1079,7 @@ check_author() {
 	git add file &&
 	GIT_AUTHOR_NAME=$1 git commit -m author-check &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^From: /p; /^ /p; /^$/q" <patch >actual &&
+	sed -n "/^From: /p; /^ /p; /^Date:/q" <patch >actual &&
 	test_cmp expect actual
 }
 
-- 
2.21.0.1049.geb646f7864


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* Re: [PATCH v2 0/6] teach branch-specific options for format-patch
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
                     ` (5 preceding siblings ...)
  2019-05-17  0:27   ` [PATCH v2 6/6] format-patch: read branch-specific To: and Cc: headers Denton Liu
@ 2019-05-17  4:12   ` Junio C Hamano
  2019-05-17  7:25     ` Denton Liu
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
  7 siblings, 1 reply; 59+ messages in thread
From: Junio C Hamano @ 2019-05-17  4:12 UTC (permalink / raw)
  To: Denton Liu; +Cc: Git Mailing List, Ævar Arnfjörð Bjarmason

Denton Liu <liu.denton@gmail.com> writes:

> diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
> index dc77941c48..d387451573 100644
> --- a/Documentation/config/format.txt
> +++ b/Documentation/config/format.txt
> @@ -28,14 +28,22 @@ format.headers::
>  
>  format.to::
>  format.cc::
> +format.<branch-name>.to::
> +format.<branch-name>.cc::
>  	Additional recipients to include in a patch to be submitted
> -	by mail.  See the --to and --cc options in
> -	linkgit:git-format-patch[1].
> +	by mail.  For the <branch-name> options, the recipients are only
> +	included if patches are generated for the given <branch-name>.
> +	See the --to and --cc options in linkgit:git-format-patch[1].

An obvious question that somebody else may raise is:

    What makes the branch name that special?  What guarantees that
    it would stay to be the *only* thing that affects the choice of
    these variables?

An obvious answer to that is "nothing---we are painting ourselves in
a corner we cannot easily get out of with this design".

If we want to drive format-patch differently depending on the
combination of the worktree location *and* the branch the patches
are generated from, we can do something like:

	[includeif "gitdir:/path/to/worktree/1"] path = one.inc
	[includeif "gitdir:/path/to/worktree/2"] path = two.inc

and then have one.inc/two.inc have customized definition of these
format.<branch>.{to,cc,...} variables.

But at that point, Ævar's "wouldn't this fit better with includeif"
suggestion becomes more and more appropriate.  Once we invent the
way to combine the conditions for includeIf, it would benefit not
just this set of variables but all others that will follow in the
future.

Having said that, as long as we are fine with the plan to deprecate
and remove these three-level variables this patch introdues in the
future, I think it is OK to have them as a temporary stop-gap
measure.

> +format.<branch-name>.coverSubject::
> +	When format-patch generates a cover letter for the given
> +	<branch-name>, use the specified subject for the cover letter
> +	instead of the generic template.

I still think it is a mistake that this has to be given separately
and possibly redundantly from the branch description.

> +static const char *branch_specific_config[] = {
> +	"branch",
> +	"format",
> +	NULL
> +};

Yuck.  This will break a workflow where a fixed branch with a known
configuration is deleted and recreated over and over again
(e.g. think of "for-linus" branches used for request-pull in each
merge window).

>  static void delete_branch_config(const char *branchname)
>  {
>  	struct strbuf buf = STRBUF_INIT;
> -	strbuf_addf(&buf, "branch.%s", branchname);
> -	if (git_config_rename_section(buf.buf, NULL) < 0)
> -		warning(_("Update of config-file failed"));
> +	int i;
> +	for (i = 0; branch_specific_config[i]; i++) {
> +		strbuf_addf(&buf, "%s.%s", branch_specific_config[i], branchname);
> +		if (git_config_rename_section(buf.buf, NULL) < 0)
> +			warning(_("Update of config-file failed"));
> +		strbuf_reset(&buf);
> +	}

This will hardcode the unwarranted limitation that the second level
of the format.*.var hierarchy MUST be branch names and nothing else,
won't it?  


^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH v2 0/6] teach branch-specific options for format-patch
  2019-05-17  4:12   ` [PATCH v2 0/6] teach branch-specific options for format-patch Junio C Hamano
@ 2019-05-17  7:25     ` Denton Liu
  2019-05-17 16:54       ` Denton Liu
  0 siblings, 1 reply; 59+ messages in thread
From: Denton Liu @ 2019-05-17  7:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List, Ævar Arnfjörð Bjarmason

Hi Junio,

On Fri, May 17, 2019 at 01:12:04PM +0900, Junio C Hamano wrote:
> Denton Liu <liu.denton@gmail.com> writes:
> 
> > diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
> > index dc77941c48..d387451573 100644
> > --- a/Documentation/config/format.txt
> > +++ b/Documentation/config/format.txt
> > @@ -28,14 +28,22 @@ format.headers::
> >  
> >  format.to::
> >  format.cc::
> > +format.<branch-name>.to::
> > +format.<branch-name>.cc::
> >  	Additional recipients to include in a patch to be submitted
> > -	by mail.  See the --to and --cc options in
> > -	linkgit:git-format-patch[1].
> > +	by mail.  For the <branch-name> options, the recipients are only
> > +	included if patches are generated for the given <branch-name>.
> > +	See the --to and --cc options in linkgit:git-format-patch[1].
> 
> An obvious question that somebody else may raise is:
> 
>     What makes the branch name that special?  What guarantees that
>     it would stay to be the *only* thing that affects the choice of
>     these variables?
> 
> An obvious answer to that is "nothing---we are painting ourselves in
> a corner we cannot easily get out of with this design".
> 
> If we want to drive format-patch differently depending on the
> combination of the worktree location *and* the branch the patches
> are generated from, we can do something like:
> 
> 	[includeif "gitdir:/path/to/worktree/1"] path = one.inc
> 	[includeif "gitdir:/path/to/worktree/2"] path = two.inc
> 
> and then have one.inc/two.inc have customized definition of these
> format.<branch>.{to,cc,...} variables.
> 
> But at that point, Ævar's "wouldn't this fit better with includeif"
> suggestion becomes more and more appropriate.  Once we invent the
> way to combine the conditions for includeIf, it would benefit not
> just this set of variables but all others that will follow in the
> future.

Hmm, I'm starting to like Ævar's idea more the more I think about it.

> 
> Having said that, as long as we are fine with the plan to deprecate
> and remove these three-level variables this patch introdues in the
> future, I think it is OK to have them as a temporary stop-gap
> measure.
> 
> > +format.<branch-name>.coverSubject::
> > +	When format-patch generates a cover letter for the given
> > +	<branch-name>, use the specified subject for the cover letter
> > +	instead of the generic template.
> 
> I still think it is a mistake that this has to be given separately
> and possibly redundantly from the branch description.

I forgot about incorporating this. Since we don't need a branch-specific
coverSubject anymore, we can push everything into a includeif since now
format.<name>.coverSubject doesn't really need to exist.

I'm going to repurpose --cover-subject format.coverSubject to be a
boolean option which'll mean "process the description and if you can
extract a subject out of it, put it on the cover letter". This way, we
can maintain backwards compatability in case users have some specific
use-case.

Unless you'd like this processing to be the default behaviour? I'm
impartial either way.

> 
> > +static const char *branch_specific_config[] = {
> > +	"branch",
> > +	"format",
> > +	NULL
> > +};
> 
> Yuck.  This will break a workflow where a fixed branch with a known
> configuration is deleted and recreated over and over again
> (e.g. think of "for-linus" branches used for request-pull in each
> merge window).

I suppose when we implement `onBranch`, you'd prefer `git branch -d` to
also not discard those sections.

> 
> >  static void delete_branch_config(const char *branchname)
> >  {
> >  	struct strbuf buf = STRBUF_INIT;
> > -	strbuf_addf(&buf, "branch.%s", branchname);
> > -	if (git_config_rename_section(buf.buf, NULL) < 0)
> > -		warning(_("Update of config-file failed"));
> > +	int i;
> > +	for (i = 0; branch_specific_config[i]; i++) {
> > +		strbuf_addf(&buf, "%s.%s", branch_specific_config[i], branchname);
> > +		if (git_config_rename_section(buf.buf, NULL) < 0)
> > +			warning(_("Update of config-file failed"));
> > +		strbuf_reset(&buf);
> > +	}
> 
> This will hardcode the unwarranted limitation that the second level
> of the format.*.var hierarchy MUST be branch names and nothing else,
> won't it?  
> 

I was expecting it to only be branch names but now let's take a
different approach.

Consider patches 3-6 dropped. I'd like to queue 1-2, though, since
they're just cleanup patches.

Also, expect a onBranch patchset some time in the future (not the near
future, school is busy).

Thanks for your feedback, Junio.

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH v2 0/6] teach branch-specific options for format-patch
  2019-05-17  7:25     ` Denton Liu
@ 2019-05-17 16:54       ` Denton Liu
  0 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-17 16:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List, Ævar Arnfjörð Bjarmason

Hi Junio,

I just realised that my use-cases wouldn't be fully covered with the
onBranch configuration option.

On Fri, May 17, 2019 at 03:25:15AM -0400, Denton Liu wrote:
> Hi Junio,
> 
> On Fri, May 17, 2019 at 01:12:04PM +0900, Junio C Hamano wrote:
> > Denton Liu <liu.denton@gmail.com> writes:
> > 
> > > diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
> > > index dc77941c48..d387451573 100644
> > > --- a/Documentation/config/format.txt
> > > +++ b/Documentation/config/format.txt
> > > @@ -28,14 +28,22 @@ format.headers::
> > >  
> > >  format.to::
> > >  format.cc::
> > > +format.<branch-name>.to::
> > > +format.<branch-name>.cc::
> > >  	Additional recipients to include in a patch to be submitted
> > > -	by mail.  See the --to and --cc options in
> > > -	linkgit:git-format-patch[1].
> > > +	by mail.  For the <branch-name> options, the recipients are only
> > > +	included if patches are generated for the given <branch-name>.
> > > +	See the --to and --cc options in linkgit:git-format-patch[1].
> > 
> > An obvious question that somebody else may raise is:
> > 
> >     What makes the branch name that special?  What guarantees that
> >     it would stay to be the *only* thing that affects the choice of
> >     these variables?
> > 
> > An obvious answer to that is "nothing---we are painting ourselves in
> > a corner we cannot easily get out of with this design".
> > 
> > If we want to drive format-patch differently depending on the
> > combination of the worktree location *and* the branch the patches
> > are generated from, we can do something like:
> > 
> > 	[includeif "gitdir:/path/to/worktree/1"] path = one.inc
> > 	[includeif "gitdir:/path/to/worktree/2"] path = two.inc
> > 
> > and then have one.inc/two.inc have customized definition of these
> > format.<branch>.{to,cc,...} variables.
> > 
> > But at that point, Ævar's "wouldn't this fit better with includeif"
> > suggestion becomes more and more appropriate.  Once we invent the
> > way to combine the conditions for includeIf, it would benefit not
> > just this set of variables but all others that will follow in the
> > future.
> 
> Hmm, I'm starting to like Ævar's idea more the more I think about it.
> 

There is one limitation with onBranch. Suppose someone runs

	$ git checkout other
	$ git format-patch master..feature

Then, with onBranch, they'd use get the To and Cc of `other`. But with
`format.feature.*`, format-patch correctly handles this and will use
`feature`'s To and Cc.

> > 
> > Having said that, as long as we are fine with the plan to deprecate
> > and remove these three-level variables this patch introdues in the
> > future, I think it is OK to have them as a temporary stop-gap
> > measure.

With this new discovery, I'm not sure it'd be possible to deprecate it
without losing a use-case.

> > 
> > > +format.<branch-name>.coverSubject::
> > > +	When format-patch generates a cover letter for the given
> > > +	<branch-name>, use the specified subject for the cover letter
> > > +	instead of the generic template.
> > 
> > I still think it is a mistake that this has to be given separately
> > and possibly redundantly from the branch description.
> 
> I forgot about incorporating this. Since we don't need a branch-specific
> coverSubject anymore, we can push everything into a includeif since now
> format.<name>.coverSubject doesn't really need to exist.
> 
> I'm going to repurpose --cover-subject format.coverSubject to be a
> boolean option which'll mean "process the description and if you can
> extract a subject out of it, put it on the cover letter". This way, we
> can maintain backwards compatability in case users have some specific
> use-case.
> 
> Unless you'd like this processing to be the default behaviour? I'm
> impartial either way.
> 
> > 
> > > +static const char *branch_specific_config[] = {
> > > +	"branch",
> > > +	"format",
> > > +	NULL
> > > +};
> > 
> > Yuck.  This will break a workflow where a fixed branch with a known
> > configuration is deleted and recreated over and over again
> > (e.g. think of "for-linus" branches used for request-pull in each
> > merge window).
> 
> I suppose when we implement `onBranch`, you'd prefer `git branch -d` to
> also not discard those sections.
> 
> > 
> > >  static void delete_branch_config(const char *branchname)
> > >  {
> > >  	struct strbuf buf = STRBUF_INIT;
> > > -	strbuf_addf(&buf, "branch.%s", branchname);
> > > -	if (git_config_rename_section(buf.buf, NULL) < 0)
> > > -		warning(_("Update of config-file failed"));
> > > +	int i;
> > > +	for (i = 0; branch_specific_config[i]; i++) {
> > > +		strbuf_addf(&buf, "%s.%s", branch_specific_config[i], branchname);
> > > +		if (git_config_rename_section(buf.buf, NULL) < 0)
> > > +			warning(_("Update of config-file failed"));
> > > +		strbuf_reset(&buf);
> > > +	}
> > 
> > This will hardcode the unwarranted limitation that the second level
> > of the format.*.var hierarchy MUST be branch names and nothing else,
> > won't it?  
> > 
> 
> I was expecting it to only be branch names but now let's take a
> different approach.
> 
> Consider patches 3-6 dropped. I'd like to queue 1-2, though, since
> they're just cleanup patches.

In light of this, I don't plan on dropping 3-6 anymore. I'm going to
reroll the new behaviour of coverSubject.

> 
> Also, expect a onBranch patchset some time in the future (not the near
> future, school is busy).
> 
> Thanks for your feedback, Junio.

^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH v3 0/8] teach branch-specific options for format-patch
  2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
                     ` (6 preceding siblings ...)
  2019-05-17  4:12   ` [PATCH v2 0/6] teach branch-specific options for format-patch Junio C Hamano
@ 2019-05-22  2:44   ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 1/8] t4014: clean up style Denton Liu
                       ` (10 more replies)
  7 siblings, 11 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

Hi Junio,

I've gotten rid of all the `format.*.coverSubject` stuff and replaced it
with a generic `format.inferCoverSubject` that will read the subject
from the branch description. I've also made `git branch -d` stop
deleting the `format.<branch>.*` variables.

A new addition in this patchset is that format-patch is now taught to
understand the `format.<branch-name>.outputDirectory` configuration.

Changes since v2:

* Replaced `format.<branch-name>.coverSubject` and `--cover-subject`
  with `format.inferCoverSubject` and `--infer-cover-subject` which
  reads the subject from the branch description
* Do not let `git branch -d` delete `format.<branch-name>.*` configs
* More documentation cleanup
* Taught format-patch to read `format.<branch-name>.outputDirectory` as
  well

Changes since v1:

* Used format.<branch-name>.* variables instead of branch.<branch-name>.*


Denton Liu (8):
  t4014: clean up style
  Doc: add more detail for git-format-patch
  format-patch: infer cover letter from branch description
  format-patch: move extra_headers logic later
  string-list: create string_list_append_all
  format-patch: read branch-specific To: and Cc: headers
  format-patch: move output_directory logic later
  format-patch: read branch-specific output directory

 Documentation/config/format.txt    |  17 +-
 Documentation/git-format-patch.txt |  44 +-
 builtin/log.c                      | 237 ++++++---
 string-list.c                      |   9 +
 string-list.h                      |   7 +
 t/t4014-format-patch.sh            | 739 +++++++++++++++++------------
 6 files changed, 667 insertions(+), 386 deletions(-)

Range-diff against v2:
1:  92d7be10aa = 1:  82c0dc9cc8 t4014: clean up style
2:  930a021b7f ! 2:  a8cc599fd2 Doc: add more detail for git-format-patch
    @@ -9,7 +9,10 @@
     
         In addition, document the special value of `--base=auto`.
     
    -    Finally, while we're at it, surround option arguments with <>.
    +    Next, while we're at it, surround option arguments with <>.
    +
    +    Finally, document the `format.outputDirectory` config and change
    +    `format.coverletter` to use camelcase.
     
         Signed-off-by: Denton Liu <liu.denton@gmail.com>
     
    @@ -67,3 +70,25 @@
      
      --root::
      	Treat the revision argument as a <revision range>, even if it
    +@@
    + -------------
    + You can specify extra mail header lines to be added to each message,
    + defaults for the subject prefix and file suffix, number patches when
    +-outputting more than one patch, add "To" or "Cc:" headers, configure
    +-attachments, and sign off patches with configuration variables.
    ++outputting more than one patch, add "To:" or "Cc:" headers, configure
    ++attachments, change the patch output directory, and sign off patches
    ++with configuration variables.
    + 
    + ------------
    + [format]
    +@@
    + 	cc = <email>
    + 	attach [ = mime-boundary-string ]
    + 	signOff = true
    +-	coverletter = auto
    ++	outputDirectory = <directory>
    ++	coverLetter = auto
    + ------------
    + 
    + 
3:  075e2c0721 < -:  ---------- format-patch: make cover letter subject configurable
-:  ---------- > 3:  e3b8c96b1c format-patch: infer cover letter from branch description
4:  323179377e ! 4:  52ee126825 format-patch: move extra_headers logic later
    @@ -7,10 +7,10 @@
         logic later in the function so that this happens. (This patch is best
         viewed with `git diff --color-moved`.)
     
    -    Note that this logic only depends on the `git_config` and
    +    Note that this logic only depends on `git_config` and
         `repo_init_revisions` and is depended on by the patch creation logic
    -    which is directly below it so this move is effectively a no-op as
    -    no dependencies being reordered.
    +    which is directly below it so this move is effectively a no-op as no
    +    dependencies being reordered.
     
         Signed-off-by: Denton Liu <liu.denton@gmail.com>
     
5:  7a8fa4f5f6 = 5:  27c60c715c string-list: create string_list_append_all
6:  9d7338f067 ! 6:  f2a1546b2d format-patch: read branch-specific To: and Cc: headers
    @@ -59,29 +59,20 @@
      	far (from config or command line).
      
     @@
    - -------------
    - You can specify extra mail header lines to be added to each message,
    - defaults for the subject prefix and file suffix, number patches when
    --outputting more than one patch, add "To" or "Cc:" headers, configure
    -+outputting more than one patch, add "To:" or "Cc:" headers, configure
    - attachments, and sign off patches with configuration variables.
    - 
    - ------------
    -@@
    + 	inferCoverSubject = true
      ------------
      
    - In addition, for a specific branch, you can specify a custom cover
    --letter subject.
    ++In addition, for a specific branch, you can specify a custom cover
     +letter subject, and add additional "To:" or "Cc:" headers.
    - 
    - ------------
    - [format "branch-name"]
    - 	coverSubject = "subject for branch-name only"
    ++
    ++------------
    ++[format "branch-name"]
     +	to = <email>
     +	cc = <email>
    - ------------
    ++------------
      
      DISCUSSION
    + ----------
     
      diff --git a/builtin/log.c b/builtin/log.c
      --- a/builtin/log.c
    @@ -96,7 +87,7 @@
      static void add_header(const char *value)
      {
     @@
    - 	return 0;
    + 	fprintf(rev->diffopt.file, "\n");
      }
      
     +static void add_branch_headers(struct rev_info *rev, const char *branch_name)
    @@ -186,25 +177,6 @@
      		strbuf_addstr(&buf, extra_hdr.items[i].string);
      		strbuf_addch(&buf, '\n');
     
    - diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
    - --- a/t/t3200-branch.sh
    - +++ b/t/t3200-branch.sh
    -@@
    - 
    - test_expect_success 'test deleting branch deletes branch config' '
    - 	git config format.my7.coverSubject "cover subject" &&
    -+	git config format.my7.to "To Me <to@example.com>" &&
    -+	git config format.my7.cc "Cc Me <cc@example.com>" &&
    - 	git branch -d my7 &&
    - 	test -z "$(git config branch.my7.remote)" &&
    - 	test -z "$(git config branch.my7.merge)" &&
    - 	test -z "$(git config format.my7.coverSubject)"
    -+	test -z "$(git config format.my7.to)" &&
    -+	test -z "$(git config format.my7.cc)"
    - '
    - 
    - test_expect_success 'test deleting branch without config' '
    -
      diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
      --- a/t/t4014-format-patch.sh
      +++ b/t/t4014-format-patch.sh
-:  ---------- > 7:  70e28c5472 format-patch: move output_directory logic later
-:  ---------- > 8:  b23c3c16f7 format-patch: read branch-specific output directory
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH v3 1/8] t4014: clean up style
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 2/8] Doc: add more detail for git-format-patch Denton Liu
                       ` (9 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In Git's tests, there is typically no space between the redirection
operator and the filename. Remove these spaces.

Since output is silenced when running without `-v` and debugging
output is useful with `-v`, remove redirections to /dev/null.

Change here-docs from `<<\EOF` to `<<-\EOF` so that they can be indented
along with the rest of the test case.

Finally, refactor to remove Git commands upstream of pipe. This way, if
an invocation of a Git command fails, the return code won't be lost.
Keep upstream non-Git commands since we have to assume a base level of
sanity.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)

diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index b6e2fdbc44..3423f974bc 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -34,7 +34,8 @@ test_expect_success setup '
 	git commit -m "Side changes #3 with \\n backslash-n in it." &&
 
 	git checkout master &&
-	git diff-tree -p C2 | git apply --index &&
+	git diff-tree -p C2 >patch &&
+	git apply --index <patch &&
 	test_tick &&
 	git commit -m "Master accepts moral equivalent of #2"
 
@@ -77,7 +78,7 @@ test_expect_success "format-patch doesn't consider merge commits" '
 	git checkout -b merger master &&
 	test_tick &&
 	git merge --no-ff slave &&
-	cnt=$(git format-patch -3 --stdout | grep "^From " | wc -l) &&
+	cnt=$(git format-patch -3 --stdout >patch && grep "^From " patch | wc -l) &&
 	test $cnt = 3
 '
 
@@ -85,21 +86,22 @@ test_expect_success "format-patch result applies" '
 
 	git checkout -b rebuild-0 master &&
 	git am -3 patch0 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success "format-patch --ignore-if-in-upstream result applies" '
 
 	git checkout -b rebuild-1 master &&
 	git am -3 patch1 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success 'commit did not screw up the log message' '
 
-	git cat-file commit side | grep "^Side .* with .* backslash-n"
+	git cat-file commit side >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -112,7 +114,8 @@ test_expect_success 'format-patch did not screw up the log message' '
 
 test_expect_success 'replay did not screw up the log message' '
 
-	git cat-file commit rebuild-1 | grep "^Side .* with .* backslash-n"
+	git cat-file commit rebuild-1 >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -122,8 +125,8 @@ test_expect_success 'extra headers' '
 " &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>
 " &&
-	git format-patch --stdout master..side > patch2 &&
-	sed -e "/^\$/q" patch2 > hdrs2 &&
+	git format-patch --stdout master..side >patch2 &&
+	sed -e "/^\$/q" patch2 >hdrs2 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "To: S E Cipient <scipient@example.com>" &&
-	git format-patch --stdout master..side > patch4 &&
-	sed -e "/^\$/q" patch4 > hdrs4 &&
+	git format-patch --stdout master..side >patch4 &&
+	sed -e "/^\$/q" patch4 >hdrs4 &&
 	grep "^To: R E Cipient <rcipient@example.com>,\$" hdrs4 &&
 	grep "^ *S E Cipient <scipient@example.com>\$" hdrs4
 '
@@ -153,72 +156,82 @@ test_expect_success 'extra headers with multiple To:s' '
 test_expect_success 'additional command line cc (ascii)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch5
+	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" patch5
+	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch7 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs7 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch8
+	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch8
+	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch8
+	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
 # check_patch <patch>: Verify that <patch> looks like a half-sane
@@ -231,52 +244,52 @@ check_patch () {
 
 test_expect_success 'format.from=false' '
 
-	git -c format.from=false format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
+	git -c format.from=false format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
 	check_patch patch &&
-	! grep "^From: C O Mitter <committer@example.com>\$" patch
+	! grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from=true' '
 
-	git -c format.from=true format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: C O Mitter <committer@example.com>\$" patch
+	git -c format.from=true format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
@@ -284,31 +297,31 @@ test_expect_success '--no-to and --to replaces config.to' '
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <someone@out.there>\$" patch11 &&
-	grep "^To: Someone Else <else@out.there>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
 '
 
 test_expect_success 'multiple files' '
@@ -338,7 +351,7 @@ test_expect_success 'reroll count (-v)' '
 check_threading () {
 	expect="$1" &&
 	shift &&
-	(git format-patch --stdout "$@"; echo $? > status.out) |
+	(git format-patch --stdout "$@"; echo $? >status.out) |
 	# Prints everything between the Message-ID and In-Reply-To,
 	# and replaces all Message-ID-lookalikes by a sequence number
 	perl -ne '
@@ -353,12 +366,12 @@ check_threading () {
 			print;
 		}
 		print "---\n" if /^From /i;
-	' > actual &&
+	' >actual &&
 	test 0 = "$(cat status.out)" &&
 	test_cmp "$expect" actual
 }
 
-cat >> expect.no-threading <<EOF
+cat >>expect.no-threading <<EOF
 ---
 ---
 ---
@@ -369,7 +382,7 @@ test_expect_success 'no threading' '
 	check_threading expect.no-threading master
 '
 
-cat > expect.thread <<EOF
+cat >expect.thread <<EOF
 ---
 Message-Id: <0>
 ---
@@ -386,7 +399,7 @@ test_expect_success 'thread' '
 	check_threading expect.thread --thread master
 '
 
-cat > expect.in-reply-to <<EOF
+cat >expect.in-reply-to <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -406,7 +419,7 @@ test_expect_success 'thread in-reply-to' '
 		--thread master
 '
 
-cat > expect.cover-letter <<EOF
+cat >expect.cover-letter <<EOF
 ---
 Message-Id: <0>
 ---
@@ -427,7 +440,7 @@ test_expect_success 'thread cover-letter' '
 	check_threading expect.cover-letter --cover-letter --thread master
 '
 
-cat > expect.cl-irt <<EOF
+cat >expect.cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -459,7 +472,7 @@ test_expect_success 'thread explicit shallow' '
 		--in-reply-to="<test.message>" --thread=shallow master
 '
 
-cat > expect.deep <<EOF
+cat >expect.deep <<EOF
 ---
 Message-Id: <0>
 ---
@@ -477,7 +490,7 @@ test_expect_success 'thread deep' '
 	check_threading expect.deep --thread=deep master
 '
 
-cat > expect.deep-irt <<EOF
+cat >expect.deep-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -500,7 +513,7 @@ test_expect_success 'thread deep in-reply-to' '
 		--in-reply-to="<test.message>" master
 '
 
-cat > expect.deep-cl <<EOF
+cat >expect.deep-cl <<EOF
 ---
 Message-Id: <0>
 ---
@@ -524,7 +537,7 @@ test_expect_success 'thread deep cover-letter' '
 	check_threading expect.deep-cl --cover-letter --thread=deep master
 '
 
-cat > expect.deep-cl-irt <<EOF
+cat >expect.deep-cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -606,7 +619,7 @@ test_expect_success 'cover-letter inherits diff options' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
   This is an excessively long subject line for a message due to the
     habit some projects have of not having a short, one-line subject at
     the start of the commit message, but rather sticking a whole
@@ -619,12 +632,12 @@ EOF
 test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 
 	git format-patch --cover-letter -2 &&
-	sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
+	sed -e "1,/A U Thor/d" -e "/^\$/q" <0000-cover-letter.patch >output &&
 	test_cmp expect output
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 index $before..$after 100644
 --- a/file
 +++ b/file
@@ -646,7 +659,7 @@ test_expect_success 'format-patch respects -U' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 
 diff --git a/file b/file
 index $before..$after 100644
@@ -662,7 +675,7 @@ EOF
 test_expect_success 'format-patch -p suppresses stat' '
 
 	git format-patch -p -2 &&
-	sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+	sed -e "1,/^\$/d" -e "/^+5/q" <0001-This-is-an-excessively-long-subject-line-for-a-messa.patch >output &&
 	test_cmp expect output
 
 '
@@ -717,7 +730,7 @@ test_expect_success 'format-patch from a subdirectory (3)' '
 '
 
 test_expect_success 'format-patch --in-reply-to' '
-	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" >patch8 &&
 	grep "^In-Reply-To: <baz@foo.bar>" patch8 &&
 	grep "^References: <baz@foo.bar>" patch8
 '
@@ -738,20 +751,20 @@ test_expect_success 'format-patch --notes --signoff' '
 	sed "1,/^---$/d" out | grep "test message"
 '
 
-echo "fatal: --name-only does not make sense" > expect.name-only
-echo "fatal: --name-status does not make sense" > expect.name-status
-echo "fatal: --check does not make sense" > expect.check
+echo "fatal: --name-only does not make sense" >expect.name-only
+echo "fatal: --name-status does not make sense" >expect.name-status
+echo "fatal: --check does not make sense" >expect.check
 
 test_expect_success 'options no longer allowed for format-patch' '
-	test_must_fail git format-patch --name-only 2> output &&
+	test_must_fail git format-patch --name-only 2>output &&
 	test_i18ncmp expect.name-only output &&
-	test_must_fail git format-patch --name-status 2> output &&
+	test_must_fail git format-patch --name-status 2>output &&
 	test_i18ncmp expect.name-status output &&
-	test_must_fail git format-patch --check 2> output &&
+	test_must_fail git format-patch --check 2>output &&
 	test_i18ncmp expect.check output'
 
 test_expect_success 'format-patch --numstat should produce a patch' '
-	git format-patch --numstat --stdout master..side > output &&
+	git format-patch --numstat --stdout master..side >output &&
 	test 5 = $(grep "^diff --git a/" output | wc -l)'
 
 test_expect_success 'format-patch -- <path>' '
@@ -763,20 +776,22 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' '
 	git format-patch --ignore-if-in-upstream HEAD
 '
 
-git_version="$(git --version | sed "s/.* //")"
+git_version="$(git --version >version && sed "s/.* //" version)"
 
 signature() {
 	printf "%s\n%s\n\n" "-- " "${1:-$git_version}"
 }
 
 test_expect_success 'format-patch default signature' '
-	git format-patch --stdout -1 | tail -n 3 >output &&
+	git format-patch --stdout -1 >patch &&
+	tail -n 3 patch >output &&
 	signature >expect &&
 	test_cmp expect output
 '
 
 test_expect_success 'format-patch --signature' '
-	git format-patch --stdout --signature="my sig" -1 | tail -n 3 >output &&
+	git format-patch --stdout --signature="my sig" -1 >patch &&
+	tail -n 3 patch >output &&
 	signature "my sig" >expect &&
 	test_cmp expect output
 '
@@ -1167,282 +1182,282 @@ append_signoff()
 
 test_expect_success 'signoff: commit with no body' '
 	append_signoff </dev/null >actual &&
-	cat <<\EOF | sed "s/EOL$//" >expected &&
-4:Subject: [PATCH] EOL
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject' '
 	echo subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject that does not end with NL' '
 	printf subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs and no trailing NL' '
 	printf "subject\n\nbody" | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: misc conforming footer elements' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
-Tested-by: Some One <someone@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-15:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <someone@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	15:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff-alike' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-Fooled-by-me: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-I want to mention about Signed-off-by: here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:I want to mention about Signed-off-by: here.
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	I want to mention about Signed-off-by: here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:I want to mention about Signed-off-by: here.
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff (2)' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-My unfortunate
-Signed-off-by: example happens to be wrapped here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:Signed-off-by: example happens to be wrapped here.
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	My unfortunate
+	Signed-off-by: example happens to be wrapped here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:Signed-off-by: example happens to be wrapped here.
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Signed-off-by: my@house
-Signed-off-by: your@house
+	Signed-off-by: my@house
+	Signed-off-by: your@house
 
-A lot of houses.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: my@house
-10:Signed-off-by: your@house
-11:
-13:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	A lot of houses.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: my@house
+	10:Signed-off-by: your@house
+	11:
+	13:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
 	printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff NOT at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	12:Signed-off-by: my@house
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: tolerate garbage in conforming footer' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Tested-by: my@house
-Some Trash
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: respect trailer config' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual &&
 
 	test_config trailer.Myfooter.ifexists add &&
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Reviewed-id: Noone
-Tested-by: my@house
-Change-id: Ideadbeef
-Signed-off-by: C O Mitter <committer@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <committer@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
@@ -1458,42 +1473,42 @@ test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (2)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter rebuild-1~2..rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (3)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter ^master rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (4)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master.. >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (5)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 HEAD >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (6)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter with nothing' '
@@ -1547,7 +1562,8 @@ test_expect_success 'format-patch format.outputDirectory option' '
 	test_config format.outputDirectory patches &&
 	rm -fr patches &&
 	git format-patch master..side &&
-	test $(git rev-list master..side | wc -l) -eq $(ls patches | wc -l)
+	git rev-list master..side >list &&
+	test_line_count = $(ls patches | wc -l) list
 '
 
 test_expect_success 'format-patch -o overrides format.outputDirectory' '
@@ -1560,13 +1576,21 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
 
 test_expect_success 'format-patch --base' '
 	git checkout side &&
-	git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual1 &&
-	git format-patch --stdout --base=HEAD~3 HEAD~.. | tail -n 7 >actual2 &&
+	git format-patch --stdout --base=HEAD~3 -1 >patch &&
+	tail -n 7 patch >actual1 &&
+	git format-patch --stdout --base=HEAD~3 HEAD~.. >patch &&
+	tail -n 7 patch >actual2 &&
 	echo >expected &&
 	echo "base-commit: $(git rev-parse HEAD~3)" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	signature >> expected &&
+	git show --patch HEAD~2 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	git show --patch HEAD~1 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	signature >>expected &&
 	test_cmp expected actual1 &&
 	test_cmp expected actual2
 '
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v3 2/8] Doc: add more detail for git-format-patch
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
  2019-05-22  2:44     ` [PATCH v3 1/8] t4014: clean up style Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 3/8] format-patch: infer cover letter from branch description Denton Liu
                       ` (8 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In git-format-patch.txt, we were missing some key user information.
First of all, using the `--to` and `--cc` options don't override
`format.to` and `format.cc` variables, respectively. They add on to each
other. Document this.

In addition, document the special value of `--base=auto`.

Next, while we're at it, surround option arguments with <>.

Finally, document the `format.outputDirectory` config and change
`format.coverletter` to use camelcase.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/git-format-patch.txt | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..4e180bbfbb 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -17,9 +17,9 @@ SYNOPSIS
 		   [--signature-file=<file>]
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
-		   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
-		   [--rfc] [--subject-prefix=Subject-Prefix]
+		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
 		   [--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
@@ -158,7 +158,7 @@ Beware that the default for 'git send-email' is to thread emails
 itself.  If you want `git format-patch` to take care of threading, you
 will want to ensure that threading is disabled for `git send-email`.
 
---in-reply-to=Message-Id::
+--in-reply-to=<Message-Id>::
 	Make the first mail (or all the mails with `--no-thread`) appear as a
 	reply to the given Message-Id, which avoids breaking threads to
 	provide a new patch series.
@@ -192,13 +192,17 @@ will want to ensure that threading is disabled for `git send-email`.
 
 --to=<email>::
 	Add a `To:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
 --cc=<email>::
 	Add a `Cc:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -309,7 +313,8 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 --base=<commit>::
 	Record the base tree information to identify the state the
 	patch series applies to.  See the BASE TREE INFORMATION section
-	below for details.
+	below for details. If <commit> is equal to "auto", a base commit
+	is automatically chosen.
 
 --root::
 	Treat the revision argument as a <revision range>, even if it
@@ -325,8 +330,9 @@ CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
 defaults for the subject prefix and file suffix, number patches when
-outputting more than one patch, add "To" or "Cc:" headers, configure
-attachments, and sign off patches with configuration variables.
+outputting more than one patch, add "To:" or "Cc:" headers, configure
+attachments, change the patch output directory, and sign off patches
+with configuration variables.
 
 ------------
 [format]
@@ -338,7 +344,8 @@ attachments, and sign off patches with configuration variables.
 	cc = <email>
 	attach [ = mime-boundary-string ]
 	signOff = true
-	coverletter = auto
+	outputDirectory = <directory>
+	coverLetter = auto
 ------------
 
 
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v3 3/8] format-patch: infer cover letter from branch description
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
  2019-05-22  2:44     ` [PATCH v3 1/8] t4014: clean up style Denton Liu
  2019-05-22  2:44     ` [PATCH v3 2/8] Doc: add more detail for git-format-patch Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 4/8] format-patch: move extra_headers logic later Denton Liu
                       ` (7 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

We used to populate the subject of the cover letter generated by
git-format-patch with "*** SUBJECT HERE ***". However, if a user submits
multiple patchsets, they may want to keep a consistent subject between
rerolls.

If git-format-patch is run with `--infer-cover-letter` or
`format.inferCoverSubject`, infer the subject for the cover letter from
the top line(s) of a branch description, similar to how a subject is
read from a commit message.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |  5 +++
 Documentation/git-format-patch.txt | 10 ++++++
 builtin/log.c                      | 58 ++++++++++++++++++------------
 t/t4014-format-patch.sh            | 33 +++++++++++++++++
 4 files changed, 84 insertions(+), 22 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index dc77941c48..c8b28fe47f 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -36,6 +36,11 @@ format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
 	subject prefix. Use this variable to change that prefix.
 
+format.<branch-name>.inferCoverSubject::
+	A boolean that controls whether or not to infer the subject for
+	the cover letter based on the branch's description. See the
+	--infer-cover-subject option in linkgit:git-format-patch[1].
+
 format.signature::
 	The default for format-patch is to output a signature containing
 	the Git version number. Use this variable to change that default.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 4e180bbfbb..f54aecf4bf 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -19,6 +19,7 @@ SYNOPSIS
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
+		   [--[no-]infer-cover-subject]
 		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
@@ -170,6 +171,14 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--[no-]infer-cover-subject::
+	Instead of using the default "*** SUBJECT HERE ***" subject for
+	the cover letter, infer the subject from the branch's
+	description.
++
+Similar to a commit message, the subject is inferred as the beginning of
+the description up to and excluding the first blank line.
+
 --subject-prefix=<Subject-Prefix>::
 	Instead of the standard '[PATCH]' prefix in the subject
 	line, instead use '[<Subject-Prefix>]'. This
@@ -346,6 +355,7 @@ with configuration variables.
 	signOff = true
 	outputDirectory = <directory>
 	coverLetter = auto
+	inferCoverSubject = true
 ------------
 
 
diff --git a/builtin/log.c b/builtin/log.c
index e43ee12fb1..617f074d60 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -769,6 +769,7 @@ static const char *signature = git_version_string;
 static const char *signature_file;
 static int config_cover_letter;
 static const char *config_output_directory;
+static int infer_cover_subject;
 
 enum {
 	COVER_UNSET,
@@ -864,6 +865,10 @@ static int git_format_config(const char *var, const char *value, void *cb)
 			from = NULL;
 		return 0;
 	}
+	if (!strcmp(var, "format.infercoversubject")) {
+		infer_cover_subject = git_config_bool(var, value);
+		return 0;
+	}
 
 	return git_log_config(var, value, cb);
 }
@@ -970,20 +975,6 @@ static void print_signature(FILE *file)
 	putc('\n', file);
 }
 
-static void add_branch_description(struct strbuf *buf, const char *branch_name)
-{
-	struct strbuf desc = STRBUF_INIT;
-	if (!branch_name || !*branch_name)
-		return;
-	read_branch_desc(&desc, branch_name);
-	if (desc.len) {
-		strbuf_addch(buf, '\n');
-		strbuf_addbuf(buf, &desc);
-		strbuf_addch(buf, '\n');
-	}
-	strbuf_release(&desc);
-}
-
 static char *find_branch_name(struct rev_info *rev)
 {
 	int i, positive = -1;
@@ -1034,13 +1025,17 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
 			      const char *branch_name,
+			      int infer_subject,
 			      int quiet)
 {
 	const char *committer;
-	const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-	const char *msg;
+	const char *subject = "*** SUBJECT HERE ***";
+	const char *body = "*** BLURB HERE ***";
+	const char *description = NULL;
 	struct shortlog log;
 	struct strbuf sb = STRBUF_INIT;
+	struct strbuf description_sb = STRBUF_INIT;
+	struct strbuf subject_sb = STRBUF_INIT;
 	int i;
 	const char *encoding = "UTF-8";
 	int need_8bit_cte = 0;
@@ -1068,17 +1063,34 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 	if (!branch_name)
 		branch_name = find_branch_name(rev);
 
-	msg = body;
+	if (branch_name && *branch_name)
+		read_branch_desc(&description_sb, branch_name);
+
+	if (description_sb.len) {
+		if (infer_subject) {
+			description = format_subject(&subject_sb, description_sb.buf, " ");
+			subject = subject_sb.buf;
+		} else {
+			description = description_sb.buf;
+		}
+	}
+
 	pp.fmt = CMIT_FMT_EMAIL;
 	pp.date_mode.type = DATE_RFC2822;
 	pp.rev = rev;
 	pp.print_email_subject = 1;
 	pp_user_info(&pp, NULL, &sb, committer, encoding);
-	pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
-	pp_remainder(&pp, &msg, &sb, 0);
-	add_branch_description(&sb, branch_name);
+	pp_title_line(&pp, &subject, &sb, encoding, need_8bit_cte);
+	pp_remainder(&pp, &body, &sb, 0);
+	if (description) {
+		strbuf_addch(&sb, '\n');
+		strbuf_addstr(&sb, description);
+		strbuf_addch(&sb, '\n');
+	}
 	fprintf(rev->diffopt.file, "%s\n", sb.buf);
 
+	strbuf_release(&description_sb);
+	strbuf_release(&subject_sb);
 	strbuf_release(&sb);
 
 	shortlog_init(&log);
@@ -1554,6 +1566,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "rfc", &rev, NULL,
 			    N_("Use [RFC PATCH] instead of [PATCH]"),
 			    PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+		OPT_BOOL(0, "infer-cover-subject", &infer_cover_subject,
+			    N_("infer a cover letter subject from the branch description")),
 		{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
 			    N_("Use [<prefix>] instead of [PATCH]"),
 			    PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1617,8 +1631,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	extra_to.strdup_strings = 1;
 	extra_cc.strdup_strings = 1;
 	init_log_defaults();
-	git_config(git_format_config, NULL);
 	repo_init_revisions(the_repository, &rev, prefix);
+	git_config(git_format_config, NULL);
 	rev.commit_format = CMIT_FMT_EMAIL;
 	rev.expand_tabs_in_log_default = 0;
 	rev.verbose_header = 1;
@@ -1893,7 +1907,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		if (thread)
 			gen_message_id(&rev, "cover");
 		make_cover_letter(&rev, use_stdout,
-				  origin, nr, list, branch_name, quiet);
+				  origin, nr, list, branch_name, infer_cover_subject, quiet);
 		print_bases(&bases, rev.diffopt.file);
 		print_signature(rev.diffopt.file);
 		total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 3423f974bc..4cb6b9edfa 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1469,6 +1469,39 @@ test_expect_success 'format patch ignores color.ui' '
 	test_cmp expect actual
 '
 
+test_expect_success 'cover letter with config subject' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.inferCoverSubject true &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	grep "^body" actual
+'
+
+test_expect_success 'cover letter with command-line subject' '
+	test_config branch.rebuild-1.description "command-line subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --infer-cover-subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] command-line subject$" actual &&
+	grep "^body" actual
+'
+
+test_expect_success 'cover letter with command-line --no-infer-cover-subject overrides config' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.inferCoverSubject true &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --no-infer-cover-subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	grep "^config subject" actual &&
+	grep "^body" actual
+'
+
 test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v3 4/8] format-patch: move extra_headers logic later
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (2 preceding siblings ...)
  2019-05-22  2:44     ` [PATCH v3 3/8] format-patch: infer cover letter from branch description Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 5/8] string-list: create string_list_append_all Denton Liu
                       ` (6 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we need to perform the addition of To: and Cc:
to extra_headers after the branch_name logic. Simply transpose this
logic later in the function so that this happens. (This patch is best
viewed with `git diff --color-moved`.)

Note that this logic only depends on `git_config` and
`repo_init_revisions` and is depended on by the patch creation logic
which is directly below it so this move is effectively a no-op as no
dependencies being reordered.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/builtin/log.c b/builtin/log.c
index 617f074d60..3f97f344df 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1667,35 +1667,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		rev.subject_prefix = strbuf_detach(&sprefix, NULL);
 	}
 
-	for (i = 0; i < extra_hdr.nr; i++) {
-		strbuf_addstr(&buf, extra_hdr.items[i].string);
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_to.nr)
-		strbuf_addstr(&buf, "To: ");
-	for (i = 0; i < extra_to.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_to.items[i].string);
-		if (i + 1 < extra_to.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_cc.nr)
-		strbuf_addstr(&buf, "Cc: ");
-	for (i = 0; i < extra_cc.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_cc.items[i].string);
-		if (i + 1 < extra_cc.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	rev.extra_headers = strbuf_detach(&buf, NULL);
-
 	if (from) {
 		if (split_ident_line(&rev.from_ident, from, strlen(from)))
 			die(_("invalid ident line: %s"), from);
@@ -1798,6 +1769,35 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	for (i = 0; i < extra_hdr.nr; i++) {
+		strbuf_addstr(&buf, extra_hdr.items[i].string);
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_to.nr)
+		strbuf_addstr(&buf, "To: ");
+	for (i = 0; i < extra_to.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_to.items[i].string);
+		if (i + 1 < extra_to.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_cc.nr)
+		strbuf_addstr(&buf, "Cc: ");
+	for (i = 0; i < extra_cc.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_cc.items[i].string);
+		if (i + 1 < extra_cc.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	rev.extra_headers = strbuf_detach(&buf, NULL);
+
 	/*
 	 * We cannot move this anywhere earlier because we do want to
 	 * know if --root was given explicitly from the command line.
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v3 5/8] string-list: create string_list_append_all
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (3 preceding siblings ...)
  2019-05-22  2:44     ` [PATCH v3 4/8] format-patch: move extra_headers logic later Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers Denton Liu
                       ` (5 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we'll need to take one string_list and append it to
the end of another. Create the `string_list_append_all` function which
does this.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)

diff --git a/string-list.c b/string-list.c
index a917955fbd..e63d58fbd2 100644
--- a/string-list.c
+++ b/string-list.c
@@ -215,6 +215,15 @@ struct string_list_item *string_list_append(struct string_list *list,
 			list->strdup_strings ? xstrdup(string) : (char *)string);
 }
 
+void string_list_append_all(struct string_list *list,
+			    const struct string_list *append_list)
+{
+	struct string_list_item *item;
+	ALLOC_GROW(list->items, list->nr + append_list->nr, list->alloc);
+	for_each_string_list_item(item, append_list)
+		string_list_append(list, item->string);
+}
+
 /*
  * Encapsulate the compare function pointer because ISO C99 forbids
  * casting from void * to a function pointer and vice versa.
diff --git a/string-list.h b/string-list.h
index f964399949..8227e00e6a 100644
--- a/string-list.h
+++ b/string-list.h
@@ -208,6 +208,13 @@ struct string_list_item *string_list_append(struct string_list *list, const char
  */
 struct string_list_item *string_list_append_nodup(struct string_list *list, char *string);
 
+/**
+ * Add all strings in append_list to list.  If list->strdup_string is
+ * set, then each string is copied; otherwise the new string_list_entry
+ * refers to the entry in the append_list.
+ */
+void string_list_append_all(struct string_list *list, const struct string_list *append_list);
+
 /**
  * Sort the list's entries by string value in `strcmp()` order.
  */
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (4 preceding siblings ...)
  2019-05-22  2:44     ` [PATCH v3 5/8] string-list: create string_list_append_all Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 7/8] format-patch: move output_directory logic later Denton Liu
                       ` (4 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

If a user wishes to keep track of whom to Cc: on individual patchsets,
they must manually keep track of each recipient and fill it in with the
`--cc` option on git-format-patch each time. However, on the Git mailing
list, Cc:'s are typically never dropped. As a result, it would be nice
to have a method to keep track of recipients on a per-branch basis.

Currently, git-format-patch gets its To: headers from the `--to` options
and the `format.to` config variable. The Cc: header is derived
similarly.

In addition to the above, read To: and Cc: headers from
`format.<branch-name>.to` and `format.<branch-name>.cc` so that users
can have branch-specific configuration options.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |   7 +-
 Documentation/git-format-patch.txt |  12 +++-
 builtin/log.c                      |  63 +++++++++++++++--
 t/t4014-format-patch.sh            | 108 +++++++++++++++++++++++------
 4 files changed, 162 insertions(+), 28 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index c8b28fe47f..95e255347a 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -28,9 +28,12 @@ format.headers::
 
 format.to::
 format.cc::
+format.<branch-name>.to::
+format.<branch-name>.cc::
 	Additional recipients to include in a patch to be submitted
-	by mail.  See the --to and --cc options in
-	linkgit:git-format-patch[1].
+	by mail.  For the <branch-name> options, the recipients are only
+	included if patches are generated for the given <branch-name>.
+	See the --to and --cc options in linkgit:git-format-patch[1].
 
 format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index f54aecf4bf..76e61b746a 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -203,7 +203,7 @@ the description up to and excluding the first blank line.
 	Add a `To:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.to` configurations.
+	`format.to` and `format.<branch-name>.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
@@ -211,7 +211,7 @@ the description up to and excluding the first blank line.
 	Add a `Cc:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.cc` configurations.
+	`format.cc` and `format.<branch-name>.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -358,6 +358,14 @@ with configuration variables.
 	inferCoverSubject = true
 ------------
 
+In addition, for a specific branch, you can specify a custom cover
+letter subject, and add additional "To:" or "Cc:" headers.
+
+------------
+[format "branch-name"]
+	to = <email>
+	cc = <email>
+------------
 
 DISCUSSION
 ----------
diff --git a/builtin/log.c b/builtin/log.c
index 3f97f344df..cb9ccdc59c 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -738,6 +738,8 @@ static char *default_attach = NULL;
 static struct string_list extra_hdr = STRING_LIST_INIT_NODUP;
 static struct string_list extra_to = STRING_LIST_INIT_NODUP;
 static struct string_list extra_cc = STRING_LIST_INIT_NODUP;
+int to_cleared;
+int cc_cleared;
 
 static void add_header(const char *value)
 {
@@ -1021,6 +1023,55 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static void add_branch_headers(struct rev_info *rev, const char *branch_name)
+{
+	struct strbuf buf = STRBUF_INIT;
+	const struct string_list *values;
+
+	if (!branch_name)
+		branch_name = find_branch_name(rev);
+
+	if (!branch_name || !*branch_name)
+		return;
+
+	/*
+	 * HACK: We only use branch-specific recipients iff the list has not
+	 * been cleared by an earlier --no-{to,cc} option on the command-line.
+	 *
+	 * When we get format.{to,cc} options, they can be cleared by
+	 * --no-{to,cc} options since the `git_config` call comes before the
+	 *  `parse_options` call.
+	 *
+	 *  However, in the case of branch.<name>.{to,cc}, this function needs
+	 *  to be called after `setup_revisions`, which must be called after
+	 *  `parse_options`. However, in order for the --no-{to,cc} logic to
+	 *  clear the extra_{to,cc} string_list, this function should actually
+	 *  be called _before_ `parse_options`. As a result, we have a circular
+	 *  dependency.
+	 *
+	 *  The {to,cc}_cleared flag lets us workaround this by just no
+	 *  including branch-specific recipients iff --no-{to,cc} has been
+	 *  specified on the command-line.
+	 */
+
+	if (!to_cleared) {
+		strbuf_addf(&buf, "format.%s.to", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_to, values);
+	}
+
+	if (!cc_cleared) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "format.%s.cc", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_cc, values);
+	}
+
+	strbuf_release(&buf);
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
@@ -1293,18 +1344,20 @@ static int header_callback(const struct option *opt, const char *arg, int unset)
 
 static int to_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		to_cleared = 1;
 		string_list_clear(&extra_to, 0);
-	else
+	} else
 		string_list_append(&extra_to, arg);
 	return 0;
 }
 
 static int cc_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		cc_cleared = 1;
 		string_list_clear(&extra_cc, 0);
-	else
+	} else
 		string_list_append(&extra_cc, arg);
 	return 0;
 }
@@ -1769,6 +1822,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	add_branch_headers(&rev, branch_name);
+
 	for (i = 0; i < extra_hdr.nr; i++) {
 		strbuf_addstr(&buf, extra_hdr.items[i].string);
 		strbuf_addch(&buf, '\n');
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 4cb6b9edfa..23c467e95b 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -234,6 +234,65 @@ test_expect_failure 'configuration To: header (rfc2047)' '
 	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
+test_expect_success 'branch-specific configuration To: header (ascii)' '
+
+	test_unconfig format.to &&
+	git config format.side.to "R E Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc822)' '
+
+	git config format.side.to "R. E. Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc2047)' '
+
+	git config format.side.to "R Ä Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_success 'all recipients included from all sources' '
+
+	git config format.to "Format To1 <formatto1@example.com>" &&
+	git config --add format.to "Format To2 <formatto2@example.com>" &&
+	git config format.cc "Format Cc1 <formatcc1@example.com>" &&
+	git config --add format.cc "Format Cc2 <formatcc2@example.com>" &&
+	git config format.side.to "Branch To1 <branchto1@example.com>" &&
+	git config --add format.side.to "Branch To2 <branchto2@example.com>" &&
+	git config format.side.cc "Branch Cc1 <branchcc1@example.com>" &&
+	git config --add format.side.cc "Branch Cc2 <branchcc2@example.com>" &&
+	cat <<-\EOF >expect &&
+	To: Format To1 <formatto1@example.com>,
+	    Format To2 <formatto2@example.com>,
+	    Command-line To1 <commandlineto1@example.com>,
+	    Command-line To2 <commandlineto2@example.com>,
+	    Branch To1 <branchto1@example.com>,
+	    Branch To2 <branchto2@example.com>
+	Cc: Format Cc1 <formatcc1@example.com>,
+	    Format Cc2 <formatcc2@example.com>,
+	    Command-line Cc1 <commandlinecc1@example.com>,
+	    Command-line Cc2 <commandlinecc2@example.com>,
+	    Branch Cc1 <branchcc1@example.com>,
+	    Branch Cc2 <branchcc2@example.com>
+
+	EOF
+	git format-patch --stdout \
+		--to="Command-line To1 <commandlineto1@example.com>" \
+		--to="Command-line To2 <commandlineto2@example.com>" \
+		--cc="Command-line Cc1 <commandlinecc1@example.com>" \
+		--cc="Command-line Cc2 <commandlinecc2@example.com>" \
+		master..side | sed -ne "/^To:/,/^$/p;/^$/q" >patch10 &&
+	test_cmp expect patch10
+'
+
 # check_patch <patch>: Verify that <patch> looks like a half-sane
 # patch email to avoid a false positive with !grep
 check_patch () {
@@ -286,42 +345,51 @@ test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side >patch10 &&
-	sed -e "/^\$/q" patch10 >hdrs10 &&
-	check_patch hdrs10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+	git config --replace-all format.side.to \
+		"B R Anch <branch@example.com>" &&
+	git format-patch --no-to --stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "R E Cipient <rcipient@example.com>" hdrs11 &&
+	! grep "B R Anch <branch@example.com>" hdrs11
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
 
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
+	git config --replace-all format.side.to \
+		"B R Anch2 <branch2@example.com>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side >patch11 &&
-	sed -e "/^\$/q" patch11 >hdrs11 &&
-	check_patch hdrs11 &&
-	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
-	grep "^To: Someone Else <else@out.there>\$" hdrs11
+		--stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "Someone <someone@out.there>" hdrs12 &&
+	! grep "B R Anch2 <branch2@example.com>" hdrs12 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs12
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side >patch12 &&
-	sed -e "/^\$/q" patch12 >hdrs12 &&
-	check_patch hdrs12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
+	git config --replace-all format.side.cc \
+		"B R Anch3 <branch3@example.com>" &&
+	git format-patch --no-cc --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "C E Cipient <rcipient@example.com>" hdrs13 &&
+	! grep "B R Anch3 <branch3@example.com>" hdrs13
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side >patch13 &&
-	sed -e "/^\$/q" patch13 >hdrs13 &&
-	check_patch hdrs13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
+	git format-patch --no-add-header --stdout master..side >patch14 &&
+	sed -e "/^\$/q" patch14 >hdrs14 &&
+	check_patch hdrs14 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs14
 '
 
 test_expect_success 'multiple files' '
@@ -963,7 +1031,7 @@ test_expect_success 'format-patch wraps extremely long subject (ascii)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1002,7 +1070,7 @@ test_expect_success 'format-patch wraps extremely long subject (rfc2047)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1011,7 +1079,7 @@ check_author() {
 	git add file &&
 	GIT_AUTHOR_NAME=$1 git commit -m author-check &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^From: /p; /^ /p; /^$/q" <patch >actual &&
+	sed -n "/^From: /p; /^ /p; /^Date:/q" <patch >actual &&
 	test_cmp expect actual
 }
 
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v3 7/8] format-patch: move output_directory logic later
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (5 preceding siblings ...)
  2019-05-22  2:44     ` [PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-22  2:44     ` [PATCH v3 8/8] format-patch: read branch-specific output directory Denton Liu
                       ` (3 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we need to create the output_directory after the
branch_name logic. Simply transpose this logic later in the function so
that this happens. (This patch is best viewed with `git diff
--color-moved`.)

Note that this logic only depends on `git_config` and the parseopt logic
and is depended on by the patch creation logic which is directly below
it so this move is effectively a no-op as no dependencies being
reordered.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/git-format-patch.txt |  4 ++--
 builtin/log.c                      | 36 +++++++++++++++---------------
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 76e61b746a..707b4bdc6b 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -358,8 +358,8 @@ with configuration variables.
 	inferCoverSubject = true
 ------------
 
-In addition, for a specific branch, you can specify a custom cover
-letter subject, and add additional "To:" or "Cc:" headers.
+In addition, for a specific branch, you can add additional "To:" or
+"Cc:" headers.
 
 ------------
 [format "branch-name"]
diff --git a/builtin/log.c b/builtin/log.c
index cb9ccdc59c..97980881ec 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1771,24 +1771,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	if (rev.show_notes)
 		init_display_notes(&rev.notes_opt);
 
-	if (!output_directory && !use_stdout)
-		output_directory = config_output_directory;
-
-	if (!use_stdout)
-		output_directory = set_outdir(prefix, output_directory);
-	else
-		setup_pager();
-
-	if (output_directory) {
-		if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
-			rev.diffopt.use_color = GIT_COLOR_NEVER;
-		if (use_stdout)
-			die(_("standard output, or directory, which one?"));
-		if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
-			die_errno(_("could not create directory '%s'"),
-				  output_directory);
-	}
-
 	if (rev.pending.nr == 1) {
 		int check_head = 0;
 
@@ -1822,6 +1804,24 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	if (!output_directory && !use_stdout)
+		output_directory = config_output_directory;
+
+	if (!use_stdout)
+		output_directory = set_outdir(prefix, output_directory);
+	else
+		setup_pager();
+
+	if (output_directory) {
+		if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
+			rev.diffopt.use_color = GIT_COLOR_NEVER;
+		if (use_stdout)
+			die(_("standard output, or directory, which one?"));
+		if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
+			die_errno(_("could not create directory '%s'"),
+				  output_directory);
+	}
+
 	add_branch_headers(&rev, branch_name);
 
 	for (i = 0; i < extra_hdr.nr; i++) {
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v3 8/8] format-patch: read branch-specific output directory
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (6 preceding siblings ...)
  2019-05-22  2:44     ` [PATCH v3 7/8] format-patch: move output_directory logic later Denton Liu
@ 2019-05-22  2:44     ` Denton Liu
  2019-05-31  0:30     ` [PATCH v3 0/8] teach branch-specific options for format-patch Denton Liu
                       ` (2 subsequent siblings)
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-22  2:44 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

If a user wishes to have a per-branch output directory for patches, they
must manually specify this on the command-line with `-o` for each
invocation of format-patch. However, this can be cumbersome for a user
to keep track of.

Read `format.<branch-name>.outputDirectory` to give a branch-specific
output directory that would override `format.outputDirectory`.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |  5 ++++-
 Documentation/git-format-patch.txt |  3 ++-
 builtin/log.c                      | 26 ++++++++++++++++++++++++--
 t/t4014-format-patch.sh            | 18 +++++++++++++++---
 4 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index 95e255347a..df67a83183 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -87,8 +87,11 @@ format.coverLetter::
 	generate a cover-letter only when there's more than one patch.
 
 format.outputDirectory::
+format.<branch-name>.outputDirectory::
 	Set a custom directory to store the resulting files instead of the
-	current working directory.
+	current working directory. If patches are being generated for
+	<branch-name>, the latter option takes priority if it exists,
+	otherwise we will fallback to the former.
 
 format.useAutoBase::
 	A boolean value which lets you enable the `--base=auto` option of
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 707b4bdc6b..346f1229d8 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -359,12 +359,13 @@ with configuration variables.
 ------------
 
 In addition, for a specific branch, you can add additional "To:" or
-"Cc:" headers.
+"Cc:" headers and change the patch output directory.
 
 ------------
 [format "branch-name"]
 	to = <email>
 	cc = <email>
+	outputDirectory = <directory>
 ------------
 
 DISCUSSION
diff --git a/builtin/log.c b/builtin/log.c
index 97980881ec..a1fe8b994a 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1023,6 +1023,24 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static const char *get_branch_output_dir(struct rev_info *rev, const char *branch_name)
+{
+	struct strbuf buf = STRBUF_INIT;
+	const char *output_directory;
+
+	if (!branch_name)
+		branch_name = find_branch_name(rev);
+
+	if (!branch_name || !*branch_name)
+		return NULL;
+
+	strbuf_addf(&buf, "format.%s.outputdirectory", branch_name);
+	git_config_get_string_const(buf.buf, &output_directory);
+	strbuf_release(&buf);
+
+	return output_directory;
+}
+
 static void add_branch_headers(struct rev_info *rev, const char *branch_name)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -1804,8 +1822,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (!output_directory && !use_stdout)
-		output_directory = config_output_directory;
+	if (!use_stdout) {
+		if (!output_directory)
+			output_directory = get_branch_output_dir(&rev, branch_name);
+		if (!output_directory)
+			output_directory = config_output_directory;
+	}
 
 	if (!use_stdout)
 		output_directory = set_outdir(prefix, output_directory);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 23c467e95b..147934922c 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1667,12 +1667,24 @@ test_expect_success 'format-patch format.outputDirectory option' '
 	test_line_count = $(ls patches | wc -l) list
 '
 
-test_expect_success 'format-patch -o overrides format.outputDirectory' '
+test_expect_success 'format-patch format.side.outputDirectory option overrides format.outputDirectory' '
 	test_config format.outputDirectory patches &&
-	rm -fr patches patchset &&
+	test_config format.side.outputDirectory sidepatches &&
+	rm -fr patches sidepatches &&
+	git format-patch master..side &&
+	git rev-list master..side >list &&
+	test_path_is_missing patches &&
+	test_line_count = $(ls sidepatches | wc -l) list
+'
+
+test_expect_success 'format-patch -o overrides format.outputDirectory and format.side.outputDirectory' '
+	test_config format.outputDirectory patches &&
+	test_config format.side.outputDirectory sidepatches &&
+	rm -fr patches sidepatches patchset &&
 	git format-patch master..side -o patchset &&
 	test_path_is_missing patches &&
-	test_path_is_dir patchset
+	test_path_is_missing sidepatches &&
+	test_line_count = $(ls patchset | wc -l) list
 '
 
 test_expect_success 'format-patch --base' '
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* Re: [PATCH v3 0/8] teach branch-specific options for format-patch
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (7 preceding siblings ...)
  2019-05-22  2:44     ` [PATCH v3 8/8] format-patch: read branch-specific output directory Denton Liu
@ 2019-05-31  0:30     ` Denton Liu
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
  2019-10-15  9:06     ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Denton Liu
  10 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-31  0:30 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

On Tue, May 21, 2019 at 10:44:23PM -0400, Denton Liu wrote:
> Hi Junio,
> 
> I've gotten rid of all the `format.*.coverSubject` stuff and replaced it
> with a generic `format.inferCoverSubject` that will read the subject
> from the branch description. I've also made `git branch -d` stop
> deleting the `format.<branch>.*` variables.
> 
> A new addition in this patchset is that format-patch is now taught to
> understand the `format.<branch-name>.outputDirectory` configuration.

[...]

Quick ping for this topic.

Thanks,

Denton

^ permalink raw reply	[flat|nested] 59+ messages in thread

* [RFC PATCH] config: learn the "onbranch:" includeIf condition
  2019-05-08  1:45           ` Junio C Hamano
@ 2019-05-31  2:00             ` Denton Liu
  2019-05-31 12:58               ` Johannes Schindelin
  2019-05-31 19:33               ` [PATCH v2] " Denton Liu
  0 siblings, 2 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-31  2:00 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

Currently, if a user wishes to have individual settings per branch, they
are required to manually keep track of the settings in their head and
manually set the options on the command-line or change the config at
each branch.

Teach config the "onbranch:" includeIf condition so that it can
conditionally include configuration files if the branch is checked out
in the current worktree.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---

I decided to go ahead and implement the includeIf onbranch semantics for
fun. For completeness, I'm sending it to the list but I'm not really
sure if this should get merged, since I don't really have a use-case for
this, especially if we go the branch-specific format-patch config route.

Another thing to note is that this change doesn't completely cover all
the use-cases that the branch-specific format-patch does. In particular,
if I run

	$ git checkout foo
	$ git format-patch master..bar

with the `format.bar.*`, we'd get bar-specific configs, whereas with
`includeIf "onbranch:bar"`, we'd fail to include bar-specific configs
and, more dangerously, we'd be including foo's configs.

But let's see how it goes.

 Documentation/config.txt  | 10 ++++++++++
 config.c                  | 16 ++++++++++++++++
 t/t1305-config-include.sh | 13 +++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 7e2a6f61f5..3b9fbe1860 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -144,6 +144,11 @@ refer to linkgit:gitignore[5] for details. For convenience:
 	This is the same as `gitdir` except that matching is done
 	case-insensitively (e.g. on case-insensitive file sytems)
 
+`onbranch`::
+	The data that follows the keyword `onbranch:` is taken to be a
+	name of a local branch. If we are in a worktree where that
+	branch is currently checked out, the include condition is met.
+
 A few more notes on matching via `gitdir` and `gitdir/i`:
 
  * Symlinks in `$GIT_DIR` are not resolved before matching.
@@ -206,6 +211,11 @@ Example
 	[includeIf "gitdir:/path/to/group/"]
 		path = foo.inc
 
+	; include only if we are in a worktree where foo-branch is
+	; currently checked out
+	[includeIf "onbranch:foo-branch"]
+		path = foo.inc
+
 Values
 ~~~~~~
 
diff --git a/config.c b/config.c
index 296a6d9cc4..954e84e13a 100644
--- a/config.c
+++ b/config.c
@@ -19,6 +19,7 @@
 #include "utf8.h"
 #include "dir.h"
 #include "color.h"
+#include "refs.h"
 
 struct config_source {
 	struct config_source *prev;
@@ -264,6 +265,19 @@ static int include_by_gitdir(const struct config_options *opts,
 	return ret;
 }
 
+static int include_by_branch(const char *cond, size_t cond_len)
+{
+	int flags;
+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+	const char *shortname;
+
+	if (!refname || !(flags & REF_ISSYMREF)	||
+			!skip_prefix(refname, "refs/heads/", &shortname))
+		return 0;
+
+	return !strncmp(cond, shortname, cond_len);
+}
+
 static int include_condition_is_true(const struct config_options *opts,
 				     const char *cond, size_t cond_len)
 {
@@ -272,6 +286,8 @@ static int include_condition_is_true(const struct config_options *opts,
 		return include_by_gitdir(opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
 		return include_by_gitdir(opts, cond, cond_len, 1);
+	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
+		return include_by_branch(cond, cond_len);
 
 	/* unknown conditionals are always false */
 	return 0;
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 579a86b7f8..062db08ad9 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -309,6 +309,19 @@ test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icas
 	)
 '
 
+test_expect_success 'conditional include, onbranch' '
+	(
+		cd bar &&
+		echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
+		echo "[test]nine=9" >.git/bar9 &&
+		test_must_fail git config test.nine &&
+		git checkout -b foo-branch &&
+		echo 9 >expect &&
+		git config test.nine >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_expect_success 'include cycles are detected' '
 	cat >.gitconfig <<-\EOF &&
 	[test]value = gitconfig
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition
  2019-05-31  2:00             ` [RFC PATCH] config: learn the "onbranch:" includeIf condition Denton Liu
@ 2019-05-31 12:58               ` Johannes Schindelin
  2019-05-31 13:16                 ` Denton Liu
  2019-05-31 19:33               ` [PATCH v2] " Denton Liu
  1 sibling, 1 reply; 59+ messages in thread
From: Johannes Schindelin @ 2019-05-31 12:58 UTC (permalink / raw)
  To: Denton Liu
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano

Hi,

On Thu, 30 May 2019, Denton Liu wrote:

> Currently, if a user wishes to have individual settings per branch, they
> are required to manually keep track of the settings in their head and
> manually set the options on the command-line or change the config at
> each branch.
>
> Teach config the "onbranch:" includeIf condition so that it can
> conditionally include configuration files if the branch is checked out
> in the current worktree.

What a coincidence. I actually wished for something like this, to have
branch-specific aliases.

However, I would need this to handle patterns (via `wildmatch()`?) rather
than branch names.

> I decided to go ahead and implement the includeIf onbranch semantics for
> fun. For completeness, I'm sending it to the list but I'm not really
> sure if this should get merged, since I don't really have a use-case for
> this, especially if we go the branch-specific format-patch config route.
>
> Another thing to note is that this change doesn't completely cover all
> the use-cases that the branch-specific format-patch does. In particular,
> if I run
>
> 	$ git checkout foo
> 	$ git format-patch master..bar
>
> with the `format.bar.*`, we'd get bar-specific configs, whereas with
> `includeIf "onbranch:bar"`, we'd fail to include bar-specific configs
> and, more dangerously, we'd be including foo's configs.

I actually think that this is fine. "on branch" means that you are on the
specified branch, not that you merely mention the branch name on the
command-line (in which case there would be the ambiguity "did the user
mean `master` or `bar`?").

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition
  2019-05-31 12:58               ` Johannes Schindelin
@ 2019-05-31 13:16                 ` Denton Liu
  2019-05-31 17:23                   ` Johannes Schindelin
  0 siblings, 1 reply; 59+ messages in thread
From: Denton Liu @ 2019-05-31 13:16 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano

Hi Johannes,

On Fri, May 31, 2019 at 02:58:30PM +0200, Johannes Schindelin wrote:
> Hi,
> 
> On Thu, 30 May 2019, Denton Liu wrote:
> 
> > Currently, if a user wishes to have individual settings per branch, they
> > are required to manually keep track of the settings in their head and
> > manually set the options on the command-line or change the config at
> > each branch.
> >
> > Teach config the "onbranch:" includeIf condition so that it can
> > conditionally include configuration files if the branch is checked out
> > in the current worktree.
> 
> What a coincidence. I actually wished for something like this, to have
> branch-specific aliases.
> 
> However, I would need this to handle patterns (via `wildmatch()`?) rather
> than branch names.

Do you mean that we should be able to match a branch by pattern? So, for
example, if we had

	[includeIf "onbranch:mas*"]

we'd match if we were on "master"?

> 
> > I decided to go ahead and implement the includeIf onbranch semantics for
> > fun. For completeness, I'm sending it to the list but I'm not really
> > sure if this should get merged, since I don't really have a use-case for
> > this, especially if we go the branch-specific format-patch config route.
> >
> > Another thing to note is that this change doesn't completely cover all
> > the use-cases that the branch-specific format-patch does. In particular,
> > if I run
> >
> > 	$ git checkout foo
> > 	$ git format-patch master..bar
> >
> > with the `format.bar.*`, we'd get bar-specific configs, whereas with
> > `includeIf "onbranch:bar"`, we'd fail to include bar-specific configs
> > and, more dangerously, we'd be including foo's configs.
> 
> I actually think that this is fine. "on branch" means that you are on the
> specified branch, not that you merely mention the branch name on the
> command-line (in which case there would be the ambiguity "did the user
> mean `master` or `bar`?").

The reason why I brought this up as a use case was because currently,
when format-patch generates a cover letter, with the above, it'll use
bar's branch description to populate it even if "foo" is checked out. As
a result, when implementing the branch-specific format-patch stuff, I
wanted to make this consistent so that we wouldn't end up in a situation
where the cover letter has the branch's description but is missing its
Cc's.

> 
> Ciao,
> Dscho

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition
  2019-05-31 13:16                 ` Denton Liu
@ 2019-05-31 17:23                   ` Johannes Schindelin
  2019-05-31 18:44                     ` Denton Liu
  0 siblings, 1 reply; 59+ messages in thread
From: Johannes Schindelin @ 2019-05-31 17:23 UTC (permalink / raw)
  To: Denton Liu
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano

Hi Denton,

On Fri, 31 May 2019, Denton Liu wrote:

> On Fri, May 31, 2019 at 02:58:30PM +0200, Johannes Schindelin wrote:
>
> > On Thu, 30 May 2019, Denton Liu wrote:
> >
> > > Currently, if a user wishes to have individual settings per branch,
> > > they are required to manually keep track of the settings in their
> > > head and manually set the options on the command-line or change the
> > > config at each branch.
> > >
> > > Teach config the "onbranch:" includeIf condition so that it can
> > > conditionally include configuration files if the branch is checked
> > > out in the current worktree.
> >
> > What a coincidence. I actually wished for something like this, to have
> > branch-specific aliases.
> >
> > However, I would need this to handle patterns (via `wildmatch()`?)
> > rather than branch names.
>
> Do you mean that we should be able to match a branch by pattern? So, for
> example, if we had
>
> 	[includeIf "onbranch:mas*"]
>
> we'd match if we were on "master"?

Yes, precisely.

I would use this for the "ever-green" branches of Git for Windows, i.e.
the branches that are continuously rebasing Git for Windows' branch
thicket on top of maint, master, next & pu. Those are called
`shears/maint`, `shears/master`, etc (for historical reasons) in
https://github.com/git-for-windows/git.

> > > I decided to go ahead and implement the includeIf onbranch semantics
> > > for fun. For completeness, I'm sending it to the list but I'm not
> > > really sure if this should get merged, since I don't really have a
> > > use-case for this, especially if we go the branch-specific
> > > format-patch config route.
> > >
> > > Another thing to note is that this change doesn't completely cover
> > > all the use-cases that the branch-specific format-patch does. In
> > > particular, if I run
> > >
> > > 	$ git checkout foo
> > > 	$ git format-patch master..bar
> > >
> > > with the `format.bar.*`, we'd get bar-specific configs, whereas with
> > > `includeIf "onbranch:bar"`, we'd fail to include bar-specific configs
> > > and, more dangerously, we'd be including foo's configs.
> >
> > I actually think that this is fine. "on branch" means that you are on the
> > specified branch, not that you merely mention the branch name on the
> > command-line (in which case there would be the ambiguity "did the user
> > mean `master` or `bar`?").
>
> The reason why I brought this up as a use case was because currently,
> when format-patch generates a cover letter, with the above, it'll use
> bar's branch description to populate it even if "foo" is checked out. As
> a result, when implementing the branch-specific format-patch stuff, I
> wanted to make this consistent so that we wouldn't end up in a situation
> where the cover letter has the branch's description but is missing its
> Cc's.

That strikes me as a different use case than `includeIf`. I could imagine
that you'd want a setting like `formatpatch.detecttargetbranch = auto` or
some such that would pick up the `format.bar*` settings if there was *one*
rev argument, and it was a commit range (or a tip commit), *and* it
obviously referred to a single target branch.

It's just a scenario that is *very* specific to `git format-patch`.

For example, I would not, ever, want `git log ..next` to pick up a
config specific to `next` just because I mentioned a commit range with
`range` as the tip to start from.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition
  2019-05-31 17:23                   ` Johannes Schindelin
@ 2019-05-31 18:44                     ` Denton Liu
  0 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-31 18:44 UTC (permalink / raw)
  To: Johannes Schindelin
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano

On Fri, May 31, 2019 at 07:23:56PM +0200, Johannes Schindelin wrote:
> Hi Denton,
> 
> On Fri, 31 May 2019, Denton Liu wrote:
> 
> > On Fri, May 31, 2019 at 02:58:30PM +0200, Johannes Schindelin wrote:
> >
> > > On Thu, 30 May 2019, Denton Liu wrote:
> > >

[...]

> 
> > > > I decided to go ahead and implement the includeIf onbranch semantics
> > > > for fun. For completeness, I'm sending it to the list but I'm not
> > > > really sure if this should get merged, since I don't really have a
> > > > use-case for this, especially if we go the branch-specific
> > > > format-patch config route.
> > > >
> > > > Another thing to note is that this change doesn't completely cover
> > > > all the use-cases that the branch-specific format-patch does. In
> > > > particular, if I run
> > > >
> > > > 	$ git checkout foo
> > > > 	$ git format-patch master..bar
> > > >
> > > > with the `format.bar.*`, we'd get bar-specific configs, whereas with
> > > > `includeIf "onbranch:bar"`, we'd fail to include bar-specific configs
> > > > and, more dangerously, we'd be including foo's configs.
> > >
> > > I actually think that this is fine. "on branch" means that you are on the
> > > specified branch, not that you merely mention the branch name on the
> > > command-line (in which case there would be the ambiguity "did the user
> > > mean `master` or `bar`?").
> >
> > The reason why I brought this up as a use case was because currently,
> > when format-patch generates a cover letter, with the above, it'll use
> > bar's branch description to populate it even if "foo" is checked out. As
> > a result, when implementing the branch-specific format-patch stuff, I
> > wanted to make this consistent so that we wouldn't end up in a situation
> > where the cover letter has the branch's description but is missing its
> > Cc's.
> 
> That strikes me as a different use case than `includeIf`. I could imagine
> that you'd want a setting like `formatpatch.detecttargetbranch = auto` or
> some such that would pick up the `format.bar*` settings if there was *one*
> rev argument, and it was a commit range (or a tip commit), *and* it
> obviously referred to a single target branch.

Correct. For context, upthread I initially implemented the
branch-specific format-patch configs but Ævar suggested that we
implement the onbranch config semantics instead.

So I was just addressing the fact that this patch can't supercede the
branch-specific format-patch stuff since they have different use cases
so the other patchset has to coexist with this one. I'm happy to see
that we're both in agreement about this.

> 
> It's just a scenario that is *very* specific to `git format-patch`.
> 
> For example, I would not, ever, want `git log ..next` to pick up a
> config specific to `next` just because I mentioned a commit range with
> `range` as the tip to start from.

Yeah, I'd never dream of implementing something that gross ;)

> 
> Ciao,
> Dscho

^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH v2] config: learn the "onbranch:" includeIf condition
  2019-05-31  2:00             ` [RFC PATCH] config: learn the "onbranch:" includeIf condition Denton Liu
  2019-05-31 12:58               ` Johannes Schindelin
@ 2019-05-31 19:33               ` Denton Liu
  2019-05-31 20:14                 ` Johannes Schindelin
                                   ` (2 more replies)
  1 sibling, 3 replies; 59+ messages in thread
From: Denton Liu @ 2019-05-31 19:33 UTC (permalink / raw)
  To: Git Mailing List
  Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano,
	Johannes Schindelin

Currently, if a user wishes to have individual settings per branch, they
are required to manually keep track of the settings in their head and
manually set the options on the command-line or change the config at
each branch.

Teach config the "onbranch:" includeIf condition so that it can
conditionally include configuration files if the branch that is checked
out in the current worktree matches the pattern given.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---

Thanks for taking a look, Johannes. I've modified the patch to match
with wildcards now.

Changes since v1:

* Allow onbranch: to specify a pattern instead of just a static
  comparison

Interdiff against v1:
  diff --git a/Documentation/config.txt b/Documentation/config.txt
  index 3b9fbe1860..93aa4323c6 100644
  --- a/Documentation/config.txt
  +++ b/Documentation/config.txt
  @@ -145,9 +145,11 @@ refer to linkgit:gitignore[5] for details. For convenience:
   	case-insensitively (e.g. on case-insensitive file sytems)
   
   `onbranch`::
  -	The data that follows the keyword `onbranch:` is taken to be a
  -	name of a local branch. If we are in a worktree where that
  -	branch is currently checked out, the include condition is met.
  +	The data that follows the keyword `onbranch:` is taken to be a pattern
  +	with standard globbing wildcards and two additional ones, `**/` and
  +	`/**`, that can match multiple path components. If we are in a worktree
  +	where the name of the branch that is currently checked out matches the
  +	pattern, the include condition is met.
   
   A few more notes on matching via `gitdir` and `gitdir/i`:
   
  diff --git a/config.c b/config.c
  index 954e84e13a..48bb435739 100644
  --- a/config.c
  +++ b/config.c
  @@ -268,6 +268,8 @@ static int include_by_gitdir(const struct config_options *opts,
   static int include_by_branch(const char *cond, size_t cond_len)
   {
   	int flags;
  +	int ret;
  +	struct strbuf pattern = STRBUF_INIT;
   	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
   	const char *shortname;
   
  @@ -275,7 +277,10 @@ static int include_by_branch(const char *cond, size_t cond_len)
   			!skip_prefix(refname, "refs/heads/", &shortname))
   		return 0;
   
  -	return !strncmp(cond, shortname, cond_len);
  +	strbuf_add(&pattern, cond, cond_len);
  +	ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
  +	strbuf_release(&pattern);
  +	return ret;
   }
   
   static int include_condition_is_true(const struct config_options *opts,
  diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
  index 062db08ad9..05c7def1f2 100755
  --- a/t/t1305-config-include.sh
  +++ b/t/t1305-config-include.sh
  @@ -314,6 +314,7 @@ test_expect_success 'conditional include, onbranch' '
   		cd bar &&
   		echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
   		echo "[test]nine=9" >.git/bar9 &&
  +		git checkout -b master &&
   		test_must_fail git config test.nine &&
   		git checkout -b foo-branch &&
   		echo 9 >expect &&
  @@ -322,6 +323,25 @@ test_expect_success 'conditional include, onbranch' '
   	)
   '
   
  +test_expect_success 'conditional include, onbranch, wildcard' '
  +	(
  +		cd bar &&
  +		echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
  +		echo "[test]ten=10" >.git/bar10 &&
  +		git checkout -b not-foo-branch/a &&
  +		test_must_fail git config test.ten &&
  +
  +		echo 10 >expect &&
  +		git checkout -b foo-branch/a/b/c &&
  +		git config test.ten >actual &&
  +		test_cmp expect actual &&
  +
  +		git checkout -b moo-bar/a &&
  +		git config test.ten >actual &&
  +		test_cmp expect actual
  +	)
  +'
  +
   test_expect_success 'include cycles are detected' '
   	cat >.gitconfig <<-\EOF &&
   	[test]value = gitconfig

 Documentation/config.txt  | 12 ++++++++++++
 config.c                  | 21 +++++++++++++++++++++
 t/t1305-config-include.sh | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 66 insertions(+)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 7e2a6f61f5..93aa4323c6 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -144,6 +144,13 @@ refer to linkgit:gitignore[5] for details. For convenience:
 	This is the same as `gitdir` except that matching is done
 	case-insensitively (e.g. on case-insensitive file sytems)
 
+`onbranch`::
+	The data that follows the keyword `onbranch:` is taken to be a pattern
+	with standard globbing wildcards and two additional ones, `**/` and
+	`/**`, that can match multiple path components. If we are in a worktree
+	where the name of the branch that is currently checked out matches the
+	pattern, the include condition is met.
+
 A few more notes on matching via `gitdir` and `gitdir/i`:
 
  * Symlinks in `$GIT_DIR` are not resolved before matching.
@@ -206,6 +213,11 @@ Example
 	[includeIf "gitdir:/path/to/group/"]
 		path = foo.inc
 
+	; include only if we are in a worktree where foo-branch is
+	; currently checked out
+	[includeIf "onbranch:foo-branch"]
+		path = foo.inc
+
 Values
 ~~~~~~
 
diff --git a/config.c b/config.c
index 296a6d9cc4..48bb435739 100644
--- a/config.c
+++ b/config.c
@@ -19,6 +19,7 @@
 #include "utf8.h"
 #include "dir.h"
 #include "color.h"
+#include "refs.h"
 
 struct config_source {
 	struct config_source *prev;
@@ -264,6 +265,24 @@ static int include_by_gitdir(const struct config_options *opts,
 	return ret;
 }
 
+static int include_by_branch(const char *cond, size_t cond_len)
+{
+	int flags;
+	int ret;
+	struct strbuf pattern = STRBUF_INIT;
+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+	const char *shortname;
+
+	if (!refname || !(flags & REF_ISSYMREF)	||
+			!skip_prefix(refname, "refs/heads/", &shortname))
+		return 0;
+
+	strbuf_add(&pattern, cond, cond_len);
+	ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
+	strbuf_release(&pattern);
+	return ret;
+}
+
 static int include_condition_is_true(const struct config_options *opts,
 				     const char *cond, size_t cond_len)
 {
@@ -272,6 +291,8 @@ static int include_condition_is_true(const struct config_options *opts,
 		return include_by_gitdir(opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
 		return include_by_gitdir(opts, cond, cond_len, 1);
+	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
+		return include_by_branch(cond, cond_len);
 
 	/* unknown conditionals are always false */
 	return 0;
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 579a86b7f8..05c7def1f2 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -309,6 +309,39 @@ test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icas
 	)
 '
 
+test_expect_success 'conditional include, onbranch' '
+	(
+		cd bar &&
+		echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
+		echo "[test]nine=9" >.git/bar9 &&
+		git checkout -b master &&
+		test_must_fail git config test.nine &&
+		git checkout -b foo-branch &&
+		echo 9 >expect &&
+		git config test.nine >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'conditional include, onbranch, wildcard' '
+	(
+		cd bar &&
+		echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
+		echo "[test]ten=10" >.git/bar10 &&
+		git checkout -b not-foo-branch/a &&
+		test_must_fail git config test.ten &&
+
+		echo 10 >expect &&
+		git checkout -b foo-branch/a/b/c &&
+		git config test.ten >actual &&
+		test_cmp expect actual &&
+
+		git checkout -b moo-bar/a &&
+		git config test.ten >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_expect_success 'include cycles are detected' '
 	cat >.gitconfig <<-\EOF &&
 	[test]value = gitconfig
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* Re: [PATCH v2] config: learn the "onbranch:" includeIf condition
  2019-05-31 19:33               ` [PATCH v2] " Denton Liu
@ 2019-05-31 20:14                 ` Johannes Schindelin
  2019-06-05  8:02                   ` Johannes Schindelin
  2019-06-05 10:08                 ` Duy Nguyen
  2019-06-05 21:21                 ` [PATCH v3] " Denton Liu
  2 siblings, 1 reply; 59+ messages in thread
From: Johannes Schindelin @ 2019-05-31 20:14 UTC (permalink / raw)
  To: Denton Liu
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano

Hi Denton,

On Fri, 31 May 2019, Denton Liu wrote:

> Currently, if a user wishes to have individual settings per branch, they
> are required to manually keep track of the settings in their head and
> manually set the options on the command-line or change the config at
> each branch.
>
> Teach config the "onbranch:" includeIf condition so that it can
> conditionally include configuration files if the branch that is checked
> out in the current worktree matches the pattern given.

This looks good to me.

Thanks,
Dscho

> Signed-off-by: Denton Liu <liu.denton@gmail.com>
> ---
>
> Thanks for taking a look, Johannes. I've modified the patch to match
> with wildcards now.
>
> Changes since v1:
>
> * Allow onbranch: to specify a pattern instead of just a static
>   comparison
>
> Interdiff against v1:
>   diff --git a/Documentation/config.txt b/Documentation/config.txt
>   index 3b9fbe1860..93aa4323c6 100644
>   --- a/Documentation/config.txt
>   +++ b/Documentation/config.txt
>   @@ -145,9 +145,11 @@ refer to linkgit:gitignore[5] for details. For convenience:
>    	case-insensitively (e.g. on case-insensitive file sytems)
>
>    `onbranch`::
>   -	The data that follows the keyword `onbranch:` is taken to be a
>   -	name of a local branch. If we are in a worktree where that
>   -	branch is currently checked out, the include condition is met.
>   +	The data that follows the keyword `onbranch:` is taken to be a pattern
>   +	with standard globbing wildcards and two additional ones, `**/` and
>   +	`/**`, that can match multiple path components. If we are in a worktree
>   +	where the name of the branch that is currently checked out matches the
>   +	pattern, the include condition is met.
>
>    A few more notes on matching via `gitdir` and `gitdir/i`:
>
>   diff --git a/config.c b/config.c
>   index 954e84e13a..48bb435739 100644
>   --- a/config.c
>   +++ b/config.c
>   @@ -268,6 +268,8 @@ static int include_by_gitdir(const struct config_options *opts,
>    static int include_by_branch(const char *cond, size_t cond_len)
>    {
>    	int flags;
>   +	int ret;
>   +	struct strbuf pattern = STRBUF_INIT;
>    	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
>    	const char *shortname;
>
>   @@ -275,7 +277,10 @@ static int include_by_branch(const char *cond, size_t cond_len)
>    			!skip_prefix(refname, "refs/heads/", &shortname))
>    		return 0;
>
>   -	return !strncmp(cond, shortname, cond_len);
>   +	strbuf_add(&pattern, cond, cond_len);
>   +	ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
>   +	strbuf_release(&pattern);
>   +	return ret;
>    }
>
>    static int include_condition_is_true(const struct config_options *opts,
>   diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
>   index 062db08ad9..05c7def1f2 100755
>   --- a/t/t1305-config-include.sh
>   +++ b/t/t1305-config-include.sh
>   @@ -314,6 +314,7 @@ test_expect_success 'conditional include, onbranch' '
>    		cd bar &&
>    		echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
>    		echo "[test]nine=9" >.git/bar9 &&
>   +		git checkout -b master &&
>    		test_must_fail git config test.nine &&
>    		git checkout -b foo-branch &&
>    		echo 9 >expect &&
>   @@ -322,6 +323,25 @@ test_expect_success 'conditional include, onbranch' '
>    	)
>    '
>
>   +test_expect_success 'conditional include, onbranch, wildcard' '
>   +	(
>   +		cd bar &&
>   +		echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
>   +		echo "[test]ten=10" >.git/bar10 &&
>   +		git checkout -b not-foo-branch/a &&
>   +		test_must_fail git config test.ten &&
>   +
>   +		echo 10 >expect &&
>   +		git checkout -b foo-branch/a/b/c &&
>   +		git config test.ten >actual &&
>   +		test_cmp expect actual &&
>   +
>   +		git checkout -b moo-bar/a &&
>   +		git config test.ten >actual &&
>   +		test_cmp expect actual
>   +	)
>   +'
>   +
>    test_expect_success 'include cycles are detected' '
>    	cat >.gitconfig <<-\EOF &&
>    	[test]value = gitconfig
>
>  Documentation/config.txt  | 12 ++++++++++++
>  config.c                  | 21 +++++++++++++++++++++
>  t/t1305-config-include.sh | 33 +++++++++++++++++++++++++++++++++
>  3 files changed, 66 insertions(+)
>
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 7e2a6f61f5..93aa4323c6 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -144,6 +144,13 @@ refer to linkgit:gitignore[5] for details. For convenience:
>  	This is the same as `gitdir` except that matching is done
>  	case-insensitively (e.g. on case-insensitive file sytems)
>
> +`onbranch`::
> +	The data that follows the keyword `onbranch:` is taken to be a pattern
> +	with standard globbing wildcards and two additional ones, `**/` and
> +	`/**`, that can match multiple path components. If we are in a worktree
> +	where the name of the branch that is currently checked out matches the
> +	pattern, the include condition is met.
> +
>  A few more notes on matching via `gitdir` and `gitdir/i`:
>
>   * Symlinks in `$GIT_DIR` are not resolved before matching.
> @@ -206,6 +213,11 @@ Example
>  	[includeIf "gitdir:/path/to/group/"]
>  		path = foo.inc
>
> +	; include only if we are in a worktree where foo-branch is
> +	; currently checked out
> +	[includeIf "onbranch:foo-branch"]
> +		path = foo.inc
> +
>  Values
>  ~~~~~~
>
> diff --git a/config.c b/config.c
> index 296a6d9cc4..48bb435739 100644
> --- a/config.c
> +++ b/config.c
> @@ -19,6 +19,7 @@
>  #include "utf8.h"
>  #include "dir.h"
>  #include "color.h"
> +#include "refs.h"
>
>  struct config_source {
>  	struct config_source *prev;
> @@ -264,6 +265,24 @@ static int include_by_gitdir(const struct config_options *opts,
>  	return ret;
>  }
>
> +static int include_by_branch(const char *cond, size_t cond_len)
> +{
> +	int flags;
> +	int ret;
> +	struct strbuf pattern = STRBUF_INIT;
> +	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
> +	const char *shortname;
> +
> +	if (!refname || !(flags & REF_ISSYMREF)	||
> +			!skip_prefix(refname, "refs/heads/", &shortname))
> +		return 0;
> +
> +	strbuf_add(&pattern, cond, cond_len);
> +	ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
> +	strbuf_release(&pattern);
> +	return ret;
> +}
> +
>  static int include_condition_is_true(const struct config_options *opts,
>  				     const char *cond, size_t cond_len)
>  {
> @@ -272,6 +291,8 @@ static int include_condition_is_true(const struct config_options *opts,
>  		return include_by_gitdir(opts, cond, cond_len, 0);
>  	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
>  		return include_by_gitdir(opts, cond, cond_len, 1);
> +	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
> +		return include_by_branch(cond, cond_len);
>
>  	/* unknown conditionals are always false */
>  	return 0;
> diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
> index 579a86b7f8..05c7def1f2 100755
> --- a/t/t1305-config-include.sh
> +++ b/t/t1305-config-include.sh
> @@ -309,6 +309,39 @@ test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icas
>  	)
>  '
>
> +test_expect_success 'conditional include, onbranch' '
> +	(
> +		cd bar &&
> +		echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
> +		echo "[test]nine=9" >.git/bar9 &&
> +		git checkout -b master &&
> +		test_must_fail git config test.nine &&
> +		git checkout -b foo-branch &&
> +		echo 9 >expect &&
> +		git config test.nine >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success 'conditional include, onbranch, wildcard' '
> +	(
> +		cd bar &&
> +		echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
> +		echo "[test]ten=10" >.git/bar10 &&
> +		git checkout -b not-foo-branch/a &&
> +		test_must_fail git config test.ten &&
> +
> +		echo 10 >expect &&
> +		git checkout -b foo-branch/a/b/c &&
> +		git config test.ten >actual &&
> +		test_cmp expect actual &&
> +
> +		git checkout -b moo-bar/a &&
> +		git config test.ten >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
>  test_expect_success 'include cycles are detected' '
>  	cat >.gitconfig <<-\EOF &&
>  	[test]value = gitconfig
> --
> 2.22.0.rc1.169.g49223abbf8
>
>

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH v2] config: learn the "onbranch:" includeIf condition
  2019-05-31 20:14                 ` Johannes Schindelin
@ 2019-06-05  8:02                   ` Johannes Schindelin
  0 siblings, 0 replies; 59+ messages in thread
From: Johannes Schindelin @ 2019-06-05  8:02 UTC (permalink / raw)
  To: Denton Liu
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano

Hi Denton,

On Fri, 31 May 2019, Johannes Schindelin wrote:

> On Fri, 31 May 2019, Denton Liu wrote:
>
> > Currently, if a user wishes to have individual settings per branch,
> > they are required to manually keep track of the settings in their head
> > and manually set the options on the command-line or change the config
> > at each branch.
> >
> > Teach config the "onbranch:" includeIf condition so that it can
> > conditionally include configuration files if the branch that is
> > checked out in the current worktree matches the pattern given.
>
> This looks good to me.

... actually...

> > diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
> > index 579a86b7f8..05c7def1f2 100755
> > --- a/t/t1305-config-include.sh
> > +++ b/t/t1305-config-include.sh
> > @@ -309,6 +309,39 @@ test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icas
> >  	)
> >  '
> >
> > +test_expect_success 'conditional include, onbranch' '
> > +	(
> > +		cd bar &&

This `bar` is a side effect of an earlier test case. To be precise, of
this test case:

test_expect_success SYMLINKS 'conditional include, gitdir matching symlink' '
	ln -s foo bar &&
	(
		cd bar &&
		echo "[includeIf \"gitdir:bar/\"]path=bar7" >>.git/config
&&
		echo "[test]seven=7" >.git/bar7 &&
		echo 7 >expect &&
		git config test.seven >actual &&
		test_cmp expect actual
	)
'

As you can see, this test case is marked with the `SYMLINKS` prerequisite,
and hence can be skipped.

I got a crazy idea, though: how about `cd foo` instead of `cd bar`? It
should still work, as `bar` is just a symbolic link (unless that test case
has been skipped, in which case `bar` does not even exist) to `foo`.

And while this still relies on a side effect of an earlier test case
('conditional include, both unanchored', to be precise), at least that
test case cannot be skipped because of a missing prerequisite.

The same `s/cd bar/cd foo` applies to the second test case in this patch,
too, of course.

Thanks,
Dscho

> > +		echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
> > +		echo "[test]nine=9" >.git/bar9 &&
> > +		git checkout -b master &&
> > +		test_must_fail git config test.nine &&
> > +		git checkout -b foo-branch &&
> > +		echo 9 >expect &&
> > +		git config test.nine >actual &&
> > +		test_cmp expect actual
> > +	)
> > +'
> > +
> > +test_expect_success 'conditional include, onbranch, wildcard' '
> > +	(
> > +		cd bar &&
> > +		echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
> > +		echo "[test]ten=10" >.git/bar10 &&
> > +		git checkout -b not-foo-branch/a &&
> > +		test_must_fail git config test.ten &&
> > +
> > +		echo 10 >expect &&
> > +		git checkout -b foo-branch/a/b/c &&
> > +		git config test.ten >actual &&
> > +		test_cmp expect actual &&
> > +
> > +		git checkout -b moo-bar/a &&
> > +		git config test.ten >actual &&
> > +		test_cmp expect actual
> > +	)
> > +'
> > +
> >  test_expect_success 'include cycles are detected' '
> >  	cat >.gitconfig <<-\EOF &&
> >  	[test]value = gitconfig
> > --
> > 2.22.0.rc1.169.g49223abbf8
> >
> >
>
>

^ permalink raw reply	[flat|nested] 59+ messages in thread

* Re: [PATCH v2] config: learn the "onbranch:" includeIf condition
  2019-05-31 19:33               ` [PATCH v2] " Denton Liu
  2019-05-31 20:14                 ` Johannes Schindelin
@ 2019-06-05 10:08                 ` Duy Nguyen
  2019-06-05 21:21                 ` [PATCH v3] " Denton Liu
  2 siblings, 0 replies; 59+ messages in thread
From: Duy Nguyen @ 2019-06-05 10:08 UTC (permalink / raw)
  To: Denton Liu
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano, Johannes Schindelin

On Sat, Jun 1, 2019 at 2:34 AM Denton Liu <liu.denton@gmail.com> wrote:
>
> Currently, if a user wishes to have individual settings per branch, they
> are required to manually keep track of the settings in their head and
> manually set the options on the command-line or change the config at
> each branch.
>
> Teach config the "onbranch:" includeIf condition so that it can
> conditionally include configuration files if the branch that is checked
> out in the current worktree matches the pattern given.

Just wondering, anybody wants special settings on detached head (and
if so, what syntax should it be)?

> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 7e2a6f61f5..93aa4323c6 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -144,6 +144,13 @@ refer to linkgit:gitignore[5] for details. For convenience:
>         This is the same as `gitdir` except that matching is done
>         case-insensitively (e.g. on case-insensitive file sytems)
>
> +`onbranch`::
> +       The data that follows the keyword `onbranch:` is taken to be a pattern
> +       with standard globbing wildcards and two additional ones, `**/` and
> +       `/**`, that can match multiple path components. If we are in a worktree
> +       where the name of the branch that is currently checked out matches the
> +       pattern, the include condition is met.

Supporting transforming "foo/" to "foo/**" would be nice (gitdir and
gitdir/i do this). Useful when you organize your branches into
"subdirs".

> @@ -264,6 +265,24 @@ static int include_by_gitdir(const struct config_options *opts,
>         return ret;
>  }
>
> +static int include_by_branch(const char *cond, size_t cond_len)
> +{
> +       int flags;
> +       int ret;
> +       struct strbuf pattern = STRBUF_INIT;
> +       const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
> +       const char *shortname;
> +
> +       if (!refname || !(flags & REF_ISSYMREF) ||
> +                       !skip_prefix(refname, "refs/heads/", &shortname))

We probably don't even need to check more than !refname. Symbolic refs
from HEAD must be refs/heads/* or it's an error (fsck will complain).
But it's probably also ok to stay strict.

> +               return 0;
> +
> +       strbuf_add(&pattern, cond, cond_len);
> +       ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
> +       strbuf_release(&pattern);
> +       return ret;
> +}
-- 
Duy

^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH v3] config: learn the "onbranch:" includeIf condition
  2019-05-31 19:33               ` [PATCH v2] " Denton Liu
  2019-05-31 20:14                 ` Johannes Schindelin
  2019-06-05 10:08                 ` Duy Nguyen
@ 2019-06-05 21:21                 ` Denton Liu
  2019-06-06 12:52                   ` Johannes Schindelin
  2 siblings, 1 reply; 59+ messages in thread
From: Denton Liu @ 2019-06-05 21:21 UTC (permalink / raw)
  To: Git Mailing List
  Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano,
	Johannes Schindelin, Duy Nguyen

Currently, if a user wishes to have individual settings per branch, they
are required to manually keep track of the settings in their head and
manually set the options on the command-line or change the config at
each branch.

Teach config the "onbranch:" includeIf condition so that it can
conditionally include configuration files if the branch that is checked
out in the current worktree matches the pattern given.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---

Thanks for the review, Johannes and Duy. I've incorporated both of your
suggestions into this round.

Notes:
    Changes since v2:
    
    * Run test-cases outside foo/ since the subdirectory is not necessary
    * Implicitly add `**` when there is a trailing `/`
    
    Changes since v1:
    
    * Allow onbranch: to specify a pattern instead of just a static
      comparison

Interdiff against v2:
  diff --git a/Documentation/config.txt b/Documentation/config.txt
  index 93aa4323c6..e3f5bc3396 100644
  --- a/Documentation/config.txt
  +++ b/Documentation/config.txt
  @@ -145,11 +145,18 @@ refer to linkgit:gitignore[5] for details. For convenience:
   	case-insensitively (e.g. on case-insensitive file sytems)
   
   `onbranch`::
  -	The data that follows the keyword `onbranch:` is taken to be a pattern
  -	with standard globbing wildcards and two additional ones, `**/` and
  -	`/**`, that can match multiple path components. If we are in a worktree
  -	where the name of the branch that is currently checked out matches the
  -	pattern, the include condition is met.
  +	The data that follows the keyword `onbranch:` is taken to be a
  +	pattern with standard globbing wildcards and two additional
  +	ones, `**/` and `/**`, that can match multiple path components.
  +	If we are in a worktree where the name of the branch that is
  +	currently checked out matches the pattern, the include condition
  +	is met.
  ++
  +If the pattern ends with `/`, `**` will be automatically added. For
  +example, the pattern `foo/` becomes `foo/**`. In other words, it matches
  +all branches that begin with `foo/`. This is useful if your branches are
  +organized hierarchically and you would like to apply a configuration to
  +all the branches in that hierarchy.
   
   A few more notes on matching via `gitdir` and `gitdir/i`:
   
  diff --git a/config.c b/config.c
  index 48bb435739..ad4166e243 100644
  --- a/config.c
  +++ b/config.c
  @@ -171,6 +171,12 @@ static int handle_path_include(const char *path, struct config_include_data *inc
   	return ret;
   }
   
  +static void add_trailing_starstar_for_dir(struct strbuf *pat)
  +{
  +	if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
  +		strbuf_addstr(pat, "**");
  +}
  +
   static int prepare_include_condition_pattern(struct strbuf *pat)
   {
   	struct strbuf path = STRBUF_INIT;
  @@ -200,8 +206,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
   	} else if (!is_absolute_path(pat->buf))
   		strbuf_insert(pat, 0, "**/", 3);
   
  -	if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
  -		strbuf_addstr(pat, "**");
  +	add_trailing_starstar_for_dir(pat);
   
   	strbuf_release(&path);
   	return prefix;
  @@ -278,6 +283,7 @@ static int include_by_branch(const char *cond, size_t cond_len)
   		return 0;
   
   	strbuf_add(&pattern, cond, cond_len);
  +	add_trailing_starstar_for_dir(&pattern);
   	ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
   	strbuf_release(&pattern);
   	return ret;
  diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
  index 05c7def1f2..9571e366f8 100755
  --- a/t/t1305-config-include.sh
  +++ b/t/t1305-config-include.sh
  @@ -310,36 +310,42 @@ test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icas
   '
   
   test_expect_success 'conditional include, onbranch' '
  -	(
  -		cd bar &&
  -		echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
  -		echo "[test]nine=9" >.git/bar9 &&
  -		git checkout -b master &&
  -		test_must_fail git config test.nine &&
  -		git checkout -b foo-branch &&
  -		echo 9 >expect &&
  -		git config test.nine >actual &&
  -		test_cmp expect actual
  -	)
  +	echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
  +	echo "[test]nine=9" >.git/bar9 &&
  +	git checkout -b master &&
  +	test_must_fail git config test.nine &&
  +	git checkout -b foo-branch &&
  +	echo 9 >expect &&
  +	git config test.nine >actual &&
  +	test_cmp expect actual
   '
   
   test_expect_success 'conditional include, onbranch, wildcard' '
  -	(
  -		cd bar &&
  -		echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
  -		echo "[test]ten=10" >.git/bar10 &&
  -		git checkout -b not-foo-branch/a &&
  -		test_must_fail git config test.ten &&
  -
  -		echo 10 >expect &&
  -		git checkout -b foo-branch/a/b/c &&
  -		git config test.ten >actual &&
  -		test_cmp expect actual &&
  -
  -		git checkout -b moo-bar/a &&
  -		git config test.ten >actual &&
  -		test_cmp expect actual
  -	)
  +	echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
  +	echo "[test]ten=10" >.git/bar10 &&
  +	git checkout -b not-foo-branch/a &&
  +	test_must_fail git config test.ten &&
  +
  +	echo 10 >expect &&
  +	git checkout -b foo-branch/a/b/c &&
  +	git config test.ten >actual &&
  +	test_cmp expect actual &&
  +
  +	git checkout -b moo-bar/a &&
  +	git config test.ten >actual &&
  +	test_cmp expect actual
  +'
  +
  +test_expect_success 'conditional include, onbranch, implicit /** for /' '
  +	echo "[includeIf \"onbranch:foo-dir/\"]path=bar11" >>.git/config &&
  +	echo "[test]eleven=11" >.git/bar11 &&
  +	git checkout -b not-foo-dir/a &&
  +	test_must_fail git config test.eleven &&
  +
  +	echo 11 >expect &&
  +	git checkout -b foo-dir/a/b/c &&
  +	git config test.eleven >actual &&
  +	test_cmp expect actual
   '
   
   test_expect_success 'include cycles are detected' '

 Documentation/config.txt  | 19 +++++++++++++++++++
 config.c                  | 31 +++++++++++++++++++++++++++++--
 t/t1305-config-include.sh | 39 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 7e2a6f61f5..e3f5bc3396 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -144,6 +144,20 @@ refer to linkgit:gitignore[5] for details. For convenience:
 	This is the same as `gitdir` except that matching is done
 	case-insensitively (e.g. on case-insensitive file sytems)
 
+`onbranch`::
+	The data that follows the keyword `onbranch:` is taken to be a
+	pattern with standard globbing wildcards and two additional
+	ones, `**/` and `/**`, that can match multiple path components.
+	If we are in a worktree where the name of the branch that is
+	currently checked out matches the pattern, the include condition
+	is met.
++
+If the pattern ends with `/`, `**` will be automatically added. For
+example, the pattern `foo/` becomes `foo/**`. In other words, it matches
+all branches that begin with `foo/`. This is useful if your branches are
+organized hierarchically and you would like to apply a configuration to
+all the branches in that hierarchy.
+
 A few more notes on matching via `gitdir` and `gitdir/i`:
 
  * Symlinks in `$GIT_DIR` are not resolved before matching.
@@ -206,6 +220,11 @@ Example
 	[includeIf "gitdir:/path/to/group/"]
 		path = foo.inc
 
+	; include only if we are in a worktree where foo-branch is
+	; currently checked out
+	[includeIf "onbranch:foo-branch"]
+		path = foo.inc
+
 Values
 ~~~~~~
 
diff --git a/config.c b/config.c
index 296a6d9cc4..ad4166e243 100644
--- a/config.c
+++ b/config.c
@@ -19,6 +19,7 @@
 #include "utf8.h"
 #include "dir.h"
 #include "color.h"
+#include "refs.h"
 
 struct config_source {
 	struct config_source *prev;
@@ -170,6 +171,12 @@ static int handle_path_include(const char *path, struct config_include_data *inc
 	return ret;
 }
 
+static void add_trailing_starstar_for_dir(struct strbuf *pat)
+{
+	if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
+		strbuf_addstr(pat, "**");
+}
+
 static int prepare_include_condition_pattern(struct strbuf *pat)
 {
 	struct strbuf path = STRBUF_INIT;
@@ -199,8 +206,7 @@ static int prepare_include_condition_pattern(struct strbuf *pat)
 	} else if (!is_absolute_path(pat->buf))
 		strbuf_insert(pat, 0, "**/", 3);
 
-	if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
-		strbuf_addstr(pat, "**");
+	add_trailing_starstar_for_dir(pat);
 
 	strbuf_release(&path);
 	return prefix;
@@ -264,6 +270,25 @@ static int include_by_gitdir(const struct config_options *opts,
 	return ret;
 }
 
+static int include_by_branch(const char *cond, size_t cond_len)
+{
+	int flags;
+	int ret;
+	struct strbuf pattern = STRBUF_INIT;
+	const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+	const char *shortname;
+
+	if (!refname || !(flags & REF_ISSYMREF)	||
+			!skip_prefix(refname, "refs/heads/", &shortname))
+		return 0;
+
+	strbuf_add(&pattern, cond, cond_len);
+	add_trailing_starstar_for_dir(&pattern);
+	ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
+	strbuf_release(&pattern);
+	return ret;
+}
+
 static int include_condition_is_true(const struct config_options *opts,
 				     const char *cond, size_t cond_len)
 {
@@ -272,6 +297,8 @@ static int include_condition_is_true(const struct config_options *opts,
 		return include_by_gitdir(opts, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
 		return include_by_gitdir(opts, cond, cond_len, 1);
+	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
+		return include_by_branch(cond, cond_len);
 
 	/* unknown conditionals are always false */
 	return 0;
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 579a86b7f8..9571e366f8 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -309,6 +309,45 @@ test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icas
 	)
 '
 
+test_expect_success 'conditional include, onbranch' '
+	echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
+	echo "[test]nine=9" >.git/bar9 &&
+	git checkout -b master &&
+	test_must_fail git config test.nine &&
+	git checkout -b foo-branch &&
+	echo 9 >expect &&
+	git config test.nine >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'conditional include, onbranch, wildcard' '
+	echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
+	echo "[test]ten=10" >.git/bar10 &&
+	git checkout -b not-foo-branch/a &&
+	test_must_fail git config test.ten &&
+
+	echo 10 >expect &&
+	git checkout -b foo-branch/a/b/c &&
+	git config test.ten >actual &&
+	test_cmp expect actual &&
+
+	git checkout -b moo-bar/a &&
+	git config test.ten >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'conditional include, onbranch, implicit /** for /' '
+	echo "[includeIf \"onbranch:foo-dir/\"]path=bar11" >>.git/config &&
+	echo "[test]eleven=11" >.git/bar11 &&
+	git checkout -b not-foo-dir/a &&
+	test_must_fail git config test.eleven &&
+
+	echo 11 >expect &&
+	git checkout -b foo-dir/a/b/c &&
+	git config test.eleven >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'include cycles are detected' '
 	cat >.gitconfig <<-\EOF &&
 	[test]value = gitconfig
-- 
2.22.0.rc1.169.g49223abbf8


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* Re: [PATCH v3] config: learn the "onbranch:" includeIf condition
  2019-06-05 21:21                 ` [PATCH v3] " Denton Liu
@ 2019-06-06 12:52                   ` Johannes Schindelin
  0 siblings, 0 replies; 59+ messages in thread
From: Johannes Schindelin @ 2019-06-06 12:52 UTC (permalink / raw)
  To: Denton Liu
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Junio C Hamano, Duy Nguyen

Hi Denton,

On Wed, 5 Jun 2019, Denton Liu wrote:

> Currently, if a user wishes to have individual settings per branch, they
> are required to manually keep track of the settings in their head and
> manually set the options on the command-line or change the config at
> each branch.
>
> Teach config the "onbranch:" includeIf condition so that it can
> conditionally include configuration files if the branch that is checked
> out in the current worktree matches the pattern given.
>
> Signed-off-by: Denton Liu <liu.denton@gmail.com>
> ---
>
> Thanks for the review, Johannes and Duy. I've incorporated both of your
> suggestions into this round.
>
> Notes:
>     Changes since v2:
>
>     * Run test-cases outside foo/ since the subdirectory is not necessary

That should indeed take care of my concern. Thank you!
Dscho

^ permalink raw reply	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 0/8] teach branch-specific options for format-patch
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (8 preceding siblings ...)
  2019-05-31  0:30     ` [PATCH v3 0/8] teach branch-specific options for format-patch Denton Liu
@ 2019-06-14 21:56     ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 1/8] t4014: clean up style Denton Liu
                         ` (7 more replies)
  2019-10-15  9:06     ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Denton Liu
  10 siblings, 8 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

Hi all,

Although 'config: learn the "onbranch:" includeIf condition'[1] seems to be
on track for "next", the content in this patchset is not entirely
superseded by it.

Patches 1 and 2 should be relatively uncontroversial since they just do
a bit of cleanup.

Patch 3 is a new feature that isn't included in the config onbranch
stuff at all. In introduces the `--infer-cover-subject` command-line
option which would allow a cover-letter's subject to be automatically
inferred from the branch's description.

Finally, patches 4-8 are all needed to implement branch-specific
format-patch options. Although this behaviour can be replicated by the
config onbranch patches, it's not a direct replacement. Consider the
following:

	$ git checkout foo
	$ git format-patch master..bar

With config onbranch, this would generate patches using foo's settings
but with this patchset, we would generate patches using bar's settings,
which seems more desirable and intuitive for users.

I suppose if adding a `format.<branch>.*` option is controversial since
we won't be sure if we're adding other intermediate format keys, we
could follow onbranch convention and use `format.forbranch:<branch>.*`
as configuration keys instead.

Thanks,

Denton

[1]: https://public-inbox.org/git/49e22269d5a66d1975fca71a300e9099f46e1c40.1559769658.git.liu.denton@gmail.com/

Changes since v2:

* Replaced `format.<branch-name>.coverSubject` and `--cover-subject`
  with `format.inferCoverSubject` and `--infer-cover-subject` which
  reads the subject from the branch description
* Do not let `git branch -d` delete `format.<branch-name>.*` configs
* More documentation cleanup
* Taught format-patch to read `format.<branch-name>.outputDirectory` as
  well

Changes since v1:

* Used format.<branch-name>.* variables instead of branch.<branch-name>.*


Denton Liu (8):
  t4014: clean up style
  Doc: add more detail for git-format-patch
  format-patch: infer cover letter from branch description
  format-patch: move extra_headers logic later
  string-list: create string_list_append_all
  format-patch: read branch-specific To: and Cc: headers
  format-patch: move output_directory logic later
  format-patch: read branch-specific output directory

 Documentation/config/format.txt    |  17 +-
 Documentation/git-format-patch.txt |  44 +-
 builtin/log.c                      | 237 ++++++---
 string-list.c                      |   9 +
 string-list.h                      |   7 +
 t/t4014-format-patch.sh            | 739 +++++++++++++++++------------
 6 files changed, 667 insertions(+), 386 deletions(-)

Range-diff against v2:
1:  75474e18f4 = 1:  650f04bc04 t4014: clean up style
2:  9ae349bf8a ! 2:  14b40b1dec Doc: add more detail for git-format-patch
    @@ -9,7 +9,10 @@
     
         In addition, document the special value of `--base=auto`.
     
    -    Finally, while we're at it, surround option arguments with <>.
    +    Next, while we're at it, surround option arguments with <>.
    +
    +    Finally, document the `format.outputDirectory` config and change
    +    `format.coverletter` to use camelcase.
     
         Signed-off-by: Denton Liu <liu.denton@gmail.com>
     
    @@ -67,3 +70,25 @@
      
      --root::
      	Treat the revision argument as a <revision range>, even if it
    +@@
    + -------------
    + You can specify extra mail header lines to be added to each message,
    + defaults for the subject prefix and file suffix, number patches when
    +-outputting more than one patch, add "To" or "Cc:" headers, configure
    +-attachments, and sign off patches with configuration variables.
    ++outputting more than one patch, add "To:" or "Cc:" headers, configure
    ++attachments, change the patch output directory, and sign off patches
    ++with configuration variables.
    + 
    + ------------
    + [format]
    +@@
    + 	cc = <email>
    + 	attach [ = mime-boundary-string ]
    + 	signOff = true
    +-	coverletter = auto
    ++	outputDirectory = <directory>
    ++	coverLetter = auto
    + ------------
    + 
    + 
3:  c0403f5139 < -:  ---------- format-patch: make cover letter subject configurable
-:  ---------- > 3:  ca2b4c21c1 format-patch: infer cover letter from branch description
4:  534c6fd48a ! 4:  82a56a0d76 format-patch: move extra_headers logic later
    @@ -7,10 +7,10 @@
         logic later in the function so that this happens. (This patch is best
         viewed with `git diff --color-moved`.)
     
    -    Note that this logic only depends on the `git_config` and
    +    Note that this logic only depends on `git_config` and
         `repo_init_revisions` and is depended on by the patch creation logic
    -    which is directly below it so this move is effectively a no-op as
    -    no dependencies being reordered.
    +    which is directly below it so this move is effectively a no-op as no
    +    dependencies being reordered.
     
         Signed-off-by: Denton Liu <liu.denton@gmail.com>
     
5:  491297c40a = 5:  3554164deb string-list: create string_list_append_all
6:  e2c71fa593 ! 6:  6ad1b25858 format-patch: read branch-specific To: and Cc: headers
    @@ -59,29 +59,20 @@
      	far (from config or command line).
      
     @@
    - -------------
    - You can specify extra mail header lines to be added to each message,
    - defaults for the subject prefix and file suffix, number patches when
    --outputting more than one patch, add "To" or "Cc:" headers, configure
    -+outputting more than one patch, add "To:" or "Cc:" headers, configure
    - attachments, and sign off patches with configuration variables.
    - 
    - ------------
    -@@
    + 	inferCoverSubject = true
      ------------
      
    - In addition, for a specific branch, you can specify a custom cover
    --letter subject.
    ++In addition, for a specific branch, you can specify a custom cover
     +letter subject, and add additional "To:" or "Cc:" headers.
    - 
    - ------------
    - [format "branch-name"]
    - 	coverSubject = "subject for branch-name only"
    ++
    ++------------
    ++[format "branch-name"]
     +	to = <email>
     +	cc = <email>
    - ------------
    ++------------
      
      DISCUSSION
    + ----------
     
      diff --git a/builtin/log.c b/builtin/log.c
      --- a/builtin/log.c
    @@ -96,7 +87,7 @@
      static void add_header(const char *value)
      {
     @@
    - 	return 0;
    + 	fprintf(rev->diffopt.file, "\n");
      }
      
     +static void add_branch_headers(struct rev_info *rev, const char *branch_name)
    @@ -186,25 +177,6 @@
      		strbuf_addstr(&buf, extra_hdr.items[i].string);
      		strbuf_addch(&buf, '\n');
     
    - diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
    - --- a/t/t3200-branch.sh
    - +++ b/t/t3200-branch.sh
    -@@
    - 
    - test_expect_success 'test deleting branch deletes branch config' '
    - 	git config format.my7.coverSubject "cover subject" &&
    -+	git config format.my7.to "To Me <to@example.com>" &&
    -+	git config format.my7.cc "Cc Me <cc@example.com>" &&
    - 	git branch -d my7 &&
    - 	test -z "$(git config branch.my7.remote)" &&
    - 	test -z "$(git config branch.my7.merge)" &&
    - 	test -z "$(git config format.my7.coverSubject)"
    -+	test -z "$(git config format.my7.to)" &&
    -+	test -z "$(git config format.my7.cc)"
    - '
    - 
    - test_expect_success 'test deleting branch without config' '
    -
      diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
      --- a/t/t4014-format-patch.sh
      +++ b/t/t4014-format-patch.sh
-:  ---------- > 7:  3bc1b95a4e format-patch: move output_directory logic later
-:  ---------- > 8:  2cf7c77f59 format-patch: read branch-specific output directory
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 1/8] t4014: clean up style
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 2/8] Doc: add more detail for git-format-patch Denton Liu
                         ` (6 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In Git's tests, there is typically no space between the redirection
operator and the filename. Remove these spaces.

Since output is silenced when running without `-v` and debugging
output is useful with `-v`, remove redirections to /dev/null.

Change here-docs from `<<\EOF` to `<<-\EOF` so that they can be indented
along with the rest of the test case.

Finally, refactor to remove Git commands upstream of pipe. This way, if
an invocation of a Git command fails, the return code won't be lost.
Keep upstream non-Git commands since we have to assume a base level of
sanity.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)

diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index b6e2fdbc44..3423f974bc 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -34,7 +34,8 @@ test_expect_success setup '
 	git commit -m "Side changes #3 with \\n backslash-n in it." &&
 
 	git checkout master &&
-	git diff-tree -p C2 | git apply --index &&
+	git diff-tree -p C2 >patch &&
+	git apply --index <patch &&
 	test_tick &&
 	git commit -m "Master accepts moral equivalent of #2"
 
@@ -77,7 +78,7 @@ test_expect_success "format-patch doesn't consider merge commits" '
 	git checkout -b merger master &&
 	test_tick &&
 	git merge --no-ff slave &&
-	cnt=$(git format-patch -3 --stdout | grep "^From " | wc -l) &&
+	cnt=$(git format-patch -3 --stdout >patch && grep "^From " patch | wc -l) &&
 	test $cnt = 3
 '
 
@@ -85,21 +86,22 @@ test_expect_success "format-patch result applies" '
 
 	git checkout -b rebuild-0 master &&
 	git am -3 patch0 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success "format-patch --ignore-if-in-upstream result applies" '
 
 	git checkout -b rebuild-1 master &&
 	git am -3 patch1 &&
-	cnt=$(git rev-list master.. | wc -l) &&
-	test $cnt = 2
+	git rev-list master.. >list &&
+	test_line_count = 2 list
 '
 
 test_expect_success 'commit did not screw up the log message' '
 
-	git cat-file commit side | grep "^Side .* with .* backslash-n"
+	git cat-file commit side >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -112,7 +114,8 @@ test_expect_success 'format-patch did not screw up the log message' '
 
 test_expect_success 'replay did not screw up the log message' '
 
-	git cat-file commit rebuild-1 | grep "^Side .* with .* backslash-n"
+	git cat-file commit rebuild-1 >actual &&
+	grep "^Side .* with .* backslash-n" actual
 
 '
 
@@ -122,8 +125,8 @@ test_expect_success 'extra headers' '
 " &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>
 " &&
-	git format-patch --stdout master..side > patch2 &&
-	sed -e "/^\$/q" patch2 > hdrs2 &&
+	git format-patch --stdout master..side >patch2 &&
+	sed -e "/^\$/q" patch2 >hdrs2 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "Cc: S E Cipient <scipient@example.com>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <scipient@example.com>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <rcipient@example.com>" &&
 	git config --add format.headers "To: S E Cipient <scipient@example.com>" &&
-	git format-patch --stdout master..side > patch4 &&
-	sed -e "/^\$/q" patch4 > hdrs4 &&
+	git format-patch --stdout master..side >patch4 &&
+	sed -e "/^\$/q" patch4 >hdrs4 &&
 	grep "^To: R E Cipient <rcipient@example.com>,\$" hdrs4 &&
 	grep "^ *S E Cipient <scipient@example.com>\$" hdrs4
 '
@@ -153,72 +156,82 @@ test_expect_success 'extra headers with multiple To:s' '
 test_expect_success 'additional command line cc (ascii)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch5
+	git format-patch --cc="S E Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" patch5
+	git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <scipient@example.com>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <rcipient@example.com>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <rcipient@example.com>" &&
-	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <rcipient@example.com>,\$" patch7 &&
-	grep "^ *S E Cipient <scipient@example.com>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <scipient@example.com>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <rcipient@example.com>,\$" hdrs7 &&
+	grep "^ *S E Cipient <scipient@example.com>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch8
+	git format-patch --to="R E Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch8
+	git format-patch --to="R. E. Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch8
+	git format-patch --to="R Ä Cipient <rcipient@example.com>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <rcipient@example.com>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
 # check_patch <patch>: Verify that <patch> looks like a half-sane
@@ -231,52 +244,52 @@ check_patch () {
 
 test_expect_success 'format.from=false' '
 
-	git -c format.from=false format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
+	git -c format.from=false format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
 	check_patch patch &&
-	! grep "^From: C O Mitter <committer@example.com>\$" patch
+	! grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from=true' '
 
-	git -c format.from=true format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: C O Mitter <committer@example.com>\$" patch
+	git -c format.from=true format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: C O Mitter <committer@example.com>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <from@example.com>\$" patch
+	git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <from@example.com>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
@@ -284,31 +297,31 @@ test_expect_success '--no-to and --to replaces config.to' '
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <someone@out.there>\$" patch11 &&
-	grep "^To: Someone Else <else@out.there>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
 '
 
 test_expect_success 'multiple files' '
@@ -338,7 +351,7 @@ test_expect_success 'reroll count (-v)' '
 check_threading () {
 	expect="$1" &&
 	shift &&
-	(git format-patch --stdout "$@"; echo $? > status.out) |
+	(git format-patch --stdout "$@"; echo $? >status.out) |
 	# Prints everything between the Message-ID and In-Reply-To,
 	# and replaces all Message-ID-lookalikes by a sequence number
 	perl -ne '
@@ -353,12 +366,12 @@ check_threading () {
 			print;
 		}
 		print "---\n" if /^From /i;
-	' > actual &&
+	' >actual &&
 	test 0 = "$(cat status.out)" &&
 	test_cmp "$expect" actual
 }
 
-cat >> expect.no-threading <<EOF
+cat >>expect.no-threading <<EOF
 ---
 ---
 ---
@@ -369,7 +382,7 @@ test_expect_success 'no threading' '
 	check_threading expect.no-threading master
 '
 
-cat > expect.thread <<EOF
+cat >expect.thread <<EOF
 ---
 Message-Id: <0>
 ---
@@ -386,7 +399,7 @@ test_expect_success 'thread' '
 	check_threading expect.thread --thread master
 '
 
-cat > expect.in-reply-to <<EOF
+cat >expect.in-reply-to <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -406,7 +419,7 @@ test_expect_success 'thread in-reply-to' '
 		--thread master
 '
 
-cat > expect.cover-letter <<EOF
+cat >expect.cover-letter <<EOF
 ---
 Message-Id: <0>
 ---
@@ -427,7 +440,7 @@ test_expect_success 'thread cover-letter' '
 	check_threading expect.cover-letter --cover-letter --thread master
 '
 
-cat > expect.cl-irt <<EOF
+cat >expect.cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -459,7 +472,7 @@ test_expect_success 'thread explicit shallow' '
 		--in-reply-to="<test.message>" --thread=shallow master
 '
 
-cat > expect.deep <<EOF
+cat >expect.deep <<EOF
 ---
 Message-Id: <0>
 ---
@@ -477,7 +490,7 @@ test_expect_success 'thread deep' '
 	check_threading expect.deep --thread=deep master
 '
 
-cat > expect.deep-irt <<EOF
+cat >expect.deep-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -500,7 +513,7 @@ test_expect_success 'thread deep in-reply-to' '
 		--in-reply-to="<test.message>" master
 '
 
-cat > expect.deep-cl <<EOF
+cat >expect.deep-cl <<EOF
 ---
 Message-Id: <0>
 ---
@@ -524,7 +537,7 @@ test_expect_success 'thread deep cover-letter' '
 	check_threading expect.deep-cl --cover-letter --thread=deep master
 '
 
-cat > expect.deep-cl-irt <<EOF
+cat >expect.deep-cl-irt <<EOF
 ---
 Message-Id: <0>
 In-Reply-To: <1>
@@ -606,7 +619,7 @@ test_expect_success 'cover-letter inherits diff options' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
   This is an excessively long subject line for a message due to the
     habit some projects have of not having a short, one-line subject at
     the start of the commit message, but rather sticking a whole
@@ -619,12 +632,12 @@ EOF
 test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
 
 	git format-patch --cover-letter -2 &&
-	sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
+	sed -e "1,/A U Thor/d" -e "/^\$/q" <0000-cover-letter.patch >output &&
 	test_cmp expect output
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 index $before..$after 100644
 --- a/file
 +++ b/file
@@ -646,7 +659,7 @@ test_expect_success 'format-patch respects -U' '
 
 '
 
-cat > expect << EOF
+cat >expect <<EOF
 
 diff --git a/file b/file
 index $before..$after 100644
@@ -662,7 +675,7 @@ EOF
 test_expect_success 'format-patch -p suppresses stat' '
 
 	git format-patch -p -2 &&
-	sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+	sed -e "1,/^\$/d" -e "/^+5/q" <0001-This-is-an-excessively-long-subject-line-for-a-messa.patch >output &&
 	test_cmp expect output
 
 '
@@ -717,7 +730,7 @@ test_expect_success 'format-patch from a subdirectory (3)' '
 '
 
 test_expect_success 'format-patch --in-reply-to' '
-	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "baz@foo.bar" >patch8 &&
 	grep "^In-Reply-To: <baz@foo.bar>" patch8 &&
 	grep "^References: <baz@foo.bar>" patch8
 '
@@ -738,20 +751,20 @@ test_expect_success 'format-patch --notes --signoff' '
 	sed "1,/^---$/d" out | grep "test message"
 '
 
-echo "fatal: --name-only does not make sense" > expect.name-only
-echo "fatal: --name-status does not make sense" > expect.name-status
-echo "fatal: --check does not make sense" > expect.check
+echo "fatal: --name-only does not make sense" >expect.name-only
+echo "fatal: --name-status does not make sense" >expect.name-status
+echo "fatal: --check does not make sense" >expect.check
 
 test_expect_success 'options no longer allowed for format-patch' '
-	test_must_fail git format-patch --name-only 2> output &&
+	test_must_fail git format-patch --name-only 2>output &&
 	test_i18ncmp expect.name-only output &&
-	test_must_fail git format-patch --name-status 2> output &&
+	test_must_fail git format-patch --name-status 2>output &&
 	test_i18ncmp expect.name-status output &&
-	test_must_fail git format-patch --check 2> output &&
+	test_must_fail git format-patch --check 2>output &&
 	test_i18ncmp expect.check output'
 
 test_expect_success 'format-patch --numstat should produce a patch' '
-	git format-patch --numstat --stdout master..side > output &&
+	git format-patch --numstat --stdout master..side >output &&
 	test 5 = $(grep "^diff --git a/" output | wc -l)'
 
 test_expect_success 'format-patch -- <path>' '
@@ -763,20 +776,22 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' '
 	git format-patch --ignore-if-in-upstream HEAD
 '
 
-git_version="$(git --version | sed "s/.* //")"
+git_version="$(git --version >version && sed "s/.* //" version)"
 
 signature() {
 	printf "%s\n%s\n\n" "-- " "${1:-$git_version}"
 }
 
 test_expect_success 'format-patch default signature' '
-	git format-patch --stdout -1 | tail -n 3 >output &&
+	git format-patch --stdout -1 >patch &&
+	tail -n 3 patch >output &&
 	signature >expect &&
 	test_cmp expect output
 '
 
 test_expect_success 'format-patch --signature' '
-	git format-patch --stdout --signature="my sig" -1 | tail -n 3 >output &&
+	git format-patch --stdout --signature="my sig" -1 >patch &&
+	tail -n 3 patch >output &&
 	signature "my sig" >expect &&
 	test_cmp expect output
 '
@@ -1167,282 +1182,282 @@ append_signoff()
 
 test_expect_success 'signoff: commit with no body' '
 	append_signoff </dev/null >actual &&
-	cat <<\EOF | sed "s/EOL$//" >expected &&
-4:Subject: [PATCH] EOL
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject' '
 	echo subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: commit with only subject that does not end with NL' '
 	printf subject | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: no existing signoffs and no trailing NL' '
 	printf "subject\n\nbody" | append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: misc conforming footer elements' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: my@house
-(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
-Tested-by: Some One <someone@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: my@house
-15:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <someone@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: my@house
+	15:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: some random signoff-alike' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
-Fooled-by-me: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-I want to mention about Signed-off-by: here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:I want to mention about Signed-off-by: here.
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	I want to mention about Signed-off-by: here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:I want to mention about Signed-off-by: here.
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: not really a signoff (2)' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-My unfortunate
-Signed-off-by: example happens to be wrapped here.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:Signed-off-by: example happens to be wrapped here.
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	My unfortunate
+	Signed-off-by: example happens to be wrapped here.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:Signed-off-by: example happens to be wrapped here.
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Signed-off-by: my@house
-Signed-off-by: your@house
+	Signed-off-by: my@house
+	Signed-off-by: your@house
 
-A lot of houses.
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: my@house
-10:Signed-off-by: your@house
-11:
-13:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	A lot of houses.
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: my@house
+	10:Signed-off-by: your@house
+	11:
+	13:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
 	printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: the same signoff NOT at the end' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Signed-off-by: C O Mitter <committer@example.com>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <committer@example.com>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <committer@example.com>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	12:Signed-off-by: my@house
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: tolerate garbage in conforming footer' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Tested-by: my@house
-Some Trash
-Signed-off-by: C O Mitter <committer@example.com>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <committer@example.com>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: respect trailer config' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:
-12:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual &&
 
 	test_config trailer.Myfooter.ifexists add &&
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-Myfooter: x
-Some Trash
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-11:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
 test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
-	append_signoff <<\EOF >actual &&
-subject
+	append_signoff <<-\EOF >actual &&
+	subject
 
-body
+	body
 
-Reviewed-id: Noone
-Tested-by: my@house
-Change-id: Ideadbeef
-Signed-off-by: C O Mitter <committer@example.com>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <committer@example.com>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <committer@example.com>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <committer@example.com>
+	EOF
 	test_cmp expected actual
 '
 
@@ -1458,42 +1473,42 @@ test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (2)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter rebuild-1~2..rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (3)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter ^master rebuild-1 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (4)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter master.. >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (5)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 HEAD >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter using branch description (6)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
 	git format-patch --stdout --cover-letter -2 >actual &&
-	grep hello actual >/dev/null
+	grep hello actual
 '
 
 test_expect_success 'cover letter with nothing' '
@@ -1547,7 +1562,8 @@ test_expect_success 'format-patch format.outputDirectory option' '
 	test_config format.outputDirectory patches &&
 	rm -fr patches &&
 	git format-patch master..side &&
-	test $(git rev-list master..side | wc -l) -eq $(ls patches | wc -l)
+	git rev-list master..side >list &&
+	test_line_count = $(ls patches | wc -l) list
 '
 
 test_expect_success 'format-patch -o overrides format.outputDirectory' '
@@ -1560,13 +1576,21 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
 
 test_expect_success 'format-patch --base' '
 	git checkout side &&
-	git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual1 &&
-	git format-patch --stdout --base=HEAD~3 HEAD~.. | tail -n 7 >actual2 &&
+	git format-patch --stdout --base=HEAD~3 -1 >patch &&
+	tail -n 7 patch >actual1 &&
+	git format-patch --stdout --base=HEAD~3 HEAD~.. >patch &&
+	tail -n 7 patch >actual2 &&
 	echo >expected &&
 	echo "base-commit: $(git rev-parse HEAD~3)" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected &&
-	signature >> expected &&
+	git show --patch HEAD~2 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	git show --patch HEAD~1 >patch &&
+	git patch-id --stable <patch >patch.id.raw &&
+	awk "{print \$1}" <patch.id.raw >patch.id &&
+	echo "prerequisite-patch-id: $(cat patch.id)" >>expected &&
+	signature >>expected &&
 	test_cmp expected actual1 &&
 	test_cmp expected actual2
 '
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 2/8] Doc: add more detail for git-format-patch
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 1/8] t4014: clean up style Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 3/8] format-patch: infer cover letter from branch description Denton Liu
                         ` (5 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In git-format-patch.txt, we were missing some key user information.
First of all, using the `--to` and `--cc` options don't override
`format.to` and `format.cc` variables, respectively. They add on to each
other. Document this.

In addition, document the special value of `--base=auto`.

Next, while we're at it, surround option arguments with <>.

Finally, document the `format.outputDirectory` config and change
`format.coverletter` to use camelcase.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/git-format-patch.txt | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 1af85d404f..4e180bbfbb 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -17,9 +17,9 @@ SYNOPSIS
 		   [--signature-file=<file>]
 		   [-n | --numbered | -N | --no-numbered]
 		   [--start-number <n>] [--numbered-files]
-		   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
-		   [--rfc] [--subject-prefix=Subject-Prefix]
+		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
 		   [--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
@@ -158,7 +158,7 @@ Beware that the default for 'git send-email' is to thread emails
 itself.  If you want `git format-patch` to take care of threading, you
 will want to ensure that threading is disabled for `git send-email`.
 
---in-reply-to=Message-Id::
+--in-reply-to=<Message-Id>::
 	Make the first mail (or all the mails with `--no-thread`) appear as a
 	reply to the given Message-Id, which avoids breaking threads to
 	provide a new patch series.
@@ -192,13 +192,17 @@ will want to ensure that threading is disabled for `git send-email`.
 
 --to=<email>::
 	Add a `To:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
 --cc=<email>::
 	Add a `Cc:` header to the email headers. This is in addition
-	to any configured headers, and may be used multiple times.
+	to any configured headers, and may be used multiple times. The
+	emails given will be used along with any emails given by
+	`format.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -309,7 +313,8 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 --base=<commit>::
 	Record the base tree information to identify the state the
 	patch series applies to.  See the BASE TREE INFORMATION section
-	below for details.
+	below for details. If <commit> is equal to "auto", a base commit
+	is automatically chosen.
 
 --root::
 	Treat the revision argument as a <revision range>, even if it
@@ -325,8 +330,9 @@ CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each message,
 defaults for the subject prefix and file suffix, number patches when
-outputting more than one patch, add "To" or "Cc:" headers, configure
-attachments, and sign off patches with configuration variables.
+outputting more than one patch, add "To:" or "Cc:" headers, configure
+attachments, change the patch output directory, and sign off patches
+with configuration variables.
 
 ------------
 [format]
@@ -338,7 +344,8 @@ attachments, and sign off patches with configuration variables.
 	cc = <email>
 	attach [ = mime-boundary-string ]
 	signOff = true
-	coverletter = auto
+	outputDirectory = <directory>
+	coverLetter = auto
 ------------
 
 
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 3/8] format-patch: infer cover letter from branch description
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 1/8] t4014: clean up style Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 2/8] Doc: add more detail for git-format-patch Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 4/8] format-patch: move extra_headers logic later Denton Liu
                         ` (4 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

We used to populate the subject of the cover letter generated by
git-format-patch with "*** SUBJECT HERE ***". However, if a user submits
multiple patchsets, they may want to keep a consistent subject between
rerolls.

If git-format-patch is run with `--infer-cover-letter` or
`format.inferCoverSubject`, infer the subject for the cover letter from
the top line(s) of a branch description, similar to how a subject is
read from a commit message.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |  5 +++
 Documentation/git-format-patch.txt | 10 ++++++
 builtin/log.c                      | 58 ++++++++++++++++++------------
 t/t4014-format-patch.sh            | 33 +++++++++++++++++
 4 files changed, 84 insertions(+), 22 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index dc77941c48..c8b28fe47f 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -36,6 +36,11 @@ format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
 	subject prefix. Use this variable to change that prefix.
 
+format.<branch-name>.inferCoverSubject::
+	A boolean that controls whether or not to infer the subject for
+	the cover letter based on the branch's description. See the
+	--infer-cover-subject option in linkgit:git-format-patch[1].
+
 format.signature::
 	The default for format-patch is to output a signature containing
 	the Git version number. Use this variable to change that default.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 4e180bbfbb..f54aecf4bf 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -19,6 +19,7 @@ SYNOPSIS
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<Message-Id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
+		   [--[no-]infer-cover-subject]
 		   [--rfc] [--subject-prefix=<Subject-Prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
@@ -170,6 +171,14 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--[no-]infer-cover-subject::
+	Instead of using the default "*** SUBJECT HERE ***" subject for
+	the cover letter, infer the subject from the branch's
+	description.
++
+Similar to a commit message, the subject is inferred as the beginning of
+the description up to and excluding the first blank line.
+
 --subject-prefix=<Subject-Prefix>::
 	Instead of the standard '[PATCH]' prefix in the subject
 	line, instead use '[<Subject-Prefix>]'. This
@@ -346,6 +355,7 @@ with configuration variables.
 	signOff = true
 	outputDirectory = <directory>
 	coverLetter = auto
+	inferCoverSubject = true
 ------------
 
 
diff --git a/builtin/log.c b/builtin/log.c
index e43ee12fb1..617f074d60 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -769,6 +769,7 @@ static const char *signature = git_version_string;
 static const char *signature_file;
 static int config_cover_letter;
 static const char *config_output_directory;
+static int infer_cover_subject;
 
 enum {
 	COVER_UNSET,
@@ -864,6 +865,10 @@ static int git_format_config(const char *var, const char *value, void *cb)
 			from = NULL;
 		return 0;
 	}
+	if (!strcmp(var, "format.infercoversubject")) {
+		infer_cover_subject = git_config_bool(var, value);
+		return 0;
+	}
 
 	return git_log_config(var, value, cb);
 }
@@ -970,20 +975,6 @@ static void print_signature(FILE *file)
 	putc('\n', file);
 }
 
-static void add_branch_description(struct strbuf *buf, const char *branch_name)
-{
-	struct strbuf desc = STRBUF_INIT;
-	if (!branch_name || !*branch_name)
-		return;
-	read_branch_desc(&desc, branch_name);
-	if (desc.len) {
-		strbuf_addch(buf, '\n');
-		strbuf_addbuf(buf, &desc);
-		strbuf_addch(buf, '\n');
-	}
-	strbuf_release(&desc);
-}
-
 static char *find_branch_name(struct rev_info *rev)
 {
 	int i, positive = -1;
@@ -1034,13 +1025,17 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
 			      const char *branch_name,
+			      int infer_subject,
 			      int quiet)
 {
 	const char *committer;
-	const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-	const char *msg;
+	const char *subject = "*** SUBJECT HERE ***";
+	const char *body = "*** BLURB HERE ***";
+	const char *description = NULL;
 	struct shortlog log;
 	struct strbuf sb = STRBUF_INIT;
+	struct strbuf description_sb = STRBUF_INIT;
+	struct strbuf subject_sb = STRBUF_INIT;
 	int i;
 	const char *encoding = "UTF-8";
 	int need_8bit_cte = 0;
@@ -1068,17 +1063,34 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 	if (!branch_name)
 		branch_name = find_branch_name(rev);
 
-	msg = body;
+	if (branch_name && *branch_name)
+		read_branch_desc(&description_sb, branch_name);
+
+	if (description_sb.len) {
+		if (infer_subject) {
+			description = format_subject(&subject_sb, description_sb.buf, " ");
+			subject = subject_sb.buf;
+		} else {
+			description = description_sb.buf;
+		}
+	}
+
 	pp.fmt = CMIT_FMT_EMAIL;
 	pp.date_mode.type = DATE_RFC2822;
 	pp.rev = rev;
 	pp.print_email_subject = 1;
 	pp_user_info(&pp, NULL, &sb, committer, encoding);
-	pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
-	pp_remainder(&pp, &msg, &sb, 0);
-	add_branch_description(&sb, branch_name);
+	pp_title_line(&pp, &subject, &sb, encoding, need_8bit_cte);
+	pp_remainder(&pp, &body, &sb, 0);
+	if (description) {
+		strbuf_addch(&sb, '\n');
+		strbuf_addstr(&sb, description);
+		strbuf_addch(&sb, '\n');
+	}
 	fprintf(rev->diffopt.file, "%s\n", sb.buf);
 
+	strbuf_release(&description_sb);
+	strbuf_release(&subject_sb);
 	strbuf_release(&sb);
 
 	shortlog_init(&log);
@@ -1554,6 +1566,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "rfc", &rev, NULL,
 			    N_("Use [RFC PATCH] instead of [PATCH]"),
 			    PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+		OPT_BOOL(0, "infer-cover-subject", &infer_cover_subject,
+			    N_("infer a cover letter subject from the branch description")),
 		{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
 			    N_("Use [<prefix>] instead of [PATCH]"),
 			    PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1617,8 +1631,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	extra_to.strdup_strings = 1;
 	extra_cc.strdup_strings = 1;
 	init_log_defaults();
-	git_config(git_format_config, NULL);
 	repo_init_revisions(the_repository, &rev, prefix);
+	git_config(git_format_config, NULL);
 	rev.commit_format = CMIT_FMT_EMAIL;
 	rev.expand_tabs_in_log_default = 0;
 	rev.verbose_header = 1;
@@ -1893,7 +1907,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		if (thread)
 			gen_message_id(&rev, "cover");
 		make_cover_letter(&rev, use_stdout,
-				  origin, nr, list, branch_name, quiet);
+				  origin, nr, list, branch_name, infer_cover_subject, quiet);
 		print_bases(&bases, rev.diffopt.file);
 		print_signature(rev.diffopt.file);
 		total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 3423f974bc..4cb6b9edfa 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1469,6 +1469,39 @@ test_expect_success 'format patch ignores color.ui' '
 	test_cmp expect actual
 '
 
+test_expect_success 'cover letter with config subject' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.inferCoverSubject true &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	grep "^body" actual
+'
+
+test_expect_success 'cover letter with command-line subject' '
+	test_config branch.rebuild-1.description "command-line subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --infer-cover-subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] command-line subject$" actual &&
+	grep "^body" actual
+'
+
+test_expect_success 'cover letter with command-line --no-infer-cover-subject overrides config' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.inferCoverSubject true &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --no-infer-cover-subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	grep "^config subject" actual &&
+	grep "^body" actual
+'
+
 test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 4/8] format-patch: move extra_headers logic later
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
                         ` (2 preceding siblings ...)
  2019-06-14 21:56       ` [RESEND PATCH v3 3/8] format-patch: infer cover letter from branch description Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 5/8] string-list: create string_list_append_all Denton Liu
                         ` (3 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we need to perform the addition of To: and Cc:
to extra_headers after the branch_name logic. Simply transpose this
logic later in the function so that this happens. (This patch is best
viewed with `git diff --color-moved`.)

Note that this logic only depends on `git_config` and
`repo_init_revisions` and is depended on by the patch creation logic
which is directly below it so this move is effectively a no-op as no
dependencies being reordered.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/builtin/log.c b/builtin/log.c
index 617f074d60..3f97f344df 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1667,35 +1667,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		rev.subject_prefix = strbuf_detach(&sprefix, NULL);
 	}
 
-	for (i = 0; i < extra_hdr.nr; i++) {
-		strbuf_addstr(&buf, extra_hdr.items[i].string);
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_to.nr)
-		strbuf_addstr(&buf, "To: ");
-	for (i = 0; i < extra_to.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_to.items[i].string);
-		if (i + 1 < extra_to.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	if (extra_cc.nr)
-		strbuf_addstr(&buf, "Cc: ");
-	for (i = 0; i < extra_cc.nr; i++) {
-		if (i)
-			strbuf_addstr(&buf, "    ");
-		strbuf_addstr(&buf, extra_cc.items[i].string);
-		if (i + 1 < extra_cc.nr)
-			strbuf_addch(&buf, ',');
-		strbuf_addch(&buf, '\n');
-	}
-
-	rev.extra_headers = strbuf_detach(&buf, NULL);
-
 	if (from) {
 		if (split_ident_line(&rev.from_ident, from, strlen(from)))
 			die(_("invalid ident line: %s"), from);
@@ -1798,6 +1769,35 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	for (i = 0; i < extra_hdr.nr; i++) {
+		strbuf_addstr(&buf, extra_hdr.items[i].string);
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_to.nr)
+		strbuf_addstr(&buf, "To: ");
+	for (i = 0; i < extra_to.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_to.items[i].string);
+		if (i + 1 < extra_to.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	if (extra_cc.nr)
+		strbuf_addstr(&buf, "Cc: ");
+	for (i = 0; i < extra_cc.nr; i++) {
+		if (i)
+			strbuf_addstr(&buf, "    ");
+		strbuf_addstr(&buf, extra_cc.items[i].string);
+		if (i + 1 < extra_cc.nr)
+			strbuf_addch(&buf, ',');
+		strbuf_addch(&buf, '\n');
+	}
+
+	rev.extra_headers = strbuf_detach(&buf, NULL);
+
 	/*
 	 * We cannot move this anywhere earlier because we do want to
 	 * know if --root was given explicitly from the command line.
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 5/8] string-list: create string_list_append_all
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
                         ` (3 preceding siblings ...)
  2019-06-14 21:56       ` [RESEND PATCH v3 4/8] format-patch: move extra_headers logic later Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers Denton Liu
                         ` (2 subsequent siblings)
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we'll need to take one string_list and append it to
the end of another. Create the `string_list_append_all` function which
does this.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)

diff --git a/string-list.c b/string-list.c
index a917955fbd..e63d58fbd2 100644
--- a/string-list.c
+++ b/string-list.c
@@ -215,6 +215,15 @@ struct string_list_item *string_list_append(struct string_list *list,
 			list->strdup_strings ? xstrdup(string) : (char *)string);
 }
 
+void string_list_append_all(struct string_list *list,
+			    const struct string_list *append_list)
+{
+	struct string_list_item *item;
+	ALLOC_GROW(list->items, list->nr + append_list->nr, list->alloc);
+	for_each_string_list_item(item, append_list)
+		string_list_append(list, item->string);
+}
+
 /*
  * Encapsulate the compare function pointer because ISO C99 forbids
  * casting from void * to a function pointer and vice versa.
diff --git a/string-list.h b/string-list.h
index f964399949..8227e00e6a 100644
--- a/string-list.h
+++ b/string-list.h
@@ -208,6 +208,13 @@ struct string_list_item *string_list_append(struct string_list *list, const char
  */
 struct string_list_item *string_list_append_nodup(struct string_list *list, char *string);
 
+/**
+ * Add all strings in append_list to list.  If list->strdup_string is
+ * set, then each string is copied; otherwise the new string_list_entry
+ * refers to the entry in the append_list.
+ */
+void string_list_append_all(struct string_list *list, const struct string_list *append_list);
+
 /**
  * Sort the list's entries by string value in `strcmp()` order.
  */
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
                         ` (4 preceding siblings ...)
  2019-06-14 21:56       ` [RESEND PATCH v3 5/8] string-list: create string_list_append_all Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 7/8] format-patch: move output_directory logic later Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 8/8] format-patch: read branch-specific output directory Denton Liu
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

If a user wishes to keep track of whom to Cc: on individual patchsets,
they must manually keep track of each recipient and fill it in with the
`--cc` option on git-format-patch each time. However, on the Git mailing
list, Cc:'s are typically never dropped. As a result, it would be nice
to have a method to keep track of recipients on a per-branch basis.

Currently, git-format-patch gets its To: headers from the `--to` options
and the `format.to` config variable. The Cc: header is derived
similarly.

In addition to the above, read To: and Cc: headers from
`format.<branch-name>.to` and `format.<branch-name>.cc` so that users
can have branch-specific configuration options.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |   7 +-
 Documentation/git-format-patch.txt |  12 +++-
 builtin/log.c                      |  63 +++++++++++++++--
 t/t4014-format-patch.sh            | 108 +++++++++++++++++++++++------
 4 files changed, 162 insertions(+), 28 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index c8b28fe47f..95e255347a 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -28,9 +28,12 @@ format.headers::
 
 format.to::
 format.cc::
+format.<branch-name>.to::
+format.<branch-name>.cc::
 	Additional recipients to include in a patch to be submitted
-	by mail.  See the --to and --cc options in
-	linkgit:git-format-patch[1].
+	by mail.  For the <branch-name> options, the recipients are only
+	included if patches are generated for the given <branch-name>.
+	See the --to and --cc options in linkgit:git-format-patch[1].
 
 format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index f54aecf4bf..76e61b746a 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -203,7 +203,7 @@ the description up to and excluding the first blank line.
 	Add a `To:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.to` configurations.
+	`format.to` and `format.<branch-name>.to` configurations.
 	The negated form `--no-to` discards all `To:` headers added so
 	far (from config or command line).
 
@@ -211,7 +211,7 @@ the description up to and excluding the first blank line.
 	Add a `Cc:` header to the email headers. This is in addition
 	to any configured headers, and may be used multiple times. The
 	emails given will be used along with any emails given by
-	`format.cc` configurations.
+	`format.cc` and `format.<branch-name>.cc` configurations.
 	The negated form `--no-cc` discards all `Cc:` headers added so
 	far (from config or command line).
 
@@ -358,6 +358,14 @@ with configuration variables.
 	inferCoverSubject = true
 ------------
 
+In addition, for a specific branch, you can specify a custom cover
+letter subject, and add additional "To:" or "Cc:" headers.
+
+------------
+[format "branch-name"]
+	to = <email>
+	cc = <email>
+------------
 
 DISCUSSION
 ----------
diff --git a/builtin/log.c b/builtin/log.c
index 3f97f344df..cb9ccdc59c 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -738,6 +738,8 @@ static char *default_attach = NULL;
 static struct string_list extra_hdr = STRING_LIST_INIT_NODUP;
 static struct string_list extra_to = STRING_LIST_INIT_NODUP;
 static struct string_list extra_cc = STRING_LIST_INIT_NODUP;
+int to_cleared;
+int cc_cleared;
 
 static void add_header(const char *value)
 {
@@ -1021,6 +1023,55 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static void add_branch_headers(struct rev_info *rev, const char *branch_name)
+{
+	struct strbuf buf = STRBUF_INIT;
+	const struct string_list *values;
+
+	if (!branch_name)
+		branch_name = find_branch_name(rev);
+
+	if (!branch_name || !*branch_name)
+		return;
+
+	/*
+	 * HACK: We only use branch-specific recipients iff the list has not
+	 * been cleared by an earlier --no-{to,cc} option on the command-line.
+	 *
+	 * When we get format.{to,cc} options, they can be cleared by
+	 * --no-{to,cc} options since the `git_config` call comes before the
+	 *  `parse_options` call.
+	 *
+	 *  However, in the case of branch.<name>.{to,cc}, this function needs
+	 *  to be called after `setup_revisions`, which must be called after
+	 *  `parse_options`. However, in order for the --no-{to,cc} logic to
+	 *  clear the extra_{to,cc} string_list, this function should actually
+	 *  be called _before_ `parse_options`. As a result, we have a circular
+	 *  dependency.
+	 *
+	 *  The {to,cc}_cleared flag lets us workaround this by just no
+	 *  including branch-specific recipients iff --no-{to,cc} has been
+	 *  specified on the command-line.
+	 */
+
+	if (!to_cleared) {
+		strbuf_addf(&buf, "format.%s.to", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_to, values);
+	}
+
+	if (!cc_cleared) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "format.%s.cc", branch_name);
+		values = git_config_get_value_multi(buf.buf);
+		if (values)
+			string_list_append_all(&extra_cc, values);
+	}
+
+	strbuf_release(&buf);
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
@@ -1293,18 +1344,20 @@ static int header_callback(const struct option *opt, const char *arg, int unset)
 
 static int to_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		to_cleared = 1;
 		string_list_clear(&extra_to, 0);
-	else
+	} else
 		string_list_append(&extra_to, arg);
 	return 0;
 }
 
 static int cc_callback(const struct option *opt, const char *arg, int unset)
 {
-	if (unset)
+	if (unset) {
+		cc_cleared = 1;
 		string_list_clear(&extra_cc, 0);
-	else
+	} else
 		string_list_append(&extra_cc, arg);
 	return 0;
 }
@@ -1769,6 +1822,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	add_branch_headers(&rev, branch_name);
+
 	for (i = 0; i < extra_hdr.nr; i++) {
 		strbuf_addstr(&buf, extra_hdr.items[i].string);
 		strbuf_addch(&buf, '\n');
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 4cb6b9edfa..23c467e95b 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -234,6 +234,65 @@ test_expect_failure 'configuration To: header (rfc2047)' '
 	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs9
 '
 
+test_expect_success 'branch-specific configuration To: header (ascii)' '
+
+	test_unconfig format.to &&
+	git config format.side.to "R E Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc822)' '
+
+	git config format.side.to "R. E. Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: \"R. E. Cipient\" <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_failure 'branch-specific configuration To: header (rfc2047)' '
+
+	git config format.side.to "R Ä Cipient <rcipient@example.com>" &&
+	git format-patch --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <rcipient@example.com>\$" hdrs10
+'
+
+test_expect_success 'all recipients included from all sources' '
+
+	git config format.to "Format To1 <formatto1@example.com>" &&
+	git config --add format.to "Format To2 <formatto2@example.com>" &&
+	git config format.cc "Format Cc1 <formatcc1@example.com>" &&
+	git config --add format.cc "Format Cc2 <formatcc2@example.com>" &&
+	git config format.side.to "Branch To1 <branchto1@example.com>" &&
+	git config --add format.side.to "Branch To2 <branchto2@example.com>" &&
+	git config format.side.cc "Branch Cc1 <branchcc1@example.com>" &&
+	git config --add format.side.cc "Branch Cc2 <branchcc2@example.com>" &&
+	cat <<-\EOF >expect &&
+	To: Format To1 <formatto1@example.com>,
+	    Format To2 <formatto2@example.com>,
+	    Command-line To1 <commandlineto1@example.com>,
+	    Command-line To2 <commandlineto2@example.com>,
+	    Branch To1 <branchto1@example.com>,
+	    Branch To2 <branchto2@example.com>
+	Cc: Format Cc1 <formatcc1@example.com>,
+	    Format Cc2 <formatcc2@example.com>,
+	    Command-line Cc1 <commandlinecc1@example.com>,
+	    Command-line Cc2 <commandlinecc2@example.com>,
+	    Branch Cc1 <branchcc1@example.com>,
+	    Branch Cc2 <branchcc2@example.com>
+
+	EOF
+	git format-patch --stdout \
+		--to="Command-line To1 <commandlineto1@example.com>" \
+		--to="Command-line To2 <commandlineto2@example.com>" \
+		--cc="Command-line Cc1 <commandlinecc1@example.com>" \
+		--cc="Command-line Cc2 <commandlinecc2@example.com>" \
+		master..side | sed -ne "/^To:/,/^$/p;/^$/q" >patch10 &&
+	test_cmp expect patch10
+'
+
 # check_patch <patch>: Verify that <patch> looks like a half-sane
 # patch email to avoid a false positive with !grep
 check_patch () {
@@ -286,42 +345,51 @@ test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <rcipient@example.com>" &&
-	git format-patch --no-to --stdout master..side >patch10 &&
-	sed -e "/^\$/q" patch10 >hdrs10 &&
-	check_patch hdrs10 &&
-	! grep "^To: R E Cipient <rcipient@example.com>\$" hdrs10
+	git config --replace-all format.side.to \
+		"B R Anch <branch@example.com>" &&
+	git format-patch --no-to --stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "R E Cipient <rcipient@example.com>" hdrs11 &&
+	! grep "B R Anch <branch@example.com>" hdrs11
 '
 
 test_expect_success '--no-to and --to replaces config.to' '
 
 	git config --replace-all format.to \
 		"Someone <someone@out.there>" &&
+	git config --replace-all format.side.to \
+		"B R Anch2 <branch2@example.com>" &&
 	git format-patch --no-to --to="Someone Else <else@out.there>" \
-		--stdout master..side >patch11 &&
-	sed -e "/^\$/q" patch11 >hdrs11 &&
-	check_patch hdrs11 &&
-	! grep "^To: Someone <someone@out.there>\$" hdrs11 &&
-	grep "^To: Someone Else <else@out.there>\$" hdrs11
+		--stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "Someone <someone@out.there>" hdrs12 &&
+	! grep "B R Anch2 <branch2@example.com>" hdrs12 &&
+	grep "^To: Someone Else <else@out.there>\$" hdrs12
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <rcipient@example.com>" &&
-	git format-patch --no-cc --stdout master..side >patch12 &&
-	sed -e "/^\$/q" patch12 >hdrs12 &&
-	check_patch hdrs12 &&
-	! grep "^Cc: C E Cipient <rcipient@example.com>\$" hdrs12
+	git config --replace-all format.side.cc \
+		"B R Anch3 <branch3@example.com>" &&
+	git format-patch --no-cc --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "C E Cipient <rcipient@example.com>" hdrs13 &&
+	! grep "B R Anch3 <branch3@example.com>" hdrs13
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <rcipient@example.com>" &&
-	git format-patch --no-add-header --stdout master..side >patch13 &&
-	sed -e "/^\$/q" patch13 >hdrs13 &&
-	check_patch hdrs13 &&
-	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs13
+	git format-patch --no-add-header --stdout master..side >patch14 &&
+	sed -e "/^\$/q" patch14 >hdrs14 &&
+	check_patch hdrs14 &&
+	! grep "^Header1: B E Cipient <rcipient@example.com>\$" hdrs14
 '
 
 test_expect_success 'multiple files' '
@@ -963,7 +1031,7 @@ test_expect_success 'format-patch wraps extremely long subject (ascii)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1002,7 +1070,7 @@ test_expect_success 'format-patch wraps extremely long subject (rfc2047)' '
 	git add file &&
 	git commit -m "$M512" &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^Subject/p; /^ /p; /^$/q" <patch >subject &&
+	sed -n "/^Subject/p; /^ /p; /^Header1:/q" <patch >subject &&
 	test_cmp expect subject
 '
 
@@ -1011,7 +1079,7 @@ check_author() {
 	git add file &&
 	GIT_AUTHOR_NAME=$1 git commit -m author-check &&
 	git format-patch --stdout -1 >patch &&
-	sed -n "/^From: /p; /^ /p; /^$/q" <patch >actual &&
+	sed -n "/^From: /p; /^ /p; /^Date:/q" <patch >actual &&
 	test_cmp expect actual
 }
 
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 7/8] format-patch: move output_directory logic later
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
                         ` (5 preceding siblings ...)
  2019-06-14 21:56       ` [RESEND PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  2019-06-14 21:56       ` [RESEND PATCH v3 8/8] format-patch: read branch-specific output directory Denton Liu
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

In a future patch, we need to create the output_directory after the
branch_name logic. Simply transpose this logic later in the function so
that this happens. (This patch is best viewed with `git diff
--color-moved`.)

Note that this logic only depends on `git_config` and the parseopt logic
and is depended on by the patch creation logic which is directly below
it so this move is effectively a no-op as no dependencies being
reordered.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/git-format-patch.txt |  4 ++--
 builtin/log.c                      | 36 +++++++++++++++---------------
 2 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 76e61b746a..707b4bdc6b 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -358,8 +358,8 @@ with configuration variables.
 	inferCoverSubject = true
 ------------
 
-In addition, for a specific branch, you can specify a custom cover
-letter subject, and add additional "To:" or "Cc:" headers.
+In addition, for a specific branch, you can add additional "To:" or
+"Cc:" headers.
 
 ------------
 [format "branch-name"]
diff --git a/builtin/log.c b/builtin/log.c
index cb9ccdc59c..97980881ec 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1771,24 +1771,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	if (rev.show_notes)
 		init_display_notes(&rev.notes_opt);
 
-	if (!output_directory && !use_stdout)
-		output_directory = config_output_directory;
-
-	if (!use_stdout)
-		output_directory = set_outdir(prefix, output_directory);
-	else
-		setup_pager();
-
-	if (output_directory) {
-		if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
-			rev.diffopt.use_color = GIT_COLOR_NEVER;
-		if (use_stdout)
-			die(_("standard output, or directory, which one?"));
-		if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
-			die_errno(_("could not create directory '%s'"),
-				  output_directory);
-	}
-
 	if (rev.pending.nr == 1) {
 		int check_head = 0;
 
@@ -1822,6 +1804,24 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
+	if (!output_directory && !use_stdout)
+		output_directory = config_output_directory;
+
+	if (!use_stdout)
+		output_directory = set_outdir(prefix, output_directory);
+	else
+		setup_pager();
+
+	if (output_directory) {
+		if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
+			rev.diffopt.use_color = GIT_COLOR_NEVER;
+		if (use_stdout)
+			die(_("standard output, or directory, which one?"));
+		if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
+			die_errno(_("could not create directory '%s'"),
+				  output_directory);
+	}
+
 	add_branch_headers(&rev, branch_name);
 
 	for (i = 0; i < extra_hdr.nr; i++) {
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [RESEND PATCH v3 8/8] format-patch: read branch-specific output directory
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
                         ` (6 preceding siblings ...)
  2019-06-14 21:56       ` [RESEND PATCH v3 7/8] format-patch: move output_directory logic later Denton Liu
@ 2019-06-14 21:56       ` Denton Liu
  7 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-06-14 21:56 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano

If a user wishes to have a per-branch output directory for patches, they
must manually specify this on the command-line with `-o` for each
invocation of format-patch. However, this can be cumbersome for a user
to keep track of.

Read `format.<branch-name>.outputDirectory` to give a branch-specific
output directory that would override `format.outputDirectory`.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 Documentation/config/format.txt    |  5 ++++-
 Documentation/git-format-patch.txt |  3 ++-
 builtin/log.c                      | 26 ++++++++++++++++++++++++--
 t/t4014-format-patch.sh            | 18 +++++++++++++++---
 4 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index 95e255347a..df67a83183 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -87,8 +87,11 @@ format.coverLetter::
 	generate a cover-letter only when there's more than one patch.
 
 format.outputDirectory::
+format.<branch-name>.outputDirectory::
 	Set a custom directory to store the resulting files instead of the
-	current working directory.
+	current working directory. If patches are being generated for
+	<branch-name>, the latter option takes priority if it exists,
+	otherwise we will fallback to the former.
 
 format.useAutoBase::
 	A boolean value which lets you enable the `--base=auto` option of
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 707b4bdc6b..346f1229d8 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -359,12 +359,13 @@ with configuration variables.
 ------------
 
 In addition, for a specific branch, you can add additional "To:" or
-"Cc:" headers.
+"Cc:" headers and change the patch output directory.
 
 ------------
 [format "branch-name"]
 	to = <email>
 	cc = <email>
+	outputDirectory = <directory>
 ------------
 
 DISCUSSION
diff --git a/builtin/log.c b/builtin/log.c
index 97980881ec..a1fe8b994a 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1023,6 +1023,24 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static const char *get_branch_output_dir(struct rev_info *rev, const char *branch_name)
+{
+	struct strbuf buf = STRBUF_INIT;
+	const char *output_directory;
+
+	if (!branch_name)
+		branch_name = find_branch_name(rev);
+
+	if (!branch_name || !*branch_name)
+		return NULL;
+
+	strbuf_addf(&buf, "format.%s.outputdirectory", branch_name);
+	git_config_get_string_const(buf.buf, &output_directory);
+	strbuf_release(&buf);
+
+	return output_directory;
+}
+
 static void add_branch_headers(struct rev_info *rev, const char *branch_name)
 {
 	struct strbuf buf = STRBUF_INIT;
@@ -1804,8 +1822,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (!output_directory && !use_stdout)
-		output_directory = config_output_directory;
+	if (!use_stdout) {
+		if (!output_directory)
+			output_directory = get_branch_output_dir(&rev, branch_name);
+		if (!output_directory)
+			output_directory = config_output_directory;
+	}
 
 	if (!use_stdout)
 		output_directory = set_outdir(prefix, output_directory);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 23c467e95b..147934922c 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1667,12 +1667,24 @@ test_expect_success 'format-patch format.outputDirectory option' '
 	test_line_count = $(ls patches | wc -l) list
 '
 
-test_expect_success 'format-patch -o overrides format.outputDirectory' '
+test_expect_success 'format-patch format.side.outputDirectory option overrides format.outputDirectory' '
 	test_config format.outputDirectory patches &&
-	rm -fr patches patchset &&
+	test_config format.side.outputDirectory sidepatches &&
+	rm -fr patches sidepatches &&
+	git format-patch master..side &&
+	git rev-list master..side >list &&
+	test_path_is_missing patches &&
+	test_line_count = $(ls sidepatches | wc -l) list
+'
+
+test_expect_success 'format-patch -o overrides format.outputDirectory and format.side.outputDirectory' '
+	test_config format.outputDirectory patches &&
+	test_config format.side.outputDirectory sidepatches &&
+	rm -fr patches sidepatches patchset &&
 	git format-patch master..side -o patchset &&
 	test_path_is_missing patches &&
-	test_path_is_dir patchset
+	test_path_is_missing sidepatches &&
+	test_line_count = $(ls patchset | wc -l) list
 '
 
 test_expect_success 'format-patch --base' '
-- 
2.22.0.355.g3bfa262345


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup)
  2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
                       ` (9 preceding siblings ...)
  2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
@ 2019-10-15  9:06     ` Denton Liu
  2019-10-15  9:06       ` [PATCH v6 1/3] format-patch: replace erroneous and condition Denton Liu
                         ` (3 more replies)
  10 siblings, 4 replies; 59+ messages in thread
From: Denton Liu @ 2019-10-15  9:06 UTC (permalink / raw)
  To: Git Mailing List
  Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano,
	Eric Sunshine, Johannes Sixt, Philip Oakley

Currently, format-patch only puts "*** SUBJECT HERE ***" when a cover
letter is generated. However, it is already smart enough to be able to
populate the cover letter with the branch description so there's no
reason why it cannot populate the subject as well.

Teach format-patch the `--infer-cover-subject` option and corresponding
`format.inferCoverSubject` configuration option which will read the
subject from the branch description using the same rules as for a commit
message (that is, it will expect a subject line followed by a blank
line).

This was based on patches 1-3 of an earlier patchset I sent[1].

Changes since v5:

* Remove speculation in log message of 1/3

* Rename pp_from_desc() to prepare_cover_text()

Changes since v4:

* Modify 1/3 to more closely reflect intent of the original author

* Incorporate Junio's suggestions into the documentation

* Extract branch desc logic into pp_from_desc()

* Fix broken tests

Changes since v3:

* Change --infer-cover-subject to --cover-from-description

* No more test cleanup patches (they were merged in
  'dl/format-patch-doc-test-cleanup')

Changes since v2:

* Break 1/4 into many different patches (one per paragraph of the
  original patch)

* Incorporate Eric's documentation/commit message suggestions

Changes since v1:

* Incorporate Eric's suggestions for cleanup in all patches

* Add patch 3/4 to make it clear what is the default value for
  format.coverLetter (since format.inferCoverSubject was borrowed from
  this config but it also did not state what the default value was)

* In 1/4, rename all instances of "expected" to "expect"

[1]: https://public-inbox.org/git/cover.1558492582.git.liu.denton@gmail.com/

Denton Liu (3):
  format-patch: replace erroneous and condition
  format-patch: use enum variables
  format-patch: teach --cover-from-description option

 Documentation/config/format.txt    |   6 +
 Documentation/git-format-patch.txt |  22 ++++
 builtin/log.c                      | 125 +++++++++++++++------
 t/t4014-format-patch.sh            | 172 +++++++++++++++++++++++++++++
 t/t9902-completion.sh              |   5 +-
 5 files changed, 296 insertions(+), 34 deletions(-)

Range-diff against v5:
1:  f89e56545b ! 1:  9d41068e73 format-patch: change erroneous and condition
    @@ Metadata
     Author: Denton Liu <liu.denton@gmail.com>
     
      ## Commit message ##
    -    format-patch: change erroneous and condition
    +    format-patch: replace erroneous and condition
     
         Commit 30984ed2e9 (format-patch: support deep threading, 2009-02-19),
         introduced the following lines:
    @@ Commit message
                 thread = git_config_bool(var, value) && THREAD_SHALLOW;
     
         Since git_config_bool() returns a bool, the trailing `&& THREAD_SHALLOW`
    -    is a no-op.
    -
    -    In Python, `x and y` is equivalent to `y if x else x`[1]. Since this
    -    seems to be a Python-ism that's mistakenly leaked into our code, convert
    -    this to the equivalent C expression.
    -
    -    [1]: https://docs.python.org/3/reference/expressions.html#boolean-operations
    -
    -    Signed-off-by: Denton Liu <liu.denton@gmail.com>
    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    is a no-op. Replace this errorneous and condition with a ternary
    +    statement so that it is clear what the configured value is when a
    +    boolean is given.
     
      ## builtin/log.c ##
     @@ builtin/log.c: static int git_format_config(const char *var, const char *value, void *cb)
2:  1fe780b5a4 = 2:  821e706bae format-patch: use enum variables
3:  d5cd34a44b ! 3:  42b4a60fd2 format-patch: teach --cover-from-description option
    @@ builtin/log.c: static void show_diffstat(struct rev_info *rev,
      	fprintf(rev->diffopt.file, "\n");
      }
      
    -+static void pp_from_desc(struct pretty_print_context *pp,
    -+			 const char *branch_name,
    -+			 struct strbuf *sb,
    -+			 const char *encoding,
    -+			 int need_8bit_cte)
    ++static void prepare_cover_text(struct pretty_print_context *pp,
    ++			       const char *branch_name,
    ++			       struct strbuf *sb,
    ++			       const char *encoding,
    ++			       int need_8bit_cte)
     +{
     +	const char *subject = "*** SUBJECT HERE ***";
     +	const char *body = "*** BLURB HERE ***";
    @@ builtin/log.c: static void make_cover_letter(struct rev_info *rev, int use_stdou
     -	pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
     -	pp_remainder(&pp, &msg, &sb, 0);
     -	add_branch_description(&sb, branch_name);
    -+	pp_from_desc(&pp, branch_name, &sb, encoding, need_8bit_cte);
    ++	prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte);
      	fprintf(rev->diffopt.file, "%s\n", sb.buf);
      
      	strbuf_release(&sb);
-- 
2.23.0.17.gd2208d9060


^ permalink raw reply	[flat|nested] 59+ messages in thread

* [PATCH v6 1/3] format-patch: replace erroneous and condition
  2019-10-15  9:06     ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Denton Liu
@ 2019-10-15  9:06       ` Denton Liu
  2019-10-15  9:06       ` [PATCH v6 2/3] format-patch: use enum variables Denton Liu
                         ` (2 subsequent siblings)
  3 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-10-15  9:06 UTC (permalink / raw)
  To: Git Mailing List
  Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano,
	Eric Sunshine, Johannes Sixt, Philip Oakley

Commit 30984ed2e9 (format-patch: support deep threading, 2009-02-19),
introduced the following lines:

	#define THREAD_SHALLOW 1

	[...]

	thread = git_config_bool(var, value) && THREAD_SHALLOW;

Since git_config_bool() returns a bool, the trailing `&& THREAD_SHALLOW`
is a no-op. Replace this errorneous and condition with a ternary
statement so that it is clear what the configured value is when a
boolean is given.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
---
 builtin/log.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/log.c b/builtin/log.c
index 44b10b3415..351f4ffcfd 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -835,7 +835,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
 			thread = THREAD_SHALLOW;
 			return 0;
 		}
-		thread = git_config_bool(var, value) && THREAD_SHALLOW;
+		thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET;
 		return 0;
 	}
 	if (!strcmp(var, "format.signoff")) {
-- 
2.23.0.17.gd2208d9060


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v6 2/3] format-patch: use enum variables
  2019-10-15  9:06     ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Denton Liu
  2019-10-15  9:06       ` [PATCH v6 1/3] format-patch: replace erroneous and condition Denton Liu
@ 2019-10-15  9:06       ` Denton Liu
  2019-10-15  9:06       ` [PATCH v6 3/3] format-patch: teach --cover-from-description option Denton Liu
  2019-10-16  1:28       ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Junio C Hamano
  3 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-10-15  9:06 UTC (permalink / raw)
  To: Git Mailing List
  Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano,
	Eric Sunshine, Johannes Sixt, Philip Oakley

Before, `thread` and `config_cover_letter` were defined as ints even
though they behaved as enums. Define actual enums and change these
variables to use these new definitions.

Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/log.c | 30 +++++++++++++++++-------------
 1 file changed, 17 insertions(+), 13 deletions(-)

diff --git a/builtin/log.c b/builtin/log.c
index 351f4ffcfd..d212a8305d 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -764,24 +764,28 @@ static void add_header(const char *value)
 	item->string[len] = '\0';
 }
 
-#define THREAD_SHALLOW 1
-#define THREAD_DEEP 2
-static int thread;
+enum cover_setting {
+	COVER_UNSET,
+	COVER_OFF,
+	COVER_ON,
+	COVER_AUTO
+};
+
+enum thread_level {
+	THREAD_UNSET,
+	THREAD_SHALLOW,
+	THREAD_DEEP
+};
+
+static enum thread_level thread;
 static int do_signoff;
 static int base_auto;
 static char *from;
 static const char *signature = git_version_string;
 static const char *signature_file;
-static int config_cover_letter;
+static enum cover_setting config_cover_letter;
 static const char *config_output_directory;
 
-enum {
-	COVER_UNSET,
-	COVER_OFF,
-	COVER_ON,
-	COVER_AUTO
-};
-
 static int git_format_config(const char *var, const char *value, void *cb)
 {
 	struct rev_info *rev = cb;
@@ -1248,9 +1252,9 @@ static int output_directory_callback(const struct option *opt, const char *arg,
 
 static int thread_callback(const struct option *opt, const char *arg, int unset)
 {
-	int *thread = (int *)opt->value;
+	enum thread_level *thread = (enum thread_level *)opt->value;
 	if (unset)
-		*thread = 0;
+		*thread = THREAD_UNSET;
 	else if (!arg || !strcmp(arg, "shallow"))
 		*thread = THREAD_SHALLOW;
 	else if (!strcmp(arg, "deep"))
-- 
2.23.0.17.gd2208d9060


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* [PATCH v6 3/3] format-patch: teach --cover-from-description option
  2019-10-15  9:06     ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Denton Liu
  2019-10-15  9:06       ` [PATCH v6 1/3] format-patch: replace erroneous and condition Denton Liu
  2019-10-15  9:06       ` [PATCH v6 2/3] format-patch: use enum variables Denton Liu
@ 2019-10-15  9:06       ` Denton Liu
  2019-10-16  1:28       ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Junio C Hamano
  3 siblings, 0 replies; 59+ messages in thread
From: Denton Liu @ 2019-10-15  9:06 UTC (permalink / raw)
  To: Git Mailing List
  Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano,
	Eric Sunshine, Johannes Sixt, Philip Oakley

Before, when format-patch generated a cover letter, only the body would
be populated with a branch's description while the subject would be
populated with placeholder text. However, users may want to have the
subject of their cover letter automatically populated in the same way.

Teach format-patch to accept the `--cover-from-description` option and
corresponding `format.coverFromDescription` config, allowing users to
populate different parts of the cover letter (including the subject
now).

Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/config/format.txt    |   6 +
 Documentation/git-format-patch.txt |  22 ++++
 builtin/log.c                      |  95 ++++++++++++----
 t/t4014-format-patch.sh            | 172 +++++++++++++++++++++++++++++
 t/t9902-completion.sh              |   5 +-
 5 files changed, 279 insertions(+), 21 deletions(-)

diff --git a/Documentation/config/format.txt b/Documentation/config/format.txt
index cb629fa769..735dfcf827 100644
--- a/Documentation/config/format.txt
+++ b/Documentation/config/format.txt
@@ -36,6 +36,12 @@ format.subjectPrefix::
 	The default for format-patch is to output files with the '[PATCH]'
 	subject prefix. Use this variable to change that prefix.
 
+format.coverFromDescription::
+	The default mode for format-patch to determine which parts of
+	the cover letter will be populated using the branch's
+	description. See the `--cover-from-description` option in
+	linkgit:git-format-patch[1].
+
 format.signature::
 	The default for format-patch is to output a signature containing
 	the Git version number. Use this variable to change that default.
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 0ac56f4b70..6800e1ab9a 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -19,6 +19,7 @@ SYNOPSIS
 		   [--start-number <n>] [--numbered-files]
 		   [--in-reply-to=<message id>] [--suffix=.<sfx>]
 		   [--ignore-if-in-upstream]
+		   [--cover-from-description=<mode>]
 		   [--rfc] [--subject-prefix=<subject prefix>]
 		   [(--reroll-count|-v) <n>]
 		   [--to=<email>] [--cc=<email>]
@@ -171,6 +172,26 @@ will want to ensure that threading is disabled for `git send-email`.
 	patches being generated, and any patch that matches is
 	ignored.
 
+--cover-from-description=<mode>::
+	Controls which parts of the cover letter will be automatically
+	populated using the branch's description.
++
+If `<mode>` is `message` or `default`, the cover letter subject will be
+populated with placeholder text. The body of the cover letter will be
+populated with the branch's description. This is the default mode when
+no configuration nor command line option is specified.
++
+If `<mode>` is `subject`, the first paragraph of the branch description will
+populate the cover letter subject. The remainder of the description will
+populate the body of the cover letter.
++
+If `<mode>` is `auto`, if the first paragraph of the branch description
+is greater than 100 bytes, then the mode will be `message`, otherwise
+`subject` will be used.
++
+If `<mode>` is `none`, both the cover letter subject and body will be
+populated with placeholder text.
+
 --subject-prefix=<subject prefix>::
 	Instead of the standard '[PATCH]' prefix in the subject
 	line, instead use '[<subject prefix>]'. This
@@ -347,6 +368,7 @@ with configuration variables.
 	signOff = true
 	outputDirectory = <directory>
 	coverLetter = auto
+	coverFromDescription = auto
 ------------
 
 
diff --git a/builtin/log.c b/builtin/log.c
index d212a8305d..04be559bd2 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -37,6 +37,7 @@
 #include "range-diff.h"
 
 #define MAIL_DEFAULT_WRAP 72
+#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -777,6 +778,13 @@ enum thread_level {
 	THREAD_DEEP
 };
 
+enum cover_from_description {
+	COVER_FROM_NONE,
+	COVER_FROM_MESSAGE,
+	COVER_FROM_SUBJECT,
+	COVER_FROM_AUTO
+};
+
 static enum thread_level thread;
 static int do_signoff;
 static int base_auto;
@@ -785,6 +793,23 @@ static const char *signature = git_version_string;
 static const char *signature_file;
 static enum cover_setting config_cover_letter;
 static const char *config_output_directory;
+static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE;
+
+static enum cover_from_description parse_cover_from_description(const char *arg)
+{
+	if (!arg || !strcmp(arg, "default"))
+		return COVER_FROM_MESSAGE;
+	else if (!strcmp(arg, "none"))
+		return COVER_FROM_NONE;
+	else if (!strcmp(arg, "message"))
+		return COVER_FROM_MESSAGE;
+	else if (!strcmp(arg, "subject"))
+		return COVER_FROM_SUBJECT;
+	else if (!strcmp(arg, "auto"))
+		return COVER_FROM_AUTO;
+	else
+		die(_("%s: invalid cover from description mode"), arg);
+}
 
 static int git_format_config(const char *var, const char *value, void *cb)
 {
@@ -891,6 +916,10 @@ static int git_format_config(const char *var, const char *value, void *cb)
 		}
 		return 0;
 	}
+	if (!strcmp(var, "format.coverfromdescription")) {
+		cover_from_description_mode = parse_cover_from_description(value);
+		return 0;
+	}
 
 	return git_log_config(var, value, cb);
 }
@@ -997,20 +1026,6 @@ static void print_signature(FILE *file)
 	putc('\n', file);
 }
 
-static void add_branch_description(struct strbuf *buf, const char *branch_name)
-{
-	struct strbuf desc = STRBUF_INIT;
-	if (!branch_name || !*branch_name)
-		return;
-	read_branch_desc(&desc, branch_name);
-	if (desc.len) {
-		strbuf_addch(buf, '\n');
-		strbuf_addbuf(buf, &desc);
-		strbuf_addch(buf, '\n');
-	}
-	strbuf_release(&desc);
-}
-
 static char *find_branch_name(struct rev_info *rev)
 {
 	int i, positive = -1;
@@ -1057,6 +1072,44 @@ static void show_diffstat(struct rev_info *rev,
 	fprintf(rev->diffopt.file, "\n");
 }
 
+static void prepare_cover_text(struct pretty_print_context *pp,
+			       const char *branch_name,
+			       struct strbuf *sb,
+			       const char *encoding,
+			       int need_8bit_cte)
+{
+	const char *subject = "*** SUBJECT HERE ***";
+	const char *body = "*** BLURB HERE ***";
+	struct strbuf description_sb = STRBUF_INIT;
+	struct strbuf subject_sb = STRBUF_INIT;
+
+	if (cover_from_description_mode == COVER_FROM_NONE)
+		goto do_pp;
+
+	if (branch_name && *branch_name)
+		read_branch_desc(&description_sb, branch_name);
+	if (!description_sb.len)
+		goto do_pp;
+
+	if (cover_from_description_mode == COVER_FROM_SUBJECT ||
+			cover_from_description_mode == COVER_FROM_AUTO)
+		body = format_subject(&subject_sb, description_sb.buf, " ");
+
+	if (cover_from_description_mode == COVER_FROM_MESSAGE ||
+			(cover_from_description_mode == COVER_FROM_AUTO &&
+			 subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN))
+		body = description_sb.buf;
+	else
+		subject = subject_sb.buf;
+
+do_pp:
+	pp_title_line(pp, &subject, sb, encoding, need_8bit_cte);
+	pp_remainder(pp, &body, sb, 0);
+
+	strbuf_release(&description_sb);
+	strbuf_release(&subject_sb);
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      struct commit *origin,
 			      int nr, struct commit **list,
@@ -1064,8 +1117,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 			      int quiet)
 {
 	const char *committer;
-	const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-	const char *msg;
 	struct shortlog log;
 	struct strbuf sb = STRBUF_INIT;
 	int i;
@@ -1095,15 +1146,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
 	if (!branch_name)
 		branch_name = find_branch_name(rev);
 
-	msg = body;
 	pp.fmt = CMIT_FMT_EMAIL;
 	pp.date_mode.type = DATE_RFC2822;
 	pp.rev = rev;
 	pp.print_email_subject = 1;
 	pp_user_info(&pp, NULL, &sb, committer, encoding);
-	pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
-	pp_remainder(&pp, &msg, &sb, 0);
-	add_branch_description(&sb, branch_name);
+	prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte);
 	fprintf(rev->diffopt.file, "%s\n", sb.buf);
 
 	strbuf_release(&sb);
@@ -1545,6 +1593,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	int use_patch_format = 0;
 	int quiet = 0;
 	int reroll_count = -1;
+	char *cover_from_description_arg = NULL;
 	char *branch_name = NULL;
 	char *base_commit = NULL;
 	struct base_tree_info bases;
@@ -1581,6 +1630,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "rfc", &rev, NULL,
 			    N_("Use [RFC PATCH] instead of [PATCH]"),
 			    PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+		OPT_STRING(0, "cover-from-description", &cover_from_description_arg,
+			    N_("cover-from-description-mode"),
+			    N_("generate parts of a cover letter based on a branch's description")),
 		{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
 			    N_("Use [<prefix>] instead of [PATCH]"),
 			    PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1672,6 +1724,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 			     PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
 			     PARSE_OPT_KEEP_DASHDASH);
 
+	if (cover_from_description_arg)
+		cover_from_description_mode = parse_cover_from_description(cover_from_description_arg);
+
 	if (0 < reroll_count) {
 		struct strbuf sprefix = STRBUF_INIT;
 		strbuf_addf(&sprefix, "%s v%d",
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 72b09896cf..88db01308a 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1517,6 +1517,178 @@ test_expect_success 'format patch ignores color.ui' '
 	test_cmp expect actual
 '
 
+test_expect_success 'cover letter with invalid --cover-from-description and config' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_must_fail git format-patch --cover-letter --cover-from-description garbage master &&
+	test_config format.coverFromDescription garbage &&
+	test_must_fail git format-patch --cover-letter master
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = default' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription default &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description default' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description default master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = none' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription none &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	! grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description none' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description none master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	! grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = message' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription message &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description message' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description message master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = subject' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription subject &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description subject' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = auto (short subject line)' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription auto &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description auto (short subject line)' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with format.coverFromDescription = auto (long subject line)' '
+	test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
+
+body" &&
+	test_config format.coverFromDescription auto &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with --cover-from-description auto (long subject line)' '
+	test_config branch.rebuild-1.description "this is a really long first line and it is over 100 characters long which is the threshold for long subjects
+
+body" &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description auto master >actual &&
+	grep "^Subject: \[PATCH 0/2\] \*\*\* SUBJECT HERE \*\*\*$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	grep "^this is a really long first line and it is over 100 characters long which is the threshold for long subjects$" actual &&
+	grep "^body$" actual
+'
+
+test_expect_success 'cover letter with command-line --cover-from-description overrides config' '
+	test_config branch.rebuild-1.description "config subject
+
+body" &&
+	test_config format.coverFromDescription none &&
+	git checkout rebuild-1 &&
+	git format-patch --stdout --cover-letter --cover-from-description subject master >actual &&
+	grep "^Subject: \[PATCH 0/2\] config subject$" actual &&
+	! grep "^\*\*\* BLURB HERE \*\*\*$" actual &&
+	! grep "^config subject$" actual &&
+	grep "^body$" actual
+'
+
 test_expect_success 'cover letter using branch description (1)' '
 	git checkout rebuild-1 &&
 	test_config branch.rebuild-1.description hello &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 75512c3403..5187e2ede5 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1548,7 +1548,10 @@ test_expect_success 'complete tree filename with metacharacters' '
 '
 
 test_expect_success PERL 'send-email' '
-	test_completion "git send-email --cov" "--cover-letter " &&
+	test_completion "git send-email --cov" <<-\EOF &&
+	--cover-from-description=Z
+	--cover-letter Z
+	EOF
 	test_completion "git send-email ma" "master "
 '
 
-- 
2.23.0.17.gd2208d9060


^ permalink raw reply related	[flat|nested] 59+ messages in thread

* Re: [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup)
  2019-10-15  9:06     ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Denton Liu
                         ` (2 preceding siblings ...)
  2019-10-15  9:06       ` [PATCH v6 3/3] format-patch: teach --cover-from-description option Denton Liu
@ 2019-10-16  1:28       ` Junio C Hamano
  3 siblings, 0 replies; 59+ messages in thread
From: Junio C Hamano @ 2019-10-16  1:28 UTC (permalink / raw)
  To: Denton Liu
  Cc: Git Mailing List, Ævar Arnfjörð Bjarmason,
	Eric Sunshine, Johannes Sixt, Philip Oakley

Thanks.  I think we are ready for 'next'.

^ permalink raw reply	[flat|nested] 59+ messages in thread

end of thread, other threads:[~2019-10-16  1:28 UTC | newest]

Thread overview: 59+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-05-05 16:24 [PATCH 0/7] teach branch-specific options for format-patch Denton Liu
2019-05-05 16:24 ` [PATCH 1/7] t4014: clean up style Denton Liu
2019-05-05 16:24 ` [PATCH 2/7] Doc: add more detail for git-format-patch Denton Liu
2019-05-05 16:24 ` [PATCH 3/7] branch.c: extract read_branch_config function Denton Liu
2019-05-05 16:24 ` [PATCH 4/7] format-patch: make cover letter subject configurable Denton Liu
2019-05-05 16:24 ` [PATCH 5/7] format-patch: move extra_headers logic later Denton Liu
2019-05-05 16:24 ` [PATCH 6/7] string-list: create string_list_append_all Denton Liu
2019-05-05 16:24 ` [PATCH 7/7] format-patch: read branch-specific To: and Cc: headers Denton Liu
2019-05-07  8:56 ` [PATCH 0/7] teach branch-specific options for format-patch Junio C Hamano
2019-05-07 14:19   ` Denton Liu
2019-05-07 15:05     ` Junio C Hamano
2019-05-07 15:21       ` Denton Liu
2019-05-07 15:46         ` Ævar Arnfjörð Bjarmason
2019-05-08  1:45           ` Junio C Hamano
2019-05-31  2:00             ` [RFC PATCH] config: learn the "onbranch:" includeIf condition Denton Liu
2019-05-31 12:58               ` Johannes Schindelin
2019-05-31 13:16                 ` Denton Liu
2019-05-31 17:23                   ` Johannes Schindelin
2019-05-31 18:44                     ` Denton Liu
2019-05-31 19:33               ` [PATCH v2] " Denton Liu
2019-05-31 20:14                 ` Johannes Schindelin
2019-06-05  8:02                   ` Johannes Schindelin
2019-06-05 10:08                 ` Duy Nguyen
2019-06-05 21:21                 ` [PATCH v3] " Denton Liu
2019-06-06 12:52                   ` Johannes Schindelin
2019-05-17  0:27 ` [PATCH v2 0/6] teach branch-specific options for format-patch Denton Liu
2019-05-17  0:27   ` [PATCH v2 1/6] t4014: clean up style Denton Liu
2019-05-17  0:27   ` [PATCH v2 2/6] Doc: add more detail for git-format-patch Denton Liu
2019-05-17  0:27   ` [PATCH v2 3/6] format-patch: make cover letter subject configurable Denton Liu
2019-05-17  0:27   ` [PATCH v2 4/6] format-patch: move extra_headers logic later Denton Liu
2019-05-17  0:27   ` [PATCH v2 5/6] string-list: create string_list_append_all Denton Liu
2019-05-17  0:27   ` [PATCH v2 6/6] format-patch: read branch-specific To: and Cc: headers Denton Liu
2019-05-17  4:12   ` [PATCH v2 0/6] teach branch-specific options for format-patch Junio C Hamano
2019-05-17  7:25     ` Denton Liu
2019-05-17 16:54       ` Denton Liu
2019-05-22  2:44   ` [PATCH v3 0/8] " Denton Liu
2019-05-22  2:44     ` [PATCH v3 1/8] t4014: clean up style Denton Liu
2019-05-22  2:44     ` [PATCH v3 2/8] Doc: add more detail for git-format-patch Denton Liu
2019-05-22  2:44     ` [PATCH v3 3/8] format-patch: infer cover letter from branch description Denton Liu
2019-05-22  2:44     ` [PATCH v3 4/8] format-patch: move extra_headers logic later Denton Liu
2019-05-22  2:44     ` [PATCH v3 5/8] string-list: create string_list_append_all Denton Liu
2019-05-22  2:44     ` [PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers Denton Liu
2019-05-22  2:44     ` [PATCH v3 7/8] format-patch: move output_directory logic later Denton Liu
2019-05-22  2:44     ` [PATCH v3 8/8] format-patch: read branch-specific output directory Denton Liu
2019-05-31  0:30     ` [PATCH v3 0/8] teach branch-specific options for format-patch Denton Liu
2019-06-14 21:56     ` [RESEND PATCH " Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 1/8] t4014: clean up style Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 2/8] Doc: add more detail for git-format-patch Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 3/8] format-patch: infer cover letter from branch description Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 4/8] format-patch: move extra_headers logic later Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 5/8] string-list: create string_list_append_all Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 6/8] format-patch: read branch-specific To: and Cc: headers Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 7/8] format-patch: move output_directory logic later Denton Liu
2019-06-14 21:56       ` [RESEND PATCH v3 8/8] format-patch: read branch-specific output directory Denton Liu
2019-10-15  9:06     ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Denton Liu
2019-10-15  9:06       ` [PATCH v6 1/3] format-patch: replace erroneous and condition Denton Liu
2019-10-15  9:06       ` [PATCH v6 2/3] format-patch: use enum variables Denton Liu
2019-10-15  9:06       ` [PATCH v6 3/3] format-patch: teach --cover-from-description option Denton Liu
2019-10-16  1:28       ` [PATCH v6 0/3] format-patch: learn --infer-cover-subject option (also t4014 cleanup) Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).