From c2c29cc03e0d8156095dd18e2f7dcb768253c61a Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 13 Aug 2018 04:47:34 -0400 Subject: [PATCH 1/6] chainlint: match arbitrary here-docs tags rather than hard-coded names chainlint.sed swallows top-level here-docs to avoid being fooled by content which might look like start-of-subshell. It likewise swallows here-docs in subshells to avoid marking content lines as breaking the &&-chain, and to avoid being fooled by content which might look like end-of-subshell, start-of-nested-subshell, or other specially-recognized constructs. At the time of implementation, it was believed that it was not possible to support arbitrary here-doc tag names since 'sed' provides no way to stash the opening tag name in a variable for later comparison against a line signaling end-of-here-doc. Consequently, tag names are hard-coded, with "EOF" being the only tag recognized at the top-level, and only "EOF", "EOT", and "INPUT_END" being recognized within subshells. Also, special care was taken to avoid being confused by here-docs nested within other here-docs. In practice, this limited number of hard-coded tag names has been "good enough" for the 13000+ existing Git test, despite many of those tests using tags other than the recognized ones, since the bodies of those here-docs do not contain content which would fool the linter. Nevertheless, the situation is not ideal since someone writing new tests, and choosing a name not in the "blessed" set could potentially trigger a false-positive. To address this shortcoming, upgrade chainlint.sed to handle arbitrary here-doc tag names, both at the top-level and within subshells. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- t/chainlint.sed | 57 +++++++++++++++++----------- t/chainlint/here-doc.expect | 2 + t/chainlint/here-doc.test | 7 ++++ t/chainlint/nested-here-doc.expect | 2 + t/chainlint/nested-here-doc.test | 10 +++++ t/chainlint/subshell-here-doc.expect | 4 ++ t/chainlint/subshell-here-doc.test | 8 ++++ 7 files changed, 67 insertions(+), 23 deletions(-) diff --git a/t/chainlint.sed b/t/chainlint.sed index 5f0882cb38..2af1a687f8 100644 --- a/t/chainlint.sed +++ b/t/chainlint.sed @@ -61,6 +61,22 @@ # "else", and "fi" in if-then-else likewise must not end with "&&", thus # receives similar treatment. # +# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a +# line such as "cat <out" is seen, the here-doc tag is moved to the front +# of the line enclosed in angle brackets as a sentinel, giving "cat >out". +# As each subsequent line is read, it is appended to the target line and a +# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if +# the content inside "<...>" matches the entirety of the newly-read line. For +# instance, if the next line read is "some data", when concatenated with the +# target line, it becomes "cat >out\nsome data", and a match is attempted +# to see if "EOF" matches "some data". Since it doesn't, the next line is +# attempted. When a line consisting of only "EOF" (and possible whitespace) is +# encountered, it is appended to the target line giving "cat >out\nEOF", +# in which case the "EOF" inside "<...>" does match the text following the +# newline, thus the closing here-doc tag has been found. The closing tag line +# and the "<...>" prefix on the target line are then discarded, leaving just +# the target line "cat >out". +# # To facilitate regression testing (and manual debugging), a ">" annotation is # applied to the line containing ")" which closes a subshell, ">>" to a line # closing a nested subshell, and ">>>" to a line closing both at once. This @@ -78,14 +94,17 @@ # here-doc -- swallow it to avoid false hits within its body (but keep the # command to which it was attached) -/<<[ ]*[-\\]*EOF[ ]*/ { - s/[ ]*<<[ ]*[-\\]*EOF// - h +/<<[ ]*[-\\]*[A-Za-z0-9_]/ { + s/^\(.*\)<<[ ]*[-\\]*\([A-Za-z0-9_][A-Za-z0-9_]*\)/<\2>\1<]*\)>.*\n[ ]*\1[ ]*$/!{ + s/\n.*$// + bhereslurp + } + s/^<[^>]*>// + s/\n.*$// } # one-liner "(...) &&" @@ -139,9 +158,7 @@ s/.*\n// /"[^'"]*'[^'"]*"/!bsqstring } # here-doc -- swallow it -/<<[ ]*[-\\]*EOF/bheredoc -/<<[ ]*[-\\]*EOT/bheredoc -/<<[ ]*[-\\]*INPUT_END/bheredoc +/<<[ ]*[-\\]*[A-Za-z0-9_]/bheredoc # comment or empty line -- discard since final non-comment, non-empty line # before closing ")", "done", "elsif", "else", or "fi" will need to be # re-visited to drop "suspect" marking since final line of those constructs @@ -249,23 +266,17 @@ s/\n// bcheckchain # found here-doc -- swallow it to avoid false hits within its body (but keep -# the command to which it was attached); take care to handle here-docs nested -# within here-docs by only recognizing closing tag matching outer here-doc -# opening tag +# the command to which it was attached) :heredoc -/EOF/{ s/[ ]*<<[ ]*[-\\]*EOF//; s/^/EOF/; } -/EOT/{ s/[ ]*<<[ ]*[-\\]*EOT//; s/^/EOT/; } -/INPUT_END/{ s/[ ]*<<[ ]*[-\\]*INPUT_END//; s/^/INPUT_END/; } +s/^\(.*\)<<[ ]*[-\\]*\([A-Za-z0-9_][A-Za-z0-9_]*\)/<\2>\1<]*\)>.*\n[ ]*\1[ ]*$/!{ + s/\n.*$// + bhereslurpsub +} +s/^<[^>]*>// s/\n.*$// bcheckchain diff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect index 2328fe7753..33bc3cc0b4 100644 --- a/t/chainlint/here-doc.expect +++ b/t/chainlint/here-doc.expect @@ -1,3 +1,5 @@ boodle wobba gorgo snoot wafta snurb && +cat >foo && + horticulture diff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test index bd36f6e1d3..8986eefe74 100644 --- a/t/chainlint/here-doc.test +++ b/t/chainlint/here-doc.test @@ -7,6 +7,13 @@ quoth the raven, nevermore... EOF +# LINT: swallow here-doc with arbitrary tag +cat <<-Arbitrary_Tag_42 >foo && +snoz +boz +woz +Arbitrary_Tag_42 + # LINT: swallow here-doc (EOF is last line of test) horticulture <<\EOF gomez diff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect index 559301e005..0c9ef1cfc6 100644 --- a/t/chainlint/nested-here-doc.expect +++ b/t/chainlint/nested-here-doc.expect @@ -1,3 +1,5 @@ +cat >foop && + ( cat && ?!AMP?! cat diff --git a/t/chainlint/nested-here-doc.test b/t/chainlint/nested-here-doc.test index 027e0bb3ff..f35404bf0f 100644 --- a/t/chainlint/nested-here-doc.test +++ b/t/chainlint/nested-here-doc.test @@ -1,3 +1,13 @@ +# LINT: inner "EOF" not misintrepreted as closing ARBITRARY here-doc +cat <foop && +naddle +fub <bip echo >bop +>) && +( + cat >bup && + meep >) diff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test index 9c3564c247..05139af0b5 100644 --- a/t/chainlint/subshell-here-doc.test +++ b/t/chainlint/subshell-here-doc.test @@ -20,4 +20,12 @@ wednesday pugsly EOF +) && +( +# LINT: swallow here-doc with arbitrary tag + cat <<-\ARBITRARY >bup && + glink + FIZZ + ARBITRARY + meep ) From 7e32a31b2101c3ced65ea9367c747930a4eb39d9 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 13 Aug 2018 04:47:35 -0400 Subject: [PATCH 2/6] chainlint: match 'quoted' here-doc tags A here-doc tag can be quoted ('EOF') or escaped (\EOF) to suppress interpolation within the body. Although, chainlint recognizes escaped tags, it does not know about quoted tags. For completeness, teach it to recognize quoted tags, as well. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- t/chainlint.sed | 8 ++++---- t/chainlint/here-doc.expect | 2 ++ t/chainlint/here-doc.test | 7 +++++++ t/chainlint/subshell-here-doc.expect | 1 + t/chainlint/subshell-here-doc.test | 4 ++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/t/chainlint.sed b/t/chainlint.sed index 2af1a687f8..2901494e8a 100644 --- a/t/chainlint.sed +++ b/t/chainlint.sed @@ -94,8 +94,8 @@ # here-doc -- swallow it to avoid false hits within its body (but keep the # command to which it was attached) -/<<[ ]*[-\\]*[A-Za-z0-9_]/ { - s/^\(.*\)<<[ ]*[-\\]*\([A-Za-z0-9_][A-Za-z0-9_]*\)/<\2>\1<\1<\1<\1<foo && +cat >bar && + horticulture diff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test index 8986eefe74..f2bb14b693 100644 --- a/t/chainlint/here-doc.test +++ b/t/chainlint/here-doc.test @@ -14,6 +14,13 @@ boz woz Arbitrary_Tag_42 +# LINT: swallow 'quoted' here-doc +cat <<'FUMP' >bar && +snoz +boz +woz +FUMP + # LINT: swallow here-doc (EOF is last line of test) horticulture <<\EOF gomez diff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect index 7c2da63bc7..7663ea7fc4 100644 --- a/t/chainlint/subshell-here-doc.expect +++ b/t/chainlint/subshell-here-doc.expect @@ -5,5 +5,6 @@ >) && ( cat >bup && + cat >bup2 && meep >) diff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test index 05139af0b5..b6b5a9b33a 100644 --- a/t/chainlint/subshell-here-doc.test +++ b/t/chainlint/subshell-here-doc.test @@ -27,5 +27,9 @@ glink FIZZ ARBITRARY + cat <<-'ARBITRARY2' >bup2 && + glink + FIZZ + ARBITRARY2 meep ) From 06fc5c9f90da9be18371bafad5f35b6b17cbf49e Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 13 Aug 2018 04:47:36 -0400 Subject: [PATCH 3/6] chainlint: recognize multi-line $(...) when command cuddled with "$(" For multi-line $(...) expressions nested within subshells, chainlint.sed only recognizes: x=$( echo foo && ... but it is not unlikely that test authors may also cuddle the command with the opening "$(", so support that style, as well: x=$(echo foo && ... The closing ")" is already correctly recognized when cuddled or not. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- t/chainlint.sed | 2 +- .../multi-line-nested-command-substitution.expect | 11 ++++++++++- .../multi-line-nested-command-substitution.test | 11 ++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/t/chainlint.sed b/t/chainlint.sed index 2901494e8a..afb2443a9c 100644 --- a/t/chainlint.sed +++ b/t/chainlint.sed @@ -216,7 +216,7 @@ s/.*\n// # "$(...)" -- command substitution; not closing ")" /\$([^)][^)]*)[^)]*$/bcheckchain # multi-line "$(...\n...)" -- command substitution; treat as nested subshell -/\$([ ]*$/bnest +/\$([^)]*$/bnest # "=(...)" -- Bash array assignment; not closing ")" /=(/bcheckchain # closing "...) &&" diff --git a/t/chainlint/multi-line-nested-command-substitution.expect b/t/chainlint/multi-line-nested-command-substitution.expect index 19c023b1c8..59b6c8b850 100644 --- a/t/chainlint/multi-line-nested-command-substitution.expect +++ b/t/chainlint/multi-line-nested-command-substitution.expect @@ -6,4 +6,13 @@ >> ) && echo ok >) | -sort +sort && +( + bar && + x=$(echo bar | + cat +>> ) && + y=$(echo baz | +>> fip) && + echo fail +>) diff --git a/t/chainlint/multi-line-nested-command-substitution.test b/t/chainlint/multi-line-nested-command-substitution.test index ca0620ab6b..300058341b 100644 --- a/t/chainlint/multi-line-nested-command-substitution.test +++ b/t/chainlint/multi-line-nested-command-substitution.test @@ -6,4 +6,13 @@ ) && echo ok ) | -sort +sort && +( + bar && + x=$(echo bar | + cat + ) && + y=$(echo baz | + fip) && + echo fail +) From d93871143fdb7c11ddea81aa7f698e5eee0246e5 Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 13 Aug 2018 04:47:37 -0400 Subject: [PATCH 4/6] chainlint: let here-doc and multi-line string commence on same line After swallowing a here-doc, chainlint.sed assumes that no other processing needs to be done on the line aside from checking for &&-chain breakage; likewise, after folding a multi-line quoted string. However, it's conceivable (even if unlikely in practice) that both a here-doc and a multi-line quoted string might commence on the same line: cat <<\EOF && echo "foo bar" data EOF Support this case by sending the line (after swallowing and folding) through the normal processing sequence rather than jumping directly to the check for broken &&-chain. This change also allows other somewhat pathological cases to be handled, such as closing a subshell on the same line starting a here-doc: ( cat <<-\INPUT) data INPUT or, for instance, opening a multi-line $(...) expression on the same line starting a here-doc: x=$(cat <<-\END && data END echo "x") among others. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- t/chainlint.sed | 7 ++++--- t/chainlint/here-doc-close-subshell.expect | 2 ++ t/chainlint/here-doc-close-subshell.test | 5 +++++ t/chainlint/here-doc-multi-line-command-subst.expect | 5 +++++ t/chainlint/here-doc-multi-line-command-subst.test | 9 +++++++++ t/chainlint/here-doc-multi-line-string.expect | 4 ++++ t/chainlint/here-doc-multi-line-string.test | 8 ++++++++ 7 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 t/chainlint/here-doc-close-subshell.expect create mode 100644 t/chainlint/here-doc-close-subshell.test create mode 100644 t/chainlint/here-doc-multi-line-command-subst.expect create mode 100644 t/chainlint/here-doc-multi-line-command-subst.test create mode 100644 t/chainlint/here-doc-multi-line-string.expect create mode 100644 t/chainlint/here-doc-multi-line-string.test diff --git a/t/chainlint.sed b/t/chainlint.sed index afb2443a9c..6661e21f20 100644 --- a/t/chainlint.sed +++ b/t/chainlint.sed @@ -157,6 +157,7 @@ s/.*\n// /^[^']*'[^']*$/{ /"[^'"]*'[^'"]*"/!bsqstring } +:folded # here-doc -- swallow it /<<[ ]*[-\\']*[A-Za-z0-9_]/bheredoc # comment or empty line -- discard since final non-comment, non-empty line @@ -255,7 +256,7 @@ s/"//g N s/\n// /"/!bdqstring -bcheckchain +bfolded # found multi-line single-quoted string '...\n...' -- slurp until end of string :sqstring @@ -263,7 +264,7 @@ s/'//g N s/\n// /'/!bsqstring -bcheckchain +bfolded # found here-doc -- swallow it to avoid false hits within its body (but keep # the command to which it was attached) @@ -278,7 +279,7 @@ N } s/^<[^>]*>// s/\n.*$// -bcheckchain +bfolded # found "case ... in" -- pass through untouched :case diff --git a/t/chainlint/here-doc-close-subshell.expect b/t/chainlint/here-doc-close-subshell.expect new file mode 100644 index 0000000000..f011e335e5 --- /dev/null +++ b/t/chainlint/here-doc-close-subshell.expect @@ -0,0 +1,2 @@ +( +> cat) diff --git a/t/chainlint/here-doc-close-subshell.test b/t/chainlint/here-doc-close-subshell.test new file mode 100644 index 0000000000..b857ff5467 --- /dev/null +++ b/t/chainlint/here-doc-close-subshell.test @@ -0,0 +1,5 @@ +( +# LINT: line contains here-doc and closes nested subshell + cat <<-\INPUT) + fizz + INPUT diff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect new file mode 100644 index 0000000000..e5fb752d2f --- /dev/null +++ b/t/chainlint/here-doc-multi-line-command-subst.expect @@ -0,0 +1,5 @@ +( + x=$(bobble && +?!AMP?!>> wiffle) + echo $x +>) diff --git a/t/chainlint/here-doc-multi-line-command-subst.test b/t/chainlint/here-doc-multi-line-command-subst.test new file mode 100644 index 0000000000..899bc5de8b --- /dev/null +++ b/t/chainlint/here-doc-multi-line-command-subst.test @@ -0,0 +1,9 @@ +( +# LINT: line contains here-doc and opens multi-line $(...) + x=$(bobble <<-\END && + fossil + vegetable + END + wiffle) + echo $x +) diff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect new file mode 100644 index 0000000000..1e5b724b9d --- /dev/null +++ b/t/chainlint/here-doc-multi-line-string.expect @@ -0,0 +1,4 @@ +( +?!AMP?! cat && echo multi-line string" + bap +>) diff --git a/t/chainlint/here-doc-multi-line-string.test b/t/chainlint/here-doc-multi-line-string.test new file mode 100644 index 0000000000..a53edbcc8d --- /dev/null +++ b/t/chainlint/here-doc-multi-line-string.test @@ -0,0 +1,8 @@ +( +# LINT: line contains here-doc and opens multi-line string + cat <<-\TXT && echo "multi-line + string" + fizzle + TXT + bap +) From 22e3e0241ab5add065411d0d8d493f066764465e Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 13 Aug 2018 04:47:38 -0400 Subject: [PATCH 5/6] chainlint: recognize multi-line quoted strings more robustly chainlint.sed recognizes multi-line quoted strings within subshells: echo "abc def" >out && so it can avoid incorrectly classifying lines internal to the string as breaking the &&-chain. To identify the first line of a multi-line string, it checks if the line contains a single quote. However, this is fragile and can be easily fooled by a line containing multiple strings: echo "xyz" "abc def" >out && Make detection more robust by checking for an odd number of quotes rather than only a single one. (Escaped quotes are not handled, but support may be added later.) The original multi-line string recognizer rather cavalierly threw away all but the final quote, whereas the new one is careful to retain all quotes, so the "expected" output of a couple existing chainlint tests is updated to account for this new behavior. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- t/chainlint.sed | 32 +++++++++++++------ t/chainlint/here-doc-multi-line-string.expect | 2 +- t/chainlint/multi-line-string.expect | 10 ++++-- t/chainlint/multi-line-string.test | 12 +++++++ 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/t/chainlint.sed b/t/chainlint.sed index 6661e21f20..8544df38df 100644 --- a/t/chainlint.sed +++ b/t/chainlint.sed @@ -151,10 +151,10 @@ s/.*\n// :slurp # incomplete line "...\" /\\$/bincomplete -# multi-line quoted string "...\n..." -/^[^"]*"[^"]*$/bdqstring -# multi-line quoted string '...\n...' (but not contraction in string "it's so") -/^[^']*'[^']*$/{ +# multi-line quoted string "...\n..."? +/"/bdqstring +# multi-line quoted string '...\n...'? (but not contraction in string "it's") +/'/{ /"[^'"]*'[^'"]*"/!bsqstring } :folded @@ -250,20 +250,32 @@ N s/\\\n// bslurp -# found multi-line double-quoted string "...\n..." -- slurp until end of string +# check for multi-line double-quoted string "...\n..." -- fold to one line :dqstring -s/"//g +# remove all quote pairs +s/"\([^"]*\)"/@!\1@!/g +# done if no dangling quote +/"/!bdqdone +# otherwise, slurp next line and try again N s/\n// -/"/!bdqstring +bdqstring +:dqdone +s/@!/"/g bfolded -# found multi-line single-quoted string '...\n...' -- slurp until end of string +# check for multi-line single-quoted string '...\n...' -- fold to one line :sqstring -s/'//g +# remove all quote pairs +s/'\([^']*\)'/@!\1@!/g +# done if no dangling quote +/'/!bsqdone +# otherwise, slurp next line and try again N s/\n// -/'/!bsqstring +bsqstring +:sqdone +s/@!/'/g bfolded # found here-doc -- swallow it to avoid false hits within its body (but keep diff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect index 1e5b724b9d..32038a070c 100644 --- a/t/chainlint/here-doc-multi-line-string.expect +++ b/t/chainlint/here-doc-multi-line-string.expect @@ -1,4 +1,4 @@ ( -?!AMP?! cat && echo multi-line string" +?!AMP?! cat && echo "multi-line string" bap >) diff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect index 8334c4cc8e..170cb59993 100644 --- a/t/chainlint/multi-line-string.expect +++ b/t/chainlint/multi-line-string.expect @@ -1,9 +1,15 @@ ( - x=line 1 line 2 line 3" && -?!AMP?! y=line 1 line2' + x="line 1 line 2 line 3" && +?!AMP?! y='line 1 line2' foobar >) && ( echo "there's nothing to see here" && exit +>) && +( + echo "xyz" "abc def ghi" && + echo 'xyz' 'abc def ghi' && + echo 'xyz' "abc def ghi" && + barfoo >) diff --git a/t/chainlint/multi-line-string.test b/t/chainlint/multi-line-string.test index 14cb44d51c..287ab89705 100644 --- a/t/chainlint/multi-line-string.test +++ b/t/chainlint/multi-line-string.test @@ -12,4 +12,16 @@ # LINT: starting multi-line single-quoted string echo "there's nothing to see here" && exit +) && +( + echo "xyz" "abc + def + ghi" && + echo 'xyz' 'abc + def + ghi' && + echo 'xyz' "abc + def + ghi" && + barfoo ) From 4f69176feb1b441d46dab7a0ac679df7edf44f5f Mon Sep 17 00:00:00 2001 From: Eric Sunshine Date: Mon, 13 Aug 2018 04:47:39 -0400 Subject: [PATCH 6/6] chainlint: add test of pathological case which triggered false positive This extract from contrib/subtree/t7900 triggered a false positive due to three chainlint limitations: * recognizing only a "blessed" set of here-doc tag names in a subshell ("EOF", "EOT", "INPUT_END"), of which "TXT" is not a member * inability to recognize multi-line $(...) when the first statement of the body is cuddled with the opening "$(" * inability to recognize multiple constructs on a single line, such as opening a multi-line $(...) and starting a here-doc Now that all of these shortcomings have been addressed, turn this rather pathological bit of shell coding into a chainlint test case. Signed-off-by: Eric Sunshine Signed-off-by: Junio C Hamano --- t/chainlint/t7900-subtree.expect | 10 ++++++++++ t/chainlint/t7900-subtree.test | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 t/chainlint/t7900-subtree.expect create mode 100644 t/chainlint/t7900-subtree.test diff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect new file mode 100644 index 0000000000..c9913429e6 --- /dev/null +++ b/t/chainlint/t7900-subtree.expect @@ -0,0 +1,10 @@ +( + chks="sub1sub2sub3sub4" && + chks_sub=$(cat | sed 's,^,sub dir/,' +>>) && + chkms="main-sub1main-sub2main-sub3main-sub4" && + chkms_sub=$(cat | sed 's,^,sub dir/,' +>>) && + subfiles=$(git ls-files) && + check_equal "$subfiles" "$chkms$chks" +>) diff --git a/t/chainlint/t7900-subtree.test b/t/chainlint/t7900-subtree.test new file mode 100644 index 0000000000..277d8358df --- /dev/null +++ b/t/chainlint/t7900-subtree.test @@ -0,0 +1,22 @@ +( + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <