Skip to contentSkip to conversation

teach branch-specific options for format-patch

PATCH Superseded Denton Liu 5 participants 59 messages May 5, 2019 – Oct 16, 2019 Documentation

lore
Share & tools
lore

Denton Liu

author
#0 lore raw

cover teach branch-specific options for format-patch

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 <[email protected]>" cc = "Duy Nguyen <[email protected]>" cc = "Junio C Hamano <[email protected]>"

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

[1]: https://public-inbox.org/git/[email protected]/

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(-)
Signature · 1 line
2.21.0.1049.geb646f7864
author
#1 lore raw

PATCH 1/7 t4014: clean up style

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.

---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)
t/t4014-format-patch.sh +319 -295
index 909c743c13..d05cd256c7 100755
@@ -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 <[email protected]>
 " &&
-	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 <[email protected]>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "Cc: S E Cipient <[email protected]>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <[email protected]>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "To: S E Cipient <[email protected]>" &&
-	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 <[email protected]>,\$" hdrs4 &&
 	grep "^ *S E Cipient <[email protected]>\$" 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 <[email protected]>" &&
-	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch5
+	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <[email protected]>\$" patch5
+	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <[email protected]>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <[email protected]>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <[email protected]>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch7 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs7 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch8
+	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch8
+	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch8
+	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" 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 <[email protected]>\$" patch
+	! grep "^From: C O Mitter <[email protected]>\$" 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 <[email protected]>\$" 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 <[email protected]>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <[email protected]>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <[email protected]>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <[email protected]>\$" 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 <[email protected]>" &&
 	git format-patch --no-to --to="Someone Else <[email protected]>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <[email protected]>\$" patch11 &&
-	grep "^To: Someone Else <[email protected]>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <[email protected]>\$" hdrs11 &&
+	grep "^To: Someone Else <[email protected]>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <[email protected]>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <[email protected]>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <[email protected]>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <[email protected]>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <[email protected]>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <[email protected]>\$" 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 "[email protected]" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "[email protected]" >patch8 &&
 	grep "^In-Reply-To: <[email protected]>" patch8 &&
 	grep "^References: <[email protected]>" 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 <[email protected]>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <[email protected]>
+	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 <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <[email protected]>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <[email protected]>
+	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
 '
Signature · 1 line
2.21.0.1049.geb646f7864
author
#2 lore raw

PATCH 2/7 Doc: add more detail for git-format-patch

In reply to Denton Liu

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 <>.

---
 Documentation/git-format-patch.txt | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)
Documentation/git-format-patch.txt +11 -6
index 1af85d404f..7b71d4e2ed 100644
@@ -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
Signature · 1 line
2.21.0.1049.geb646f7864
author
#3 lore raw

PATCH 3/7 branch.c: extract read_branch_config function

In reply to Denton Liu

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.

---
 branch.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
branch.c +7 -2
index 28b81a7e02..4b49976924 100644
@@ -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.
Signature · 1 line
2.21.0.1049.geb646f7864
author
#4 lore raw

PATCH 4/7 format-patch: make cover letter subject configurable

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/branch.txt +4 -0
index 019d60ede2..2bff738982 100644
@@ -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.
Documentation/git-format-patch.txt +12 -0
index 7b71d4e2ed..af7883acbe 100644
@@ -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
 ----------
branch.c +5 -0
index 4b49976924..40d30b8fa7 100644
@@ -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.
branch.h +5 -0
index 29c1afa4d0..6a8936bbc8 100644
@@ -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
builtin/log.c +19 -7
index ab859f5904..6f19326aea 100644
@@ -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++;
t/t4014-format-patch.sh +20 -0
index d05cd256c7..1cf4dfbce2 100755
@@ -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 &&
t/t9902-completion.sh +4 -1
index f5e21bf970..0da92179da 100755
@@ -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 "
 '
 
Signature · 1 line
2.21.0.1049.geb646f7864
author
#5 lore raw

PATCH 5/7 format-patch: move extra_headers logic later

In reply to Denton Liu

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.

---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)
builtin/log.c +29 -29
index 6f19326aea..685e319078 100644
@@ -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.
Signature · 1 line
2.21.0.1049.geb646f7864
author
#6 lore raw

PATCH 6/7 string-list: create string_list_append_all

In reply to Denton Liu

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.

---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)
string-list.c +9 -0
index a917955fbd..e63d58fbd2 100644
@@ -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.
string-list.h +7 -0
index 18c718c12c..32e0c4b47f 100644
@@ -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.
  */
Signature · 1 line
2.21.0.1049.geb646f7864
author
#7 lore raw

PATCH 7/7 format-patch: read branch-specific To: and Cc: headers

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/branch.txt +6 -0
index 2bff738982..22b9bf3d0d 100644
@@ -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].
Documentation/git-format-patch.txt +6 -4
index af7883acbe..1c972f683a 100644
@@ -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
builtin/log.c +59 -4

95 lines not on this page · raw message

t/t4014-format-patch.sh +88 -20

157 lines not on this page · raw message

Signature · 1 line
2.21.0.1049.geb646f7864
#8 lore raw
In reply to Denton Liu
Quoted 8 lines · Denton Liu

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.

Quoted 8 lines

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

[branch "submitted/fix-revisions-txt"]
coverSubject = "cleanup revisions.txt" cc = "Andreas Heiduk <[email protected]>" cc = "Duy Nguyen <[email protected]>" cc = "Junio C Hamano <[email protected]>"

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.

author
#9 lore raw

Hi Junio,

Quoted 13 lines · Junio C Hamano
Quoted 11 lines · Denton Liu

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:

Quoted 7 lines
Quoted 5 lines

[branch "submitted/fix-revisions-txt"]
coverSubject = "cleanup revisions.txt" cc = "Andreas Heiduk <[email protected]>" cc = "Duy Nguyen <[email protected]>" cc = "Junio C Hamano <[email protected]>"

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.

Quoted 6 lines

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).

Quoted 5 lines

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

#10 lore raw
Quoted 5 lines · Denton Liu

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.

Quoted 6 lines
Quoted 2 lines

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?

author
#11 lore raw
Quoted 1 line · Junio C Hamano

Denton Liu <[email protected]> writes:

[snip]

Quoted 10 lines
Quoted 4 lines

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.

Quoted 9 lines

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.

#12 lore raw
Quoted 25 lines · Denton Liu
Quoted 1 line · Junio C Hamano

Denton Liu <[email protected]> writes:

[snip]

Quoted 10 lines
Quoted 4 lines

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.

Quoted 9 lines

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?

Quoted 8 lines

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...

#13 lore raw
Quoted 6 lines · Ævar Arnfjörð Bjarmason

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?

author
#14 lore raw

RFC PATCH 1/1 config: learn the "onbranch:" includeIf condition

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.

---

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(+)
Documentation/config.txt +10 -0
index 7e2a6f61f5..3b9fbe1860 100644
@@ -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
 ~~~~~~
 
config.c +16 -0
index 296a6d9cc4..954e84e13a 100644
@@ -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;
t/t1305-config-include.sh +13 -0
index 579a86b7f8..062db08ad9 100755
@@ -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
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
#15 lore raw

Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition

Hi,

Quoted 8 lines · Denton Liu

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.

Quoted 9 lines

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

Quoted 6 lines
	$ 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

author
#16 lore raw

Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition

Hi Johannes,

Quoted 13 lines · Johannes Schindelin

Hi,

Quoted 8 lines · Denton Liu

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.

Quoted 5 lines

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"?

Quoted 13 lines
Quoted 13 lines

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
Quoted 8 lines
Quoted 3 lines

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.

Quoted 3 lines

Ciao,
Dscho

#17 lore raw

Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition

Hi Denton,

Quoted 19 lines · Denton Liu
Quoted 11 lines · Johannes Schindelin
Quoted 8 lines · Denton Liu

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.

Quoted 5 lines

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.

Quoted 6 lines

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.

Quoted 22 lines
Quoted 13 lines
Quoted 13 lines

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
Quoted 8 lines
Quoted 3 lines

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?").

Quoted 7 lines

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

author
#18 lore raw

Re: [RFC PATCH] config: learn the "onbranch:" includeIf condition

Quoted 8 lines · Johannes Schindelin

Hi Denton,

Quoted 4 lines · Denton Liu
Quoted 2 lines · Johannes Schindelin

On Thu, 30 May 2019, Denton Liu wrote:

[...]

Quoted 31 lines
Quoted 23 lines
Quoted 14 lines
Quoted 14 lines

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
Quoted 8 lines
Quoted 3 lines

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?").

Quoted 7 lines

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.

Quoted 5 lines

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.

Quoted 6 lines

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 ;)

Quoted 3 lines

Ciao,
Dscho

author
#19 lore raw

PATCH v2 1/1 config: learn the "onbranch:" includeIf condition

In reply to Denton Liu

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.

---

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(+)
Documentation/config.txt +12 -0
index 7e2a6f61f5..93aa4323c6 100644
@@ -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
 ~~~~~~
 
config.c +21 -0
index 296a6d9cc4..48bb435739 100644
@@ -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;
t/t1305-config-include.sh +33 -0
index 579a86b7f8..05c7def1f2 100755
@@ -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
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
#20 lore raw

Re: [PATCH v2] config: learn the "onbranch:" includeIf condition

Hi Denton,

Quoted 8 lines · Denton Liu

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

Quoted 224 lines

---

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(+)
Documentation/config.txt +12 -0
index 7e2a6f61f5..93aa4323c6 100644
@@ -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
 ~~~~~~
 
config.c +21 -0
index 296a6d9cc4..48bb435739 100644
@@ -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;
t/t1305-config-include.sh +33 -1
index 579a86b7f8..05c7def1f2 100755
@@ -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

#21 lore raw

Re: [PATCH v2] config: learn the "onbranch:" includeIf condition

Hi Denton,

Quoted 7 lines · Johannes Schindelin
Quoted 5 lines · Denton Liu

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.

Quoted 5 lines
Quoted 3 lines

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...

Quoted 11 lines

t/t1305-config-include.sh test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icas · line 314

Quoted 11 lines
t/t1305-config-include.sh +3 -0
index 579a86b7f8..05c7def1f2 100755
@@ -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

Quoted 39 lines
Quoted 37 lines
t/t1305-config-include.sh +30 -1
+		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

#22 lore raw

Re: [PATCH v2] config: learn the "onbranch:" includeIf condition

In reply to Denton Liu
Quoted 9 lines · Denton Liu

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)?

Quoted 5 lines
Documentation/config.txt +0 -0
index 7e2a6f61f5..93aa4323c6 100644

Documentation/config.txt refer to linkgit:gitignore[5] for details. For convenience: · line 152

Quoted 9 lines
Documentation/config.txt +6 -0
@@ -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".

Quoted 4 lines
Documentation/config.txt +0 -0
@@ -264,6 +265,24 @@ static int include_by_gitdir(const struct config_options *opts,
        return ret;
 }
 

Documentation/config.txt static int include_by_gitdir(const struct config_options *opts, · line 277

Quoted 10 lines
Documentation/config.txt +10 -0
+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.

Quoted 7 lines
Documentation/config.txt +7 -0
+               return 0;
+
+       strbuf_add(&pattern, cond, cond_len);
+       ret = !wildmatch(pattern.buf, shortname, WM_PATHNAME);
+       strbuf_release(&pattern);
+       return ret;
+}
Signature · 1 line
Duy
author
#23 lore raw

PATCH v3 1/1 config: learn the "onbranch:" includeIf condition

In reply to Denton Liu

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.

---

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(-)
Documentation/config.txt +19 -0
index 7e2a6f61f5..e3f5bc3396 100644
@@ -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
 ~~~~~~
 
config.c +29 -2
index 296a6d9cc4..ad4166e243 100644
@@ -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;
t/t1305-config-include.sh +39 -0
index 579a86b7f8..9571e366f8 100755
@@ -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
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
#24 lore raw

Re: [PATCH v3] config: learn the "onbranch:" includeIf condition

Hi Denton,

Quoted 12 lines · Denton Liu

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.

---

Quoted 7 lines

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

author
#25 lore raw

v2 cover teach branch-specific options for format-patch

In reply to Denton Liu

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:

Documentation/config/branch.txt +0 -10
index d292210cf6..8f4b3faadd 100644
@@ -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].
Documentation/config/format.txt +10 -2
index dc77941c48..d387451573 100644
@@ -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.
Documentation/git-format-patch.txt +3 -3
index 1c972f683a..4e826010f6 100644
@@ -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>
branch.c +2 -12
index 83cd441790..643694542a 100644
@@ -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.
branch.h +0 -5
index 363a4fae9d..6f38db14e9 100644
@@ -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
builtin/branch.c +13 -3
index d4359b33ac..367e1fc9bc 100644
@@ -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);
 }
 
builtin/log.c +17 -2
index 3adc942b8c..e1d793577d 100644
@@ -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);
t/t3200-branch.sh +7 -1
index e9ad50b66d..9ae3da888e 100755
@@ -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 <[email protected]>" &&
+	git config format.my7.cc "Cc Me <[email protected]>" &&
 	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' '
t/t4014-format-patch.sh +12 -12
index 5d6c85489e..9c527510c3 100755
@@ -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 <[email protected]>" &&
+	git config format.side.to "R E Cipient <[email protected]>" &&
 	git format-patch --stdout master..side >patch10 &&
 	sed -e "/^\$/q" patch10 >hdrs10 &&
 	grep "^To: R E Cipient <[email protected]>\$" 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 <[email protected]>" &&
+	git config format.side.to "R. E. Cipient <[email protected]>" &&
 	git format-patch --stdout master..side >patch10 &&
 	sed -e "/^\$/q" patch10 >hdrs10 &&
 	grep "^To: \"R. E. Cipient\" <[email protected]>\$" 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 <[email protected]>" &&
+	git config format.side.to "R Ä Cipient <[email protected]>" &&
 	git format-patch --stdout master..side >patch10 &&
 	sed -e "/^\$/q" patch10 >hdrs10 &&
 	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" hdrs10
@@ -265,10 +265,10 @@ test_expect_success 'all recipients included from all sources' '
 	git config --add format.to "Format To2 <[email protected]>" &&
 	git config format.cc "Format Cc1 <[email protected]>" &&
 	git config --add format.cc "Format Cc2 <[email protected]>" &&
-	git config branch.side.to "Branch To1 <[email protected]>" &&
-	git config --add branch.side.to "Branch To2 <[email protected]>" &&
-	git config branch.side.cc "Branch Cc1 <[email protected]>" &&
-	git config --add branch.side.cc "Branch Cc2 <[email protected]>" &&
+	git config format.side.to "Branch To1 <[email protected]>" &&
+	git config --add format.side.to "Branch To2 <[email protected]>" &&
+	git config format.side.cc "Branch Cc1 <[email protected]>" &&
+	git config --add format.side.cc "Branch Cc2 <[email protected]>" &&
 	cat <<-\EOF >expect &&
 	To: Format To1 <[email protected]>,
 	    Format To2 <[email protected]>,
@@ -345,7 +345,7 @@ test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <[email protected]>" &&
-	git config --replace-all branch.side.to \
+	git config --replace-all format.side.to \
 		"B R Anch <[email protected]>" &&
 	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 <[email protected]>" &&
-	git config --replace-all branch.side.to \
+	git config --replace-all format.side.to \
 		"B R Anch2 <[email protected]>" &&
 	git format-patch --no-to --to="Someone Else <[email protected]>" \
 		--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 <[email protected]>" &&
-	git config --replace-all branch.side.cc \
+	git config --replace-all format.side.cc \
 		"B R Anch3 <[email protected]>" &&
 	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
Signature · 1 line
2.21.0.1049.geb646f7864
author
#26 lore raw

PATCH v2 1/6 t4014: clean up style

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.

---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)
t/t4014-format-patch.sh +319 -295
index b6e2fdbc44..3423f974bc 100755
@@ -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 <[email protected]>
 " &&
-	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 <[email protected]>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "Cc: S E Cipient <[email protected]>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <[email protected]>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "To: S E Cipient <[email protected]>" &&
-	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 <[email protected]>,\$" hdrs4 &&
 	grep "^ *S E Cipient <[email protected]>\$" 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 <[email protected]>" &&
-	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch5
+	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <[email protected]>\$" patch5
+	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <[email protected]>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <[email protected]>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <[email protected]>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch7 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs7 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch8
+	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch8
+	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch8
+	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" 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 <[email protected]>\$" patch
+	! grep "^From: C O Mitter <[email protected]>\$" 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 <[email protected]>\$" 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 <[email protected]>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <[email protected]>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <[email protected]>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <[email protected]>\$" 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 <[email protected]>" &&
 	git format-patch --no-to --to="Someone Else <[email protected]>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <[email protected]>\$" patch11 &&
-	grep "^To: Someone Else <[email protected]>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <[email protected]>\$" hdrs11 &&
+	grep "^To: Someone Else <[email protected]>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <[email protected]>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <[email protected]>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <[email protected]>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <[email protected]>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <[email protected]>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <[email protected]>\$" 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 "[email protected]" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "[email protected]" >patch8 &&
 	grep "^In-Reply-To: <[email protected]>" patch8 &&
 	grep "^References: <[email protected]>" 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 <[email protected]>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <[email protected]>
+	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 <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <[email protected]>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <[email protected]>
+	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
 '
Signature · 1 line
2.21.0.1049.geb646f7864
author
#27 lore raw

PATCH v2 2/6 Doc: add more detail for git-format-patch

In reply to Denton Liu

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 <>.

---
 Documentation/git-format-patch.txt | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)
Documentation/git-format-patch.txt +11 -6
index 1af85d404f..7b71d4e2ed 100644
@@ -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
Signature · 1 line
2.21.0.1049.geb646f7864
author
#28 lore raw

PATCH v2 3/6 format-patch: make cover letter subject configurable

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +5 -0
index dc77941c48..7d2b3b7331 100644
@@ -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.
Documentation/git-format-patch.txt +12 -0
index 7b71d4e2ed..a43f317877 100644
@@ -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
 ----------
builtin/branch.c +13 -3
index d4359b33ac..367e1fc9bc 100644
@@ -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);
 }
 
builtin/log.c +34 -7

95 lines not on this page · raw message

t/t3200-branch.sh +3 -1
index e9ad50b66d..dad23f7d4c 100755
@@ -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' '
t/t4014-format-patch.sh +20 -0
index 3423f974bc..9d11992e10 100755
@@ -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 &&
t/t9902-completion.sh +4 -1
index 43cf313a1c..a96f1662c7 100755
@@ -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 "
 '
 
Signature · 1 line
2.21.0.1049.geb646f7864
author
#29 lore raw

PATCH v2 4/6 format-patch: move extra_headers logic later

In reply to Denton Liu

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.

---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)
builtin/log.c +29 -29
index 2bbe712e7c..f66a65fe84 100644
@@ -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.
Signature · 1 line
2.21.0.1049.geb646f7864
author
#30 lore raw

PATCH v2 5/6 string-list: create string_list_append_all

In reply to Denton Liu

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.

---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)
string-list.c +9 -0
index a917955fbd..e63d58fbd2 100644
@@ -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.
string-list.h +7 -0
index f964399949..8227e00e6a 100644
@@ -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.
  */
Signature · 1 line
2.21.0.1049.geb646f7864
author
#31 lore raw

PATCH v2 6/6 format-patch: read branch-specific To: and Cc: headers

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +5 -2
index 7d2b3b7331..d387451573 100644
@@ -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]'
Documentation/git-format-patch.txt +6 -4
index a43f317877..4e826010f6 100644
@@ -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
builtin/log.c +59 -4

95 lines not on this page · raw message

t/t3200-branch.sh +4 -0
index dad23f7d4c..9ae3da888e 100755
@@ -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 <[email protected]>" &&
+	git config format.my7.cc "Cc Me <[email protected]>" &&
 	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' '
t/t4014-format-patch.sh +88 -20

157 lines not on this page · raw message

Signature · 1 line
2.21.0.1049.geb646f7864
#32 lore raw
In reply to Denton Liu
Quoted 6 lines · Denton Liu
Documentation/config/format.txt +0 -0
index dc77941c48..d387451573 100644
@@ -28,14 +28,22 @@ format.headers::
 

Documentation/config/format.txt format.headers:: · line 36

Quoted 10 lines
Documentation/config/format.txt +5 -2
 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.

Documentation/config/format.txt format.headers:: · line 45

Quoted 4 lines
Documentation/config/format.txt +4 -0
+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.

Documentation/config/format.txt format.headers:: · line 45

Quoted 5 lines
Documentation/config/format.txt +5 -0
+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).

Documentation/config/format.txt format.headers::

Quoted 13 lines
Documentation/config/format.txt +7 -3
 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?

author
#33 lore raw

Hi Junio,

Quoted 35 lines · Junio C Hamano
Quoted 6 lines · Denton Liu
Documentation/config/format.txt +0 -0
index dc77941c48..d387451573 100644
@@ -28,14 +28,22 @@ format.headers::
 

Documentation/config/format.txt format.headers:: · line 36

Quoted 10 lines
Documentation/config/format.txt +5 -2
 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

Quoted 8 lines

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.

Quoted 6 lines

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.

Quoted 7 lines

Documentation/config/format.txt format.headers:: · line 45

Quoted 4 lines
Documentation/config/format.txt +4 -0
+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.

Quoted 11 lines

Documentation/config/format.txt format.headers:: · line 45

Quoted 6 lines
Documentation/config/format.txt +5 -0
+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.

Quoted 10 lines
Quoted 10 lines
Documentation/config/format.txt +3 -3
 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);
Quoted 9 lines

Documentation/config/format.txt

Quoted 4 lines
Documentation/config/format.txt +4 -0
+		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.

author
#34 lore raw

Hi Junio,

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

Quoted 41 lines · Denton Liu

Hi Junio,

Quoted 38 lines · Junio C Hamano
Quoted 6 lines · Denton Liu
Documentation/config/format.txt +0 -0
index dc77941c48..d387451573 100644
@@ -28,14 +28,22 @@ format.headers::
 

Documentation/config/format.txt format.headers:: · line 36

Quoted 10 lines
Documentation/config/format.txt +5 -2
 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.

Quoted 8 lines
Quoted 5 lines

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.

Quoted 5 lines
Quoted 5 lines

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.

Quoted 57 lines
Quoted 8 lines

Documentation/config/format.txt format.headers:: · line 45

Quoted 5 lines
Documentation/config/format.txt +4 -0
+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.

Quoted 11 lines

Documentation/config/format.txt format.headers:: · line 45

Quoted 6 lines
Documentation/config/format.txt +5 -0
+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.

Quoted 10 lines
Quoted 10 lines
Documentation/config/format.txt +3 -3
 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);
Quoted 9 lines

Documentation/config/format.txt

Quoted 4 lines
Documentation/config/format.txt +4 -0
+		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?

Quoted 5 lines

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.

Quoted 5 lines

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

Thanks for your feedback, Junio.

author
#35 lore raw

v3 cover teach branch-specific options for format-patch

In reply to Denton Liu

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.

@@ -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.

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 <[email protected]>" &&
-+ git config format.my7.cc "Cc Me <[email protected]>" &&
- 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
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#36 lore raw

PATCH v3 1/8 t4014: clean up style

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.

---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)
t/t4014-format-patch.sh +319 -295
index b6e2fdbc44..3423f974bc 100755
@@ -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 <[email protected]>
 " &&
-	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 <[email protected]>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "Cc: S E Cipient <[email protected]>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <[email protected]>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "To: S E Cipient <[email protected]>" &&
-	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 <[email protected]>,\$" hdrs4 &&
 	grep "^ *S E Cipient <[email protected]>\$" 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 <[email protected]>" &&
-	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch5
+	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <[email protected]>\$" patch5
+	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <[email protected]>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <[email protected]>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <[email protected]>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch7 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs7 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch8
+	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch8
+	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch8
+	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" 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 <[email protected]>\$" patch
+	! grep "^From: C O Mitter <[email protected]>\$" 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 <[email protected]>\$" 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 <[email protected]>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <[email protected]>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <[email protected]>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <[email protected]>\$" 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 <[email protected]>" &&
 	git format-patch --no-to --to="Someone Else <[email protected]>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <[email protected]>\$" patch11 &&
-	grep "^To: Someone Else <[email protected]>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <[email protected]>\$" hdrs11 &&
+	grep "^To: Someone Else <[email protected]>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <[email protected]>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <[email protected]>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <[email protected]>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <[email protected]>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <[email protected]>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <[email protected]>\$" 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 "[email protected]" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "[email protected]" >patch8 &&
 	grep "^In-Reply-To: <[email protected]>" patch8 &&
 	grep "^References: <[email protected]>" 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 <[email protected]>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <[email protected]>
+	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 <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <[email protected]>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <[email protected]>
+	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
 '
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#37 lore raw

PATCH v3 2/8 Doc: add more detail for git-format-patch

In reply to Denton Liu

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.

---
 Documentation/git-format-patch.txt | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)
Documentation/git-format-patch.txt +16 -9
index 1af85d404f..4e180bbfbb 100644
@@ -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
 ------------
 
 
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#38 lore raw

PATCH v3 3/8 format-patch: infer cover letter from branch description

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +5 -0
index dc77941c48..c8b28fe47f 100644
@@ -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.
Documentation/git-format-patch.txt +10 -0
index 4e180bbfbb..f54aecf4bf 100644
@@ -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
 ------------
 
 
builtin/log.c +36 -22

119 lines not on this page · raw message

t/t4014-format-patch.sh +33 -0
index 3423f974bc..4cb6b9edfa 100755
@@ -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 &&
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#39 lore raw

PATCH v3 4/8 format-patch: move extra_headers logic later

In reply to Denton Liu

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.

---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)
builtin/log.c +29 -29
index 617f074d60..3f97f344df 100644
@@ -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.
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#40 lore raw

PATCH v3 5/8 string-list: create string_list_append_all

In reply to Denton Liu

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.

---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)
string-list.c +9 -0
index a917955fbd..e63d58fbd2 100644
@@ -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.
string-list.h +7 -0
index f964399949..8227e00e6a 100644
@@ -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.
  */
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#41 lore raw

PATCH v3 6/8 format-patch: read branch-specific To: and Cc: headers

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +5 -2
index c8b28fe47f..95e255347a 100644
@@ -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]'
Documentation/git-format-patch.txt +10 -2
index f54aecf4bf..76e61b746a 100644
@@ -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
 ----------
builtin/log.c +59 -4

95 lines not on this page · raw message

t/t4014-format-patch.sh +88 -20

157 lines not on this page · raw message

Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#42 lore raw

PATCH v3 7/8 format-patch: move output_directory logic later

In reply to Denton Liu

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.

---
 Documentation/git-format-patch.txt |  4 ++--
 builtin/log.c                      | 36 +++++++++++++++---------------
 2 files changed, 20 insertions(+), 20 deletions(-)
Documentation/git-format-patch.txt +2 -2
index 76e61b746a..707b4bdc6b 100644
@@ -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"]
builtin/log.c +18 -18
index cb9ccdc59c..97980881ec 100644
@@ -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++) {
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#43 lore raw

PATCH v3 8/8 format-patch: read branch-specific output directory

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +4 -1
index 95e255347a..df67a83183 100644
@@ -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
Documentation/git-format-patch.txt +2 -1
index 707b4bdc6b..346f1229d8 100644
@@ -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
builtin/log.c +24 -2
index 97980881ec..a1fe8b994a 100644
@@ -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);
t/t4014-format-patch.sh +15 -3
index 23c467e95b..147934922c 100755
@@ -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' '
Signature · 1 line
2.22.0.rc1.169.g49223abbf8
author
#44 lore raw
In reply to Denton Liu
Quoted 9 lines · Denton Liu

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

author
#45 lore raw

v3 cover teach branch-specific options for format-patch

In reply to Denton Liu

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.

@@ -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.

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 <[email protected]>" &&
-+ git config format.my7.cc "Cc Me <[email protected]>" &&
- 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
Signature · 1 line
2.22.0.355.g3bfa262345
author
#46 lore raw

PATCH RESEND v3 1/8 t4014: clean up style

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.

---
 t/t4014-format-patch.sh | 614 +++++++++++++++++++++-------------------
 1 file changed, 319 insertions(+), 295 deletions(-)
t/t4014-format-patch.sh +319 -295
index b6e2fdbc44..3423f974bc 100755
@@ -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 <[email protected]>
 " &&
-	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 <[email protected]>\$" hdrs2 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs2
 
@@ -134,7 +137,7 @@ test_expect_success 'extra headers without newlines' '
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "Cc: S E Cipient <[email protected]>" &&
 	git format-patch --stdout master..side >patch3 &&
-	sed -e "/^\$/q" patch3 > hdrs3 &&
+	sed -e "/^\$/q" patch3 >hdrs3 &&
 	grep "^To: R E Cipient <[email protected]>\$" hdrs3 &&
 	grep "^Cc: S E Cipient <[email protected]>\$" hdrs3
 
@@ -144,8 +147,8 @@ test_expect_success 'extra headers with multiple To:s' '
 
 	git config --replace-all format.headers "To: R E Cipient <[email protected]>" &&
 	git config --add format.headers "To: S E Cipient <[email protected]>" &&
-	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 <[email protected]>,\$" hdrs4 &&
 	grep "^ *S E Cipient <[email protected]>\$" 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 <[email protected]>" &&
-	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch5
+	git format-patch --cc="S E Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs5
 '
 
 test_expect_failure 'additional command line cc (rfc822)' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch5 &&
-	grep "^ *\"S. E. Cipient\" <[email protected]>\$" patch5
+	git format-patch --cc="S. E. Cipient <[email protected]>" --stdout master..side >patch5 &&
+	sed -e "/^\$/q" patch5 >hdrs5 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs5 &&
+	grep "^ *\"S. E. Cipient\" <[email protected]>\$" hdrs5
 '
 
 test_expect_success 'command line headers' '
 
 	git config --unset-all format.headers &&
-	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
-	grep "^Cc: R E Cipient <[email protected]>\$" patch6
+	git format-patch --add-header="Cc: R E Cipient <[email protected]>" --stdout master..side >patch6 &&
+	sed -e "/^\$/q" patch6 >hdrs6 &&
+	grep "^Cc: R E Cipient <[email protected]>\$" hdrs6
 '
 
 test_expect_success 'configuration headers and command line headers' '
 
 	git config --replace-all format.headers "Cc: R E Cipient <[email protected]>" &&
-	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
-	grep "^Cc: R E Cipient <[email protected]>,\$" patch7 &&
-	grep "^ *S E Cipient <[email protected]>\$" patch7
+	git format-patch --add-header="Cc: S E Cipient <[email protected]>" --stdout master..side >patch7 &&
+	sed -e "/^\$/q" patch7 >hdrs7 &&
+	grep "^Cc: R E Cipient <[email protected]>,\$" hdrs7 &&
+	grep "^ *S E Cipient <[email protected]>\$" hdrs7
 '
 
 test_expect_success 'command line To: header (ascii)' '
 
 	git config --unset-all format.headers &&
-	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch8
+	git format-patch --to="R E Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc822)' '
 
-	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch8
+	git format-patch --to="R. E. Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs8
 '
 
 test_expect_failure 'command line To: header (rfc2047)' '
 
-	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side | sed -e "/^\$/q" >patch8 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch8
+	git format-patch --to="R Ä Cipient <[email protected]>" --stdout master..side >patch8 &&
+	sed -e "/^\$/q" patch8 >hdrs8 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" hdrs8
 '
 
 test_expect_success 'configuration To: header (ascii)' '
 
 	git config format.to "R E Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: R E Cipient <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: R E Cipient <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc822)' '
 
 	git config format.to "R. E. Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: \"R. E. Cipient\" <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: \"R. E. Cipient\" <[email protected]>\$" hdrs9
 '
 
 test_expect_failure 'configuration To: header (rfc2047)' '
 
 	git config format.to "R Ä Cipient <[email protected]>" &&
-	git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 &&
-	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" patch9
+	git format-patch --stdout master..side >patch9 &&
+	sed -e "/^\$/q" patch9 >hdrs9 &&
+	grep "^To: =?UTF-8?q?R=20=C3=84=20Cipient?= <[email protected]>\$" 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 <[email protected]>\$" patch
+	! grep "^From: C O Mitter <[email protected]>\$" 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 <[email protected]>\$" 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 <[email protected]>\$" hdrs
 '
 
 test_expect_success 'format.from with address' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --no-from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--from overrides format.from' '
 
-	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side |
-	sed -e "/^\$/q" >patch &&
-	check_patch patch &&
-	! grep "^From: F R Om <[email protected]>\$" patch
+	git -c format.from="F R Om <[email protected]>" format-patch --from --stdout master..side >patch &&
+	sed -e "/^\$/q" patch >hdrs &&
+	check_patch hdrs &&
+	! grep "^From: F R Om <[email protected]>\$" hdrs
 '
 
 test_expect_success '--no-to overrides config.to' '
 
 	git config --replace-all format.to \
 		"R E Cipient <[email protected]>" &&
-	git format-patch --no-to --stdout master..side |
-	sed -e "/^\$/q" >patch10 &&
-	check_patch patch10 &&
-	! grep "^To: R E Cipient <[email protected]>\$" patch10
+	git format-patch --no-to --stdout master..side >patch10 &&
+	sed -e "/^\$/q" patch10 >hdrs10 &&
+	check_patch hdrs10 &&
+	! grep "^To: R E Cipient <[email protected]>\$" 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 <[email protected]>" &&
 	git format-patch --no-to --to="Someone Else <[email protected]>" \
-		--stdout master..side |
-	sed -e "/^\$/q" >patch11 &&
-	check_patch patch11 &&
-	! grep "^To: Someone <[email protected]>\$" patch11 &&
-	grep "^To: Someone Else <[email protected]>\$" patch11
+		--stdout master..side >patch11 &&
+	sed -e "/^\$/q" patch11 >hdrs11 &&
+	check_patch hdrs11 &&
+	! grep "^To: Someone <[email protected]>\$" hdrs11 &&
+	grep "^To: Someone Else <[email protected]>\$" hdrs11
 '
 
 test_expect_success '--no-cc overrides config.cc' '
 
 	git config --replace-all format.cc \
 		"C E Cipient <[email protected]>" &&
-	git format-patch --no-cc --stdout master..side |
-	sed -e "/^\$/q" >patch12 &&
-	check_patch patch12 &&
-	! grep "^Cc: C E Cipient <[email protected]>\$" patch12
+	git format-patch --no-cc --stdout master..side >patch12 &&
+	sed -e "/^\$/q" patch12 >hdrs12 &&
+	check_patch hdrs12 &&
+	! grep "^Cc: C E Cipient <[email protected]>\$" hdrs12
 '
 
 test_expect_success '--no-add-header overrides config.headers' '
 
 	git config --replace-all format.headers \
 		"Header1: B E Cipient <[email protected]>" &&
-	git format-patch --no-add-header --stdout master..side |
-	sed -e "/^\$/q" >patch13 &&
-	check_patch patch13 &&
-	! grep "^Header1: B E Cipient <[email protected]>\$" patch13
+	git format-patch --no-add-header --stdout master..side >patch13 &&
+	sed -e "/^\$/q" patch13 >hdrs13 &&
+	check_patch hdrs13 &&
+	! grep "^Header1: B E Cipient <[email protected]>\$" 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 "[email protected]" > patch8 &&
+	git format-patch -1 --stdout --in-reply-to "[email protected]" >patch8 &&
 	grep "^In-Reply-To: <[email protected]>" patch8 &&
 	grep "^References: <[email protected]>" 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 <[email protected]>
-EOF
+	cat <<-\EOF | sed "s/EOL$//" >expected &&
+	4:Subject: [PATCH] EOL
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
-EOF
+	Signed-off-by: my@house
+	(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+	Tested-by: Some One <[email protected]>
+	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 <[email protected]>
+	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 <[email protected]>
-EOF
+	body
+	Fooled-by-me: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-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 <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>" |
 		append_signoff >actual &&
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-9:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	9:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Signed-off-by: my@house
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-11:Signed-off-by: C O Mitter <[email protected]>
-12:Signed-off-by: my@house
-EOF
+	Signed-off-by: C O Mitter <[email protected]>
+	Signed-off-by: my@house
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-13:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Tested-by: my@house
+	Some Trash
+	Signed-off-by: C O Mitter <[email protected]>
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	13:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:
+	12:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-EOF
+	Myfooter: x
+	Some Trash
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	11:Signed-off-by: C O Mitter <[email protected]>
+	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 <[email protected]>
-Bug: 1234
-EOF
-	cat >expected <<\EOF &&
-4:Subject: [PATCH] subject
-8:
-10:
-14:Signed-off-by: C O Mitter <[email protected]>
-EOF
+	Reviewed-id: Noone
+	Tested-by: my@house
+	Change-id: Ideadbeef
+	Signed-off-by: C O Mitter <[email protected]>
+	Bug: 1234
+	EOF
+	cat >expected <<-\EOF &&
+	4:Subject: [PATCH] subject
+	8:
+	10:
+	14:Signed-off-by: C O Mitter <[email protected]>
+	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
 '
Signature · 1 line
2.22.0.355.g3bfa262345
author
#47 lore raw

PATCH RESEND v3 2/8 Doc: add more detail for git-format-patch

In reply to Denton Liu

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.

---
 Documentation/git-format-patch.txt | 25 ++++++++++++++++---------
 1 file changed, 16 insertions(+), 9 deletions(-)
Documentation/git-format-patch.txt +16 -9
index 1af85d404f..4e180bbfbb 100644
@@ -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
 ------------
 
 
Signature · 1 line
2.22.0.355.g3bfa262345
author
#48 lore raw

PATCH RESEND v3 3/8 format-patch: infer cover letter from branch description

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +5 -0
index dc77941c48..c8b28fe47f 100644
@@ -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.
Documentation/git-format-patch.txt +10 -0
index 4e180bbfbb..f54aecf4bf 100644
@@ -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
 ------------
 
 
builtin/log.c +36 -22

119 lines not on this page · raw message

t/t4014-format-patch.sh +33 -0
index 3423f974bc..4cb6b9edfa 100755
@@ -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 &&
Signature · 1 line
2.22.0.355.g3bfa262345
author
#49 lore raw

PATCH RESEND v3 4/8 format-patch: move extra_headers logic later

In reply to Denton Liu

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.

---
 builtin/log.c | 58 +++++++++++++++++++++++++--------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)
builtin/log.c +29 -29
index 617f074d60..3f97f344df 100644
@@ -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.
Signature · 1 line
2.22.0.355.g3bfa262345
author
#50 lore raw

PATCH RESEND v3 5/8 string-list: create string_list_append_all

In reply to Denton Liu

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.

---
 string-list.c | 9 +++++++++
 string-list.h | 7 +++++++
 2 files changed, 16 insertions(+)
string-list.c +9 -0
index a917955fbd..e63d58fbd2 100644
@@ -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.
string-list.h +7 -0
index f964399949..8227e00e6a 100644
@@ -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.
  */
Signature · 1 line
2.22.0.355.g3bfa262345
author
#51 lore raw

PATCH RESEND v3 6/8 format-patch: read branch-specific To: and Cc: headers

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +5 -2
index c8b28fe47f..95e255347a 100644
@@ -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]'
Documentation/git-format-patch.txt +10 -2
index f54aecf4bf..76e61b746a 100644
@@ -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
 ----------
builtin/log.c +59 -4

95 lines not on this page · raw message

t/t4014-format-patch.sh +88 -20

157 lines not on this page · raw message

Signature · 1 line
2.22.0.355.g3bfa262345
author
#52 lore raw

PATCH RESEND v3 7/8 format-patch: move output_directory logic later

In reply to Denton Liu

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.

---
 Documentation/git-format-patch.txt |  4 ++--
 builtin/log.c                      | 36 +++++++++++++++---------------
 2 files changed, 20 insertions(+), 20 deletions(-)
Documentation/git-format-patch.txt +2 -2
index 76e61b746a..707b4bdc6b 100644
@@ -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"]
builtin/log.c +18 -18
index cb9ccdc59c..97980881ec 100644
@@ -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++) {
Signature · 1 line
2.22.0.355.g3bfa262345
author
#53 lore raw

PATCH RESEND v3 8/8 format-patch: read branch-specific output directory

In reply to Denton Liu

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.

---
 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(-)
Documentation/config/format.txt +4 -1
index 95e255347a..df67a83183 100644
@@ -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
Documentation/git-format-patch.txt +2 -1
index 707b4bdc6b..346f1229d8 100644
@@ -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
builtin/log.c +24 -2
index 97980881ec..a1fe8b994a 100644
@@ -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);
t/t4014-format-patch.sh +15 -3
index 23c467e95b..147934922c 100755
@@ -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' '
Signature · 1 line
2.22.0.355.g3bfa262345
author
#54 lore raw

v6 cover format-patch: learn --infer-cover-subject option (also t4014 cleanup)

In reply to Denton Liu

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/[email protected]/

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 <[email protected]>

## 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 <[email protected]>
- Signed-off-by: Junio C Hamano <[email protected]>
+ 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);
Signature · 1 line
2.23.0.17.gd2208d9060
author
#55 lore raw

PATCH v6 1/3 format-patch: replace erroneous and condition

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.

---
 builtin/log.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/log.c +1 -1
index 44b10b3415..351f4ffcfd 100644
@@ -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")) {
Signature · 1 line
2.23.0.17.gd2208d9060
author
#56 lore raw

PATCH v6 2/3 format-patch: use enum variables

In reply to Denton Liu

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.

---
 builtin/log.c | 30 +++++++++++++++++-------------
 1 file changed, 17 insertions(+), 13 deletions(-)
builtin/log.c +17 -13
index 351f4ffcfd..d212a8305d 100644
@@ -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"))
Signature · 1 line
2.23.0.17.gd2208d9060
author
#57 lore raw

PATCH v6 3/3 format-patch: teach --cover-from-description option

In reply to Denton Liu

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).

---
 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(-)
Documentation/config/format.txt +6 -0
index cb629fa769..735dfcf827 100644
@@ -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.
Documentation/git-format-patch.txt +22 -0
index 0ac56f4b70..6800e1ab9a 100644
@@ -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
 ------------
 
 
builtin/log.c +75 -20

166 lines not on this page · raw message

t/t4014-format-patch.sh +172 -0

178 lines not on this page · raw message

t/t9902-completion.sh +4 -1
index 75512c3403..5187e2ede5 100755
@@ -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 "
 '
 
Signature · 1 line
2.23.0.17.gd2208d9060