{"thread":{"id":"48778","subject":"[PATCH 01/29] t7508: use test_when_finished() instead of managing exit code manually","startedAt":"2018-06-26T07:30:54Z","lastAt":"2018-08-29T22:37:08Z","messageCount":123,"participants":["Eric Sunshine","Luke Diamand","Elijah Newren","Jonathan Tan","Johannes Sixt","Junio C Hamano","Jeff King","Jonathan Nieder","Stefan Beller","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":29},"messages":[{"id":"350915","messageId":"20180626073001.6555-2-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 01/29] t7508: use test_when_finished() instead of managing exit code manually","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:33Z","receivedAt":"2018-06-26T07:30:54Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test manages its own exit code in order to perform a cleanup action\nunconditionally, whether the test succeeds or fails overall. In doing\nso, it intentionally breaks the &&-chain. Such manual exit code\nmanagement to ensure cleanup predates the invention of\ntest_when_finished().\n\nAn upcoming change will teach --chain-lint to detect &&-chain breakage\ninside subshells, so this manual exit code management with intentional\n&&-chain breakage will run afoul of --chain-lint. Therefore, replace\nthe manual exit code handling with test_when_finished() and a normal\n&&-chain. While at it, drop the now-unnecessary subshell.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n\nNotes:\n    This series is built atop 'master'. If the series is queued there,\n    this patch is needed to avoid test-suite breakage. However, the\n    issue fixed by this patch is already also fixed by\n    'jc/clean-after-sanity-tests' in 'next' (although, that patch\n    doesn't bother dropping the now-unnecessary subshell, like this\n    patch does.)\n\n t/t7508-status.sh | 20 ++++++++------------\n 1 file changed, 8 insertions(+), 12 deletions(-)\n\ndiff --git a/t/t7508-status.sh b/t/t7508-status.sh\nindex 18a40257fb..67bf4393bb 100755\n--- a/t/t7508-status.sh\n+++ b/t/t7508-status.sh\n@@ -1099,18 +1099,14 @@ EOF\n '\n \n test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' '\n-\t(\n-\t\tchmod a-w .git &&\n-\t\t# make dir1/tracked stat-dirty\n-\t\t>dir1/tracked1 && mv -f dir1/tracked1 dir1/tracked &&\n-\t\tgit status -s >output &&\n-\t\t! grep dir1/tracked output &&\n-\t\t# make sure \"status\" succeeded without writing index out\n-\t\tgit diff-files | grep dir1/tracked\n-\t)\n-\tstatus=$?\n-\tchmod 775 .git\n-\t(exit $status)\n+\tchmod a-w .git &&\n+\ttest_when_finished \"chmod 775 .git\" &&\n+\t# make dir1/tracked stat-dirty\n+\t>dir1/tracked1 && mv -f dir1/tracked1 dir1/tracked &&\n+\tgit status -s >output &&\n+\t! grep dir1/tracked output &&\n+\t# make sure \"status\" succeeded without writing index out\n+\tgit diff-files | grep dir1/tracked\n '\n \n (cd sm && echo > bar && git add bar && git commit -q -m 'Add bar') && git add sm\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350916","messageId":"20180626073001.6555-1-sunshine@sunshineco.com","threadId":"48778","inReplyTo":null,"subject":"[PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:32Z","receivedAt":"2018-06-26T07:30:56Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint[1] option detects breakage in the top-level &&-chain of\ntests. This series undertakes the more complex task of teaching it to\nalso detect &&-chain breakage within subshells. See patch 29/29 for the\ngory details of how that's done.\n\nThe series is built atop 'master', however, it has a conflict with an\nin-flight topic. In particular, patch 1/29 rewrites a test in t7508 in\n'master' to avoid &&-chain breakage. 'jc/clean-after-sanity-tests' in\n'next' performs pretty much the same rewrite. If this series is queued\natop 'master', then it needs patch 1/29; if it is queued somewhere above\n'jc/clean-after-sanity-tests', then 1/29 can be dropped.\n\nAside from identifying a rather significant number of &&-chain breaks,\nrepairing those broken chains uncovered genuine bugs in several tests\nwhich were hidden by missing &&-chain links. Those bugs are also fixed\nby this series. I would appreciate if the following people would\ndouble-check my fixes:\n\nStefan Bellar - 8/29 \"t7400\" and (especially) 13/29 \"lib-submodule-update\"\nJonathan Tan - 10/29 \"t9001\"\nElijah Newren - 6/29 \"t6036\"\n\nIn order to keep the noise level down and ease review burden, please\ntake into account that I largely avoided modernizations and cleanups,\nand limited changes to merely adding \"&&\" even when some other\ntransformation would have made the fix nicer overall. (For example, I\nresisted the urge to replace a series of 'echo' statements in a subshell\nwith a simple here-doc.)\n\nFinally, although all simple &&-chain fixes originally inhabited a\nsingle patch, they were split out into several patches to avoid hitting\nvger.kernel.org's message size limit. However, when queuing, all patches\ntitled \"t????: fix broken &&-chains in subshells\" could be squashed into\na single patch titled \"t: fix broken &&-chains in subshells\".\n\n[1]: https://public-inbox.org/git/20150320100429.GA17354@peff.net/\n\nEric Sunshine (29):\n  t7508: use test_when_finished() instead of managing exit code manually\n  t0001: use \"{...}\" block around \"||\" expression rather than subshell\n  t1300: use sane_unset() to avoid breaking &&-chain\n  t3303: use standard here-doc tag \"EOF\" to avoid fooling --chain-lint\n  t5505: modernize and simplify hard-to-digest test\n  t6036: fix broken \"merge fails but has appropriate contents\" tests\n  t7201: drop pointless \"exit 0\" at end of subshell\n  t7400: fix broken \"submodule add/reconfigure --force\" test\n  t7810: use test_expect_code() instead of hand-rolled comparison\n  t9001: fix broken \"invoke hook\" test\n  t9104: use \"{...}\" block around \"||\" expression rather than subshell\n  t9401: drop unnecessary nested subshell\n  t/lib-submodule-update: fix broken \"replace submodule must-fail\" test\n  t: drop subshell with missing &&-chain in favor of simpler construct\n  t: drop unnecessary terminating semicolons in subshell\n  t: use test_might_fail() instead of manipulating exit code manually\n  t: use test_must_fail() instead of checking exit code manually\n  t0000-t0999: fix broken &&-chains in subshells\n  t1000-t1999: fix broken &&-chains in subshells\n  t2000-t2999: fix broken &&-chains in subshells\n  t3000-t3999: fix broken &&-chains in subshells\n  t3030: fix broken &&-chains in subshells\n  t4000-t4999: fix broken &&-chains in subshells\n  t5000-t5999: fix broken &&-chains in subshells\n  t6000-t6999: fix broken &&-chains in subshells\n  t7000-t7999: fix broken &&-chains in subshells\n  t9000-t9999: fix broken &&-chains in subshells\n  t9119: fix broken &&-chains in subshells\n  t/test-lib: teach --chain-lint to detect broken &&-chains in subshells\n\n t/lib-submodule-update.sh                     |  10 +-\n t/t0000-basic.sh                              |   2 +-\n t/t0001-init.sh                               |   2 +-\n t/t0003-attributes.sh                         |  24 +-\n t/t0021-conversion.sh                         |   4 +-\n t/t0090-cache-tree.sh                         |   2 +-\n t/t1004-read-tree-m-u-wf.sh                   |   8 +-\n t/t1005-read-tree-reset.sh                    |  10 +-\n t/t1008-read-tree-overlay.sh                  |   2 +-\n t/t1020-subdirectory.sh                       |   2 +-\n t/t1050-large.sh                              |   6 +-\n t/t1300-config.sh                             |   2 +-\n t/t1411-reflog-show.sh                        |   6 +-\n t/t1507-rev-parse-upstream.sh                 |   6 +-\n t/t1512-rev-parse-disambiguation.sh           |   6 +-\n t/t1700-split-index.sh                        |   2 +-\n t/t2016-checkout-patch.sh                     |  24 +-\n t/t2103-update-index-ignore-missing.sh        |   2 +-\n t/t2202-add-addremove.sh                      |  14 +-\n t/t3000-ls-files-others.sh                    |   2 +-\n t/t3006-ls-files-long.sh                      |   2 +-\n t/t3008-ls-files-lazy-init-name-hash.sh       |   8 +-\n t/t3030-merge-recursive.sh                    | 340 +++++++++---------\n t/t3050-subprojects-fetch.sh                  |   8 +-\n t/t3102-ls-tree-wildcards.sh                  |   2 +-\n t/t3301-notes.sh                              |   8 +-\n t/t3303-notes-subtrees.sh                     |  12 +-\n t/t3400-rebase.sh                             |   8 +-\n t/t3402-rebase-merge.sh                       |   4 +-\n t/t3404-rebase-interactive.sh                 |   6 +-\n t/t3418-rebase-continue.sh                    |   4 +-\n t/t3700-add.sh                                |   8 +-\n t/t3701-add-interactive.sh                    |  16 +-\n t/t3904-stash-patch.sh                        |   8 +-\n t/t4001-diff-rename.sh                        |   2 +-\n t/t4010-diff-pathspec.sh                      |   4 +-\n t/t4012-diff-binary.sh                        |   6 +-\n t/t4025-hunk-header.sh                        |   8 +-\n t/t4041-diff-submodule-option.sh              |   4 +-\n t/t4060-diff-submodule-option-diff-format.sh  |   2 +-\n t/t4121-apply-diffs.sh                        |   2 +-\n t/t5300-pack-object.sh                        |   2 +-\n t/t5302-pack-index.sh                         |   2 +-\n t/t5400-send-pack.sh                          |   4 +-\n t/t5401-update-hooks.sh                       |   4 +-\n t/t5405-send-pack-rewind.sh                   |   3 +-\n t/t5406-remote-rejects.sh                     |   2 +-\n t/t5500-fetch-pack.sh                         |   2 +-\n t/t5505-remote.sh                             |  10 +-\n t/t5512-ls-remote.sh                          |   4 +-\n t/t5516-fetch-push.sh                         |  10 +-\n t/t5517-push-mirror.sh                        |  10 +-\n t/t5526-fetch-submodules.sh                   |   2 +-\n t/t5531-deep-submodule-push.sh                |   2 +-\n t/t5543-atomic-push.sh                        |   2 +-\n t/t5601-clone.sh                              |   2 +-\n t/t5605-clone-local.sh                        |   2 +-\n t/t5801-remote-helpers.sh                     |   2 +-\n t/t6010-merge-base.sh                         |   2 +-\n t/t6029-merge-subtree.sh                      |  16 +-\n t/t6036-recursive-corner-cases.sh             |  14 +-\n t/t6042-merge-rename-corner-cases.sh          |   8 +-\n t/t6043-merge-rename-directories.sh           |   2 +-\n t/t7001-mv.sh                                 |   2 +-\n t/t7105-reset-patch.sh                        |  12 +-\n t/t7201-co.sh                                 |  41 ++-\n t/t7301-clean-interactive.sh                  |  41 ++-\n t/t7400-submodule-basic.sh                    |  12 +-\n t/t7406-submodule-update.sh                   |   4 +-\n t/t7408-submodule-reference.sh                |   2 +-\n t/t7501-commit.sh                             |  56 +--\n t/t7506-status-submodule.sh                   |  10 +-\n t/t7508-status.sh                             |  20 +-\n t/t7610-mergetool.sh                          |   8 +-\n t/t7810-grep.sh                               |   7 +-\n t/t9001-send-email.sh                         |  10 +-\n t/t9100-git-svn-basic.sh                      |   2 +-\n t/t9101-git-svn-props.sh                      |   2 +-\n t/t9104-git-svn-follow-parent.sh              |   4 +-\n t/t9119-git-svn-info.sh                       | 120 +++----\n t/t9122-git-svn-author.sh                     |   6 +-\n t/t9129-git-svn-i18n-commitencoding.sh        |   2 +-\n t/t9130-git-svn-authors-file.sh               |   4 +-\n t/t9134-git-svn-ignore-paths.sh               |   6 +-\n t/t9137-git-svn-dcommit-clobber-series.sh     |   2 +-\n t/t9138-git-svn-authors-prog.sh               |   6 +-\n t/t9146-git-svn-empty-dirs.sh                 |  12 +-\n t/t9147-git-svn-include-paths.sh              |   6 +-\n t/t9164-git-svn-dcommit-concurrent.sh         |   2 +-\n ...65-git-svn-fetch-merge-branch-of-branch.sh |   2 +-\n t/t9200-git-cvsexportcommit.sh                |   6 +-\n t/t9400-git-cvsserver-server.sh               |   6 +-\n t/t9401-git-cvsserver-crlf.sh                 |   4 +-\n t/t9600-cvsimport.sh                          |   2 +-\n t/t9806-git-p4-options.sh                     |   2 +-\n t/t9810-git-p4-rcs.sh                         |   2 +-\n t/t9811-git-p4-label-import.sh                |   2 +-\n t/t9814-git-p4-rename.sh                      |  16 +-\n t/t9815-git-p4-submit-fail.sh                 |   2 +-\n t/t9830-git-p4-symlink-dir.sh                 |   2 +-\n t/t9831-git-p4-triggers.sh                    |   2 +-\n t/t9902-completion.sh                         |   4 +-\n t/t9903-bash-prompt.sh                        |   2 +-\n t/test-lib.sh                                 | 245 ++++++++++++-\n 104 files changed, 810 insertions(+), 593 deletions(-)\n\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350917","messageId":"20180626073001.6555-4-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 03/29] t1300: use sane_unset() to avoid breaking &&-chain","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:35Z","receivedAt":"2018-06-26T07:30:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test intentionally breaks the &&-chain after \"unset HOME\" since it\ndoesn't know if 'unset' will succeed or fail. However, an upcoming\nchange will teach --chain-lint to detect &&-chain breakage inside\nsubshells, and it will catch this broken &&-chain. Instead, use\nsane_unset() which can be safely linked into the &&-chain.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t1300-config.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 03c223708e..24706ba412 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -888,7 +888,7 @@ EOF\n \n test_expect_success !MINGW 'get --path copes with unset $HOME' '\n \t(\n-\t\tunset HOME;\n+\t\tsane_unset HOME &&\n \t\ttest_must_fail git config --get --path path.home \\\n \t\t\t>result 2>msg &&\n \t\tgit config --get --path path.normal >>result &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350918","messageId":"20180626073001.6555-3-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 02/29] t0001: use \"{...}\" block around \"||\" expression rather than subshell","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:34Z","receivedAt":"2018-06-26T07:31:01Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test uses (... || true) as a shorthand for an if-then-else\nstatement. The subshell prevents it from breaking the top-level\n&&-chain.\n\nHowever, an upcoming change will teach --chain-lint to check the\n&&-chain inside subshells. Although it specially recognizes and allows\n(... || git ...) and (... || test*), it intentionally avoids treating\n(... || true) specially since such a construct typically can be\nexpressed more naturally with test_might_fail() and a normal &&-chain.\n\nThis case is special, though, since the invoked command, 'setfacl',\nmight not even exist (indeed, MacOS has no such command) which is not a\nvalid use-case for test_might_fail(). Sidestep the issue by wrapping the\n\"||\" expression in a \"{...}\" block instead of a subshell since \"{...}\"\nblocks are not checked for &&-chain breakage.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t0001-init.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex c413bff9cf..fa44a55a5b 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -260,7 +260,7 @@ test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shar\n \t\t# the repository itself should follow \"shared\"\n \t\tmkdir newdir &&\n \t\t# Remove a default ACL if possible.\n-\t\t(setfacl -k newdir 2>/dev/null || true) &&\n+\t\t{ setfacl -k newdir 2>/dev/null || true; } &&\n \t\tumask 002 &&\n \t\tgit init --bare --shared=0660 newdir/a/b/c &&\n \t\ttest_path_is_dir newdir/a/b/c/refs &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350919","messageId":"20180626073001.6555-5-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 04/29] t3303: use standard here-doc tag \"EOF\" to avoid fooling --chain-lint","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:36Z","receivedAt":"2018-06-26T07:31:03Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"An upcoming change will teach --chain-lint to detect &&-chain breakage\ninside subshells. The check works by performing textual transformations\non the test to link the subshell body directly into the parent's\n&&-chain. Special care is taken with the final statement in a\nsubshell. For instance:\n\n    statement1 &&\n    (\n        statement2\n    ) &&\n    statement3\n\nis transformed to:\n\n    statement1 &&\n    statement2 &&\n    statement3\n\nNotice that \"statement2\" gains a \"&&\".\n\nSpecial care is is taken with here-docs since \"&&\" needs to be added to\nthe \"<<EOF\" line which opens the here-doc, not the \"EOF\" line which\ncloses it. For practical reasons (namely, transformations are performed\nline-by-line), only here-docs tagged with \"EOF\" are recognized.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t3303-notes-subtrees.sh | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh\nindex 704aee81ef..e353faa50b 100755\n--- a/t/t3303-notes-subtrees.sh\n+++ b/t/t3303-notes-subtrees.sh\n@@ -39,7 +39,7 @@ test_expect_success \"setup: create $number_of_commits commits\" '\n \t\twhile [ $nr -lt $number_of_commits ]; do\n \t\t\tnr=$(($nr+1)) &&\n \t\t\ttest_tick &&\n-\t\t\tcat <<INPUT_END\n+\t\t\tcat <<EOF\n commit refs/heads/master\n committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n data <<COMMIT\n@@ -47,15 +47,15 @@ commit #$nr\n COMMIT\n \n M 644 inline file\n-data <<EOF\n+data <<DATA\n file in commit #$nr\n-EOF\n+DATA\n \n-INPUT_END\n+EOF\n \n \t\tdone &&\n \t\ttest_tick &&\n-\t\tcat <<INPUT_END\n+\t\tcat <<EOF\n commit refs/notes/commits\n committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE\n data <<COMMIT\n@@ -64,7 +64,7 @@ COMMIT\n \n deleteall\n \n-INPUT_END\n+EOF\n \n \t) |\n \tgit fast-import --quiet &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350920","messageId":"20180626073001.6555-10-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 09/29] t7810: use test_expect_code() instead of hand-rolled comparison","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:41Z","receivedAt":"2018-06-26T07:31:06Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test manually checks the exit code of git-grep for a particular\nvalue. In doing so, it breaks the &&-chain. An upcoming change will\nteach --chain-lint to check the &&-chain inside subshells, so this\nmanual exit code handling will run afoul of the &&-chain check.\nTherefore, replace the manual handling with test_expect_code() and a\nnormal &&-chain.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t7810-grep.sh | 7 +++----\n 1 file changed, 3 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t7810-grep.sh b/t/t7810-grep.sh\nindex 1797f632a3..fecee602c1 100755\n--- a/t/t7810-grep.sh\n+++ b/t/t7810-grep.sh\n@@ -845,10 +845,9 @@ test_expect_success 'grep from a subdirectory to search wider area (1)' '\n test_expect_success 'grep from a subdirectory to search wider area (2)' '\n \tmkdir -p s &&\n \t(\n-\t\tcd s || exit 1\n-\t\t( git grep xxyyzz .. >out ; echo $? >status )\n-\t\t! test -s out &&\n-\t\ttest 1 = $(cat status)\n+\t\tcd s &&\n+\t\ttest_expect_code 1 git grep xxyyzz .. >out &&\n+\t\t! test -s out\n \t)\n '\n \n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350921","messageId":"20180626073001.6555-13-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 12/29] t9401: drop unnecessary nested subshell","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:44Z","receivedAt":"2018-06-26T07:31:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test employs an unnecessary nested subshell:\n\n    (cd foo &&\n     statement1 &&\n     (cd bar &&\n      statement2))\n\nAn upcoming change will teach --chain-lint to check the &&-chain in\nsubshells. The check works by performing textual transformations on the\ntest to link the subshell body directly into the parent's &&-chain. It\nemploys heuristics to identify the extent of a subshell, however,\nclosing two subshells on a single line like this will fool it.\n\nRather than extending the heuristics even further for this one-off case,\njust drop the pointless nested subshell.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t9401-git-cvsserver-crlf.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh\nindex 84787eee9a..8b8d7ac34a 100755\n--- a/t/t9401-git-cvsserver-crlf.sh\n+++ b/t/t9401-git-cvsserver-crlf.sh\n@@ -288,9 +288,9 @@ test_expect_success 'add bin (guess)' '\n test_expect_success 'remove files (guess)' '\n     (cd cvswork &&\n     GIT_CONFIG=\"$git_config\" cvs -Q rm -f subdir/file.h &&\n-    (cd subdir &&\n+    cd subdir &&\n     GIT_CONFIG=\"$git_config\" cvs -Q rm -f withCr.bin\n-    )) &&\n+    ) &&\n     marked_as cvswork/subdir withCr.bin -kb &&\n     marked_as cvswork/subdir file.h \"\"\n '\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350922","messageId":"20180626073001.6555-16-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 15/29] t: drop unnecessary terminating semicolons in subshell","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:47Z","receivedAt":"2018-06-26T07:31:11Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"An upcoming change will teach --chain-lint to check the &&-chain inside\nsubshells. The semicolons after the final commands in these subshells\nwill trip up --chain-lint since they break the &&-chain. Since those\nsemicolons are unnecessary, just drop them.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t3102-ls-tree-wildcards.sh | 2 +-\n t/t4010-diff-pathspec.sh     | 4 ++--\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh\nindex e804377f1c..1e16c6b8ea 100755\n--- a/t/t3102-ls-tree-wildcards.sh\n+++ b/t/t3102-ls-tree-wildcards.sh\n@@ -23,7 +23,7 @@ test_expect_success 'ls-tree outside prefix' '\n \tcat >expect <<-EOF &&\n \t100644 blob $EMPTY_BLOB\t../a[a]/three\n \tEOF\n-\t( cd aa && git ls-tree -r HEAD \"../a[a]\"; ) >actual &&\n+\t( cd aa && git ls-tree -r HEAD \"../a[a]\" ) >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh\nindex 35b35a81c8..b7f25071cf 100755\n--- a/t/t4010-diff-pathspec.sh\n+++ b/t/t4010-diff-pathspec.sh\n@@ -111,10 +111,10 @@ test_expect_success 'diff-tree -r with wildcard' '\n test_expect_success 'setup submodules' '\n \ttest_tick &&\n \tgit init submod &&\n-\t( cd submod && test_commit first; ) &&\n+\t( cd submod && test_commit first ) &&\n \tgit add submod &&\n \tgit commit -m first &&\n-\t( cd submod && test_commit second; ) &&\n+\t( cd submod && test_commit second ) &&\n \tgit add submod &&\n \tgit commit -m second\n '\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350923","messageId":"20180626073001.6555-15-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 14/29] t: drop subshell with missing &&-chain in favor of simpler construct","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:46Z","receivedAt":"2018-06-26T07:31:15Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"These tests employ a noisy subshell (with missing &&-chain) to feed\ninput into Git commands:\n\n    (echo a; echo b; echo c) | git some-command ...\n\nDrop the subshell in favor of a simple 'printf':\n\n    printf \"%s\\n\" a b c | git some-command ...\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t0090-cache-tree.sh         |  2 +-\n t/t2016-checkout-patch.sh     | 24 ++++++++++----------\n t/t3404-rebase-interactive.sh |  6 ++---\n t/t3701-add-interactive.sh    | 16 +++++++-------\n t/t3904-stash-patch.sh        |  8 +++----\n t/t7105-reset-patch.sh        | 12 +++++-----\n t/t7301-clean-interactive.sh  | 41 +++++++++++++++++------------------\n t/t7501-commit.sh             |  4 ++--\n t/t7610-mergetool.sh          |  8 +++----\n 9 files changed, 60 insertions(+), 61 deletions(-)\n\ndiff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh\nindex 0c61268fd2..f86cc76c9d 100755\n--- a/t/t0090-cache-tree.sh\n+++ b/t/t0090-cache-tree.sh\n@@ -156,7 +156,7 @@ test_expect_success PERL 'commit --interactive gives cache-tree on partial commi\n \t\treturn 44;\n \t}\n \tEOT\n-\t(echo p; echo 1; echo; echo s; echo n; echo y; echo q) |\n+\tprintf \"%s\\n\" p 1 \"\" s n y q |\n \tgit commit --interactive -m foo &&\n \ttest_cache_tree\n '\ndiff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh\nindex 9cd0ac4ba3..4dcad0f4a2 100755\n--- a/t/t2016-checkout-patch.sh\n+++ b/t/t2016-checkout-patch.sh\n@@ -20,33 +20,33 @@ test_expect_success PERL 'setup' '\n \n test_expect_success PERL 'saying \"n\" does nothing' '\n \tset_and_save_state dir/foo work head &&\n-\t(echo n; echo n) | git checkout -p &&\n+\tprintf \"%s\\n\" n n | git checkout -p &&\n \tverify_saved_state bar &&\n \tverify_saved_state dir/foo\n '\n \n test_expect_success PERL 'git checkout -p' '\n-\t(echo n; echo y) | git checkout -p &&\n+\tprintf \"%s\\n\" n y | git checkout -p &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo head head\n '\n \n test_expect_success PERL 'git checkout -p with staged changes' '\n \tset_state dir/foo work index &&\n-\t(echo n; echo y) | git checkout -p &&\n+\tprintf \"%s\\n\" n y | git checkout -p &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo index index\n '\n \n test_expect_success PERL 'git checkout -p HEAD with NO staged changes: abort' '\n \tset_and_save_state dir/foo work head &&\n-\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\tprintf \"%s\\n\" n y n | git checkout -p HEAD &&\n \tverify_saved_state bar &&\n \tverify_saved_state dir/foo\n '\n \n test_expect_success PERL 'git checkout -p HEAD with NO staged changes: apply' '\n-\t(echo n; echo y; echo y) | git checkout -p HEAD &&\n+\tprintf \"%s\\n\" n y y | git checkout -p HEAD &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo head head\n '\n@@ -54,14 +54,14 @@ test_expect_success PERL 'git checkout -p HEAD with NO staged changes: apply' '\n test_expect_success PERL 'git checkout -p HEAD with change already staged' '\n \tset_state dir/foo index index &&\n \t# the third n is to get out in case it mistakenly does not apply\n-\t(echo n; echo y; echo n) | git checkout -p HEAD &&\n+\tprintf \"%s\\n\" n y n | git checkout -p HEAD &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo head head\n '\n \n test_expect_success PERL 'git checkout -p HEAD^' '\n \t# the third n is to get out in case it mistakenly does not apply\n-\t(echo n; echo y; echo n) | git checkout -p HEAD^ &&\n+\tprintf \"%s\\n\" n y n | git checkout -p HEAD^ &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo parent parent\n '\n@@ -69,7 +69,7 @@ test_expect_success PERL 'git checkout -p HEAD^' '\n test_expect_success PERL 'git checkout -p handles deletion' '\n \tset_state dir/foo work index &&\n \trm dir/foo &&\n-\t(echo n; echo y) | git checkout -p &&\n+\tprintf \"%s\\n\" n y | git checkout -p &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo index index\n '\n@@ -81,21 +81,21 @@ test_expect_success PERL 'git checkout -p handles deletion' '\n \n test_expect_success PERL 'path limiting works: dir' '\n \tset_state dir/foo work head &&\n-\t(echo y; echo n) | git checkout -p dir &&\n+\tprintf \"%s\\n\" y n | git checkout -p dir &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo head head\n '\n \n test_expect_success PERL 'path limiting works: -- dir' '\n \tset_state dir/foo work head &&\n-\t(echo y; echo n) | git checkout -p -- dir &&\n+\tprintf \"%s\\n\" y n | git checkout -p -- dir &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo head head\n '\n \n test_expect_success PERL 'path limiting works: HEAD^ -- dir' '\n \t# the third n is to get out in case it mistakenly does not apply\n-\t(echo y; echo n; echo n) | git checkout -p HEAD^ -- dir &&\n+\tprintf \"%s\\n\" y n n | git checkout -p HEAD^ -- dir &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo parent parent\n '\n@@ -103,7 +103,7 @@ test_expect_success PERL 'path limiting works: HEAD^ -- dir' '\n test_expect_success PERL 'path limiting works: foo inside dir' '\n \tset_state dir/foo work head &&\n \t# the third n is to get out in case it mistakenly does not apply\n-\t(echo y; echo n; echo n) | (cd dir && git checkout -p foo) &&\n+\tprintf \"%s\\n\" y n n | (cd dir && git checkout -p foo) &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo head head\n '\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 352a52e59d..0dea186289 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -509,7 +509,7 @@ test_expect_success 'interrupted squash works as expected' '\n \tone=$(git rev-parse HEAD~3) &&\n \tset_fake_editor &&\n \ttest_must_fail env FAKE_LINES=\"1 squash 3 2\" git rebase -i HEAD~3 &&\n-\t(echo one; echo two; echo four) > conflict &&\n+\tprintf \"%s\\n\" one two four > conflict &&\n \tgit add conflict &&\n \ttest_must_fail git rebase --continue &&\n \techo resolved > conflict &&\n@@ -523,10 +523,10 @@ test_expect_success 'interrupted squash works as expected (case 2)' '\n \tone=$(git rev-parse HEAD~3) &&\n \tset_fake_editor &&\n \ttest_must_fail env FAKE_LINES=\"3 squash 1 2\" git rebase -i HEAD~3 &&\n-\t(echo one; echo four) > conflict &&\n+\tprintf \"%s\\n\" one four > conflict &&\n \tgit add conflict &&\n \ttest_must_fail git rebase --continue &&\n-\t(echo one; echo two; echo four) > conflict &&\n+\tprintf \"%s\\n\" one two four > conflict &&\n \tgit add conflict &&\n \ttest_must_fail git rebase --continue &&\n \techo resolved > conflict &&\ndiff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh\nindex b170fb02b8..7eec7bb3bb 100755\n--- a/t/t3701-add-interactive.sh\n+++ b/t/t3701-add-interactive.sh\n@@ -46,13 +46,13 @@ test_expect_success 'setup expected' '\n '\n \n test_expect_success 'diff works (initial)' '\n-\t(echo d; echo 1) | git add -i >output &&\n+\tprintf \"%s\\n\" d 1 | git add -i >output &&\n \tsed -ne \"/new file/,/content/p\" <output >diff &&\n \tdiff_cmp expected diff\n '\n test_expect_success 'revert works (initial)' '\n \tgit add file &&\n-\t(echo r; echo 1) | git add -i &&\n+\tprintf \"%s\\n\" r 1 | git add -i &&\n \tgit ls-files >output &&\n \t! grep . output\n '\n@@ -83,13 +83,13 @@ test_expect_success 'setup expected' '\n '\n \n test_expect_success 'diff works (commit)' '\n-\t(echo d; echo 1) | git add -i >output &&\n+\tprintf \"%s\\n\" d 1 | git add -i >output &&\n \tsed -ne \"/^index/,/content/p\" <output >diff &&\n \tdiff_cmp expected diff\n '\n test_expect_success 'revert works (commit)' '\n \tgit add file &&\n-\t(echo r; echo 1) | git add -i &&\n+\tprintf \"%s\\n\" r 1 | git add -i &&\n \tgit add -i </dev/null >output &&\n \tgrep \"unchanged *+3/-0 file\" output\n '\n@@ -102,7 +102,7 @@ test_expect_success 'setup expected' '\n \n test_expect_success 'dummy edit works' '\n \ttest_set_editor : &&\n-\t(echo e; echo a) | git add -p &&\n+\tprintf \"%s\\n\" e a | git add -p &&\n \tgit diff > diff &&\n \tdiff_cmp expected diff\n '\n@@ -127,7 +127,7 @@ test_expect_success 'setup fake editor' '\n \n test_expect_success 'bad edit rejected' '\n \tgit reset &&\n-\t(echo e; echo n; echo d) | git add -p >output &&\n+\tprintf \"%s\\n\" e n d | git add -p >output &&\n \tgrep \"hunk does not apply\" output\n '\n \n@@ -140,7 +140,7 @@ test_expect_success 'setup patch' '\n \n test_expect_success 'garbage edit rejected' '\n \tgit reset &&\n-\t(echo e; echo n; echo d) | git add -p >output &&\n+\tprintf \"%s\\n\" e n d | git add -p >output &&\n \tgrep \"hunk does not apply\" output\n '\n \n@@ -170,7 +170,7 @@ test_expect_success 'setup expected' '\n '\n \n test_expect_success 'real edit works' '\n-\t(echo e; echo n; echo d) | git add -p &&\n+\tprintf \"%s\\n\" e n d | git add -p &&\n \tgit diff >output &&\n \tdiff_cmp expected output\n '\ndiff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh\nindex 83744f8c93..f32e272786 100755\n--- a/t/t3904-stash-patch.sh\n+++ b/t/t3904-stash-patch.sh\n@@ -29,14 +29,14 @@ test_expect_success 'setup' '\n test_expect_success 'saying \"n\" does nothing' '\n \tset_state HEAD HEADfile_work HEADfile_index &&\n \tset_state dir/foo work index &&\n-\t(echo n; echo n; echo n) | test_must_fail git stash save -p &&\n+\tprintf \"%s\\n\" n n n | test_must_fail git stash save -p &&\n \tverify_state HEAD HEADfile_work HEADfile_index &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo work index\n '\n \n test_expect_success 'git stash -p' '\n-\t(echo y; echo n; echo y) | git stash save -p &&\n+\tprintf \"%s\\n\" y n y | git stash save -p &&\n \tverify_state HEAD committed HEADfile_index &&\n \tverify_saved_state bar &&\n \tverify_state dir/foo head index &&\n@@ -51,7 +51,7 @@ test_expect_success 'git stash -p --no-keep-index' '\n \tset_state HEAD HEADfile_work HEADfile_index &&\n \tset_state bar bar_work bar_index &&\n \tset_state dir/foo work index &&\n-\t(echo y; echo n; echo y) | git stash save -p --no-keep-index &&\n+\tprintf \"%s\\n\" y n y | git stash save -p --no-keep-index &&\n \tverify_state HEAD committed committed &&\n \tverify_state bar bar_work dummy &&\n \tverify_state dir/foo head head &&\n@@ -66,7 +66,7 @@ test_expect_success 'git stash --no-keep-index -p' '\n \tset_state HEAD HEADfile_work HEADfile_index &&\n \tset_state bar bar_work bar_index &&\n \tset_state dir/foo work index &&\n-\t(echo y; echo n; echo y) | git stash save --no-keep-index -p &&\n+\tprintf \"%s\\n\" y n y | git stash save --no-keep-index -p &&\n \tverify_state HEAD committed committed &&\n \tverify_state dir/foo head head &&\n \tverify_state bar bar_work dummy &&\ndiff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh\nindex 98b7d7b969..d907b6dd18 100755\n--- a/t/t7105-reset-patch.sh\n+++ b/t/t7105-reset-patch.sh\n@@ -19,20 +19,20 @@ test_expect_success PERL 'setup' '\n \n test_expect_success PERL 'saying \"n\" does nothing' '\n \tset_and_save_state dir/foo work work &&\n-\t(echo n; echo n) | git reset -p &&\n+\tprintf \"%s\\n\" n n | git reset -p &&\n \tverify_saved_state dir/foo &&\n \tverify_saved_state bar\n '\n \n test_expect_success PERL 'git reset -p' '\n-\t(echo n; echo y) | git reset -p >output &&\n+\tprintf \"%s\\n\" n y | git reset -p >output &&\n \tverify_state dir/foo work head &&\n \tverify_saved_state bar &&\n \ttest_i18ngrep \"Unstage\" output\n '\n \n test_expect_success PERL 'git reset -p HEAD^' '\n-\t(echo n; echo y) | git reset -p HEAD^ >output &&\n+\tprintf \"%s\\n\" n y | git reset -p HEAD^ >output &&\n \tverify_state dir/foo work parent &&\n \tverify_saved_state bar &&\n \ttest_i18ngrep \"Apply\" output\n@@ -45,20 +45,20 @@ test_expect_success PERL 'git reset -p HEAD^' '\n \n test_expect_success PERL 'git reset -p dir' '\n \tset_state dir/foo work work &&\n-\t(echo y; echo n) | git reset -p dir &&\n+\tprintf \"%s\\n\" y n | git reset -p dir &&\n \tverify_state dir/foo work head &&\n \tverify_saved_state bar\n '\n \n test_expect_success PERL 'git reset -p -- foo (inside dir)' '\n \tset_state dir/foo work work &&\n-\t(echo y; echo n) | (cd dir && git reset -p -- foo) &&\n+\tprintf \"%s\\n\" y n | (cd dir && git reset -p -- foo) &&\n \tverify_state dir/foo work head &&\n \tverify_saved_state bar\n '\n \n test_expect_success PERL 'git reset -p HEAD^ -- dir' '\n-\t(echo y; echo n) | git reset -p HEAD^ -- dir &&\n+\tprintf \"%s\\n\" y n | git reset -p HEAD^ -- dir &&\n \tverify_state dir/foo work parent &&\n \tverify_saved_state bar\n '\ndiff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh\nindex 1bf9789c8a..2bc8526f59 100755\n--- a/t/t7301-clean-interactive.sh\n+++ b/t/t7301-clean-interactive.sh\n@@ -107,7 +107,7 @@ test_expect_success 'git clean -id (filter all)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo f; echo \"*\"; echo; echo c) | \\\n+\tprintf \"%s\\n\" f \"*\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -129,7 +129,7 @@ test_expect_success 'git clean -id (filter patterns)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo f; echo \"part3.* *.out\"; echo; echo c) | \\\n+\tprintf \"%s\\n\" f \"part3.* *.out\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -151,7 +151,7 @@ test_expect_success 'git clean -id (filter patterns 2)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo f; echo \"* !*.out\"; echo; echo c) | \\\n+\tprintf \"%s\\n\" f \"* !*.out\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -173,7 +173,7 @@ test_expect_success 'git clean -id (select - all)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo \"*\"; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"*\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -195,7 +195,7 @@ test_expect_success 'git clean -id (select - none)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -217,7 +217,7 @@ test_expect_success 'git clean -id (select - number)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo 3; echo; echo c) | \\\n+\tprintf \"%s\\n\" s 3 \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -239,7 +239,7 @@ test_expect_success 'git clean -id (select - number 2)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo 2 3; echo 5; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"2 3\" 5 \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -261,7 +261,7 @@ test_expect_success 'git clean -id (select - number 3)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo 3,4 5; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"3,4 5\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -282,7 +282,7 @@ test_expect_success 'git clean -id (select - filenames)' '\n \n \tmkdir -p build docs &&\n \ttouch a.out foo.txt bar.txt baz.txt &&\n-\t(echo s; echo a.out fo ba bar; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"a.out fo ba bar\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest ! -f a.out &&\n@@ -298,7 +298,7 @@ test_expect_success 'git clean -id (select - range)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo 1,3-4; echo 2; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"1,3-4\" 2 \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -320,7 +320,7 @@ test_expect_success 'git clean -id (select - range 2)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo 4- 1; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"4- 1\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -342,7 +342,7 @@ test_expect_success 'git clean -id (inverse select)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo s; echo \"*\"; echo -5- 1 -2; echo; echo c) | \\\n+\tprintf \"%s\\n\" s \"*\" \"-5- 1 -2\" \"\" c |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -364,7 +364,7 @@ test_expect_success 'git clean -id (ask)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \\\n+\tprintf \"%s\\n\" a Y y no yes bad \"\" |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -386,7 +386,7 @@ test_expect_success 'git clean -id (ask - Ctrl+D)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(echo a; echo Y; echo no; echo yes; echo \"\\04\") | \\\n+\tprintf \"%s\\n\" a Y no yes \"\\04\" |\n \tgit clean -id &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -408,8 +408,8 @@ test_expect_success 'git clean -id with prefix and path (filter)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(cd build/ && \\\n-\t (echo f; echo \"docs\"; echo \"*.h\"; echo ; echo c) | \\\n+\t(cd build/ &&\n+\t printf \"%s\\n\" f docs \"*.h\" \"\" c |\n \t git clean -id ..) &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -431,9 +431,8 @@ test_expect_success 'git clean -id with prefix and path (select by name)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(cd build/ && \\\n-\t (echo s; echo \"../docs/\"; echo \"../src/part3.c\"; \\\n-\t  echo \"../src/part4.c\";  echo; echo c) | \\\n+\t(cd build/ &&\n+\t printf \"%s\\n\" s ../docs/ ../src/part3.c ../src/part4.c \"\" c |\n \t git clean -id ..) &&\n \ttest -f Makefile &&\n \ttest -f README &&\n@@ -455,8 +454,8 @@ test_expect_success 'git clean -id with prefix and path (ask)' '\n \tmkdir -p build docs &&\n \ttouch a.out src/part3.c src/part3.h src/part4.c src/part4.h \\\n \tdocs/manual.txt obj.o build/lib.so &&\n-\t(cd build/ && \\\n-\t (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \\\n+\t(cd build/ &&\n+\t printf \"%s\\n\" a Y y no yes bad \"\" |\n \t git clean -id ..) &&\n \ttest -f Makefile &&\n \ttest -f README &&\ndiff --git a/t/t7501-commit.sh b/t/t7501-commit.sh\nindex 9dbbd01fc0..589b6cea23 100755\n--- a/t/t7501-commit.sh\n+++ b/t/t7501-commit.sh\n@@ -47,7 +47,7 @@ test_expect_success 'paths and -a do not mix' '\n test_expect_success PERL 'can use paths with --interactive' '\n \techo bong-o-bong >file &&\n \t# 2: update, 1:st path, that is all, 7: quit\n-\t( echo 2; echo 1; echo; echo 7 ) |\n+\tprintf \"%s\\n\" 2 1 \"\" 7 |\n \tgit commit -m foo --interactive file &&\n \tgit reset --hard HEAD^\n '\n@@ -293,7 +293,7 @@ test_expect_success PERL 'interactive add' '\n test_expect_success PERL \"commit --interactive doesn't change index if editor aborts\" '\n \techo zoo >file &&\n \ttest_must_fail git diff --exit-code >diff1 &&\n-\t(echo u ; echo \"*\" ; echo q) |\n+\tprintf \"%s\\n\" u \"*\" q |\n \t(\n \t\tEDITOR=: &&\n \t\texport EDITOR &&\ndiff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh\nindex 1a430b9c40..a66d038083 100755\n--- a/t/t7610-mergetool.sh\n+++ b/t/t7610-mergetool.sh\n@@ -57,18 +57,18 @@ test_expect_success 'setup' '\n \n \tgit checkout -b delete-base branch1 &&\n \tmkdir -p a/a &&\n-\t(echo one; echo two; echo 3; echo 4) >a/a/file.txt &&\n+\tprintf \"%s\\n\" one two 3 4 >a/a/file.txt &&\n \tgit add a/a/file.txt &&\n \tgit commit -m\"base file\" &&\n \tgit checkout -b move-to-b delete-base &&\n \tmkdir -p b/b &&\n \tgit mv a/a/file.txt b/b/file.txt &&\n-\t(echo one; echo two; echo 4) >b/b/file.txt &&\n+\tprintf \"%s\\n\" one two 4 >b/b/file.txt &&\n \tgit commit -a -m\"move to b\" &&\n \tgit checkout -b move-to-c delete-base &&\n \tmkdir -p c/c &&\n \tgit mv a/a/file.txt c/c/file.txt &&\n-\t(echo one; echo two; echo 3) >c/c/file.txt &&\n+\tprintf \"%s\\n\" one two 3 >c/c/file.txt &&\n \tgit commit -a -m\"move to c\" &&\n \n \tgit checkout -b stash1 master &&\n@@ -349,7 +349,7 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' '\n \tgit checkout -b test$test_count move-to-c &&\n \ttest_config mergetool.keepTemporaries true &&\n \ttest_must_fail git merge move-to-b &&\n-\t! (echo a; echo n) | git mergetool a/a/file.txt &&\n+\t! printf \"%s\\n\" a n | git mergetool a/a/file.txt &&\n \ttest -d a/a &&\n \tcat >expect <<-\\EOF &&\n \tfile_BASE_.txt\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350924","messageId":"20180626073001.6555-21-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 20/29] t2000-t2999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:52Z","receivedAt":"2018-06-26T07:31:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t2103-update-index-ignore-missing.sh |  2 +-\n t/t2202-add-addremove.sh               | 14 +++++++-------\n 2 files changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/t/t2103-update-index-ignore-missing.sh b/t/t2103-update-index-ignore-missing.sh\nindex 332694e7d3..0114f05228 100755\n--- a/t/t2103-update-index-ignore-missing.sh\n+++ b/t/t2103-update-index-ignore-missing.sh\n@@ -32,7 +32,7 @@ test_expect_success basics '\n \t\ttest_create_repo xyzzy &&\n \t\tcd xyzzy &&\n \t\t>file &&\n-\t\tgit add file\n+\t\tgit add file &&\n \t\tgit commit -m \"sub initial\"\n \t) &&\n \tgit add xyzzy &&\ndiff --git a/t/t2202-add-addremove.sh b/t/t2202-add-addremove.sh\nindex 6a5a3166b1..17744e8c57 100755\n--- a/t/t2202-add-addremove.sh\n+++ b/t/t2202-add-addremove.sh\n@@ -6,12 +6,12 @@ test_description='git add --all'\n \n test_expect_success setup '\n \t(\n-\t\techo .gitignore\n+\t\techo .gitignore &&\n \t\techo will-remove\n \t) >expect &&\n \t(\n-\t\techo actual\n-\t\techo expect\n+\t\techo actual &&\n+\t\techo expect &&\n \t\techo ignored\n \t) >.gitignore &&\n \tgit --literal-pathspecs add --all &&\n@@ -25,10 +25,10 @@ test_expect_success setup '\n \n test_expect_success 'git add --all' '\n \t(\n-\t\techo .gitignore\n-\t\techo not-ignored\n-\t\techo \"M\t.gitignore\"\n-\t\techo \"A\tnot-ignored\"\n+\t\techo .gitignore &&\n+\t\techo not-ignored &&\n+\t\techo \"M\t.gitignore\" &&\n+\t\techo \"A\tnot-ignored\" &&\n \t\techo \"D\twill-remove\"\n \t) >expect &&\n \t>ignored &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350925","messageId":"20180626073001.6555-19-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 18/29] t0000-t0999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:50Z","receivedAt":"2018-06-26T07:31:20Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t0000-basic.sh      |  2 +-\n t/t0003-attributes.sh | 24 ++++++++++++------------\n t/t0021-conversion.sh |  4 ++--\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/t/t0000-basic.sh b/t/t0000-basic.sh\nindex af61d083b4..34859fe4a5 100755\n--- a/t/t0000-basic.sh\n+++ b/t/t0000-basic.sh\n@@ -1081,7 +1081,7 @@ test_expect_success 'very long name in the index handled sanely' '\n \t(\n \t\tgit ls-files -s path4 |\n \t\tsed -e \"s/\t.*/\t/\" |\n-\t\ttr -d \"\\012\"\n+\t\ttr -d \"\\012\" &&\n \t\techo \"$a\"\n \t) | git update-index --index-info &&\n \tlen=$(git ls-files \"a*\" | wc -c) &&\ndiff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh\nindex f19ae4f8cc..5c37c2e1f8 100755\n--- a/t/t0003-attributes.sh\n+++ b/t/t0003-attributes.sh\n@@ -34,15 +34,15 @@ test_expect_success 'open-quoted pathname' '\n test_expect_success 'setup' '\n \tmkdir -p a/b/d a/c b &&\n \t(\n-\t\techo \"[attr]notest !test\"\n-\t\techo \"\\\" d \\\"\ttest=d\"\n-\t\techo \" e\ttest=e\"\n-\t\techo \" e\\\"\ttest=e\"\n-\t\techo \"f\ttest=f\"\n-\t\techo \"a/i test=a/i\"\n-\t\techo \"onoff test -test\"\n-\t\techo \"offon -test test\"\n-\t\techo \"no notest\"\n+\t\techo \"[attr]notest !test\" &&\n+\t\techo \"\\\" d \\\"\ttest=d\" &&\n+\t\techo \" e\ttest=e\" &&\n+\t\techo \" e\\\"\ttest=e\" &&\n+\t\techo \"f\ttest=f\" &&\n+\t\techo \"a/i test=a/i\" &&\n+\t\techo \"onoff test -test\" &&\n+\t\techo \"offon -test test\" &&\n+\t\techo \"no notest\" &&\n \t\techo \"A/e/F test=A/e/F\"\n \t) >.gitattributes &&\n \t(\n@@ -51,7 +51,7 @@ test_expect_success 'setup' '\n \t) >a/.gitattributes &&\n \t(\n \t\techo \"h test=a/b/h\" &&\n-\t\techo \"d/* test=a/b/d/*\"\n+\t\techo \"d/* test=a/b/d/*\" &&\n \t\techo \"d/yes notest\"\n \t) >a/b/.gitattributes &&\n \t(\n@@ -287,7 +287,7 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' '\n \t(\n \t\tcd bare.git &&\n \t\t(\n-\t\t\techo \"f\ttest=f\"\n+\t\t\techo \"f\ttest=f\" &&\n \t\t\techo \"a/i test=a/i\"\n \t\t) >.gitattributes &&\n \t\tattr_check f unspecified &&\n@@ -312,7 +312,7 @@ test_expect_success 'bare repository: test info/attributes' '\n \t(\n \t\tcd bare.git &&\n \t\t(\n-\t\t\techo \"f\ttest=f\"\n+\t\t\techo \"f\ttest=f\" &&\n \t\t\techo \"a/i test=a/i\"\n \t\t) >info/attributes &&\n \t\tattr_check f f &&\ndiff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh\nindex 9479a4aaab..6a213608cc 100755\n--- a/t/t0021-conversion.sh\n+++ b/t/t0021-conversion.sh\n@@ -785,7 +785,7 @@ test_expect_success PERL 'missing file in delayed checkout' '\n \t\tcd repo &&\n \t\tgit init &&\n \t\techo \"*.a filter=bug\" >.gitattributes &&\n-\t\tcp \"$TEST_ROOT/test.o\" missing-delay.a\n+\t\tcp \"$TEST_ROOT/test.o\" missing-delay.a &&\n \t\tgit add . &&\n \t\tgit commit -m \"test commit\"\n \t) &&\n@@ -807,7 +807,7 @@ test_expect_success PERL 'invalid file in delayed checkout' '\n \t\tgit init &&\n \t\techo \"*.a filter=bug\" >.gitattributes &&\n \t\tcp \"$TEST_ROOT/test.o\" invalid-delay.a &&\n-\t\tcp \"$TEST_ROOT/test.o\" unfiltered\n+\t\tcp \"$TEST_ROOT/test.o\" unfiltered &&\n \t\tgit add . &&\n \t\tgit commit -m \"test commit\"\n \t) &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350926","messageId":"20180626073001.6555-25-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 24/29] t5000-t5999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:56Z","receivedAt":"2018-06-26T07:31:21Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t5300-pack-object.sh         |  2 +-\n t/t5302-pack-index.sh          |  2 +-\n t/t5401-update-hooks.sh        |  4 ++--\n t/t5406-remote-rejects.sh      |  2 +-\n t/t5500-fetch-pack.sh          |  2 +-\n t/t5505-remote.sh              |  2 +-\n t/t5512-ls-remote.sh           |  4 ++--\n t/t5516-fetch-push.sh          | 10 +++++-----\n t/t5517-push-mirror.sh         | 10 +++++-----\n t/t5526-fetch-submodules.sh    |  2 +-\n t/t5531-deep-submodule-push.sh |  2 +-\n t/t5543-atomic-push.sh         |  2 +-\n t/t5601-clone.sh               |  2 +-\n t/t5605-clone-local.sh         |  2 +-\n t/t5801-remote-helpers.sh      |  2 +-\n 15 files changed, 25 insertions(+), 25 deletions(-)\n\ndiff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh\nindex 2336d09dcc..6c620cd540 100755\n--- a/t/t5300-pack-object.sh\n+++ b/t/t5300-pack-object.sh\n@@ -191,7 +191,7 @@ test_expect_success 'survive missing objects/pack directory' '\n \t\tmkdir missing-pack &&\n \t\tcd missing-pack &&\n \t\tgit init &&\n-\t\tGOP=.git/objects/pack\n+\t\tGOP=.git/objects/pack &&\n \t\trm -fr $GOP &&\n \t\tgit index-pack --stdin --keep=test <../test-3-${packname_3}.pack &&\n \t\ttest -f $GOP/pack-${packname_3}.pack &&\ndiff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh\nindex bb9b8bb309..91d51b35f9 100755\n--- a/t/t5302-pack-index.sh\n+++ b/t/t5302-pack-index.sh\n@@ -237,7 +237,7 @@ test_expect_success 'running index-pack in the object store' '\n     rm -f .git/objects/pack/* &&\n     cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack &&\n     (\n-\tcd .git/objects/pack\n+\tcd .git/objects/pack &&\n \tgit index-pack pack-${pack1}.pack\n     ) &&\n     test -f .git/objects/pack/pack-${pack1}.idx\ndiff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh\nindex 7f278d8ce9..b5f886a0e2 100755\n--- a/t/t5401-update-hooks.sh\n+++ b/t/t5401-update-hooks.sh\n@@ -82,13 +82,13 @@ test_expect_success 'hooks ran' '\n '\n \n test_expect_success 'pre-receive hook input' '\n-\t(echo $commit0 $commit1 refs/heads/master;\n+\t(echo $commit0 $commit1 refs/heads/master &&\n \t echo $commit1 $commit0 refs/heads/tofail\n \t) | test_cmp - victim.git/pre-receive.stdin\n '\n \n test_expect_success 'update hook arguments' '\n-\t(echo refs/heads/master $commit0 $commit1;\n+\t(echo refs/heads/master $commit0 $commit1 &&\n \t echo refs/heads/tofail $commit1 $commit0\n \t) | test_cmp - victim.git/update.args\n '\ndiff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh\nindex 59e80a5ea2..350d2e6ea5 100755\n--- a/t/t5406-remote-rejects.sh\n+++ b/t/t5406-remote-rejects.sh\n@@ -6,7 +6,7 @@ test_description='remote push rejects are reported by client'\n \n test_expect_success 'setup' '\n \tmkdir .git/hooks &&\n-\t(echo \"#!/bin/sh\" ; echo \"exit 1\") >.git/hooks/update &&\n+\t(echo \"#!/bin/sh\" && echo \"exit 1\") >.git/hooks/update &&\n \tchmod +x .git/hooks/update &&\n \techo 1 >file &&\n \tgit add file &&\ndiff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\nindex 8390c0a2d2..c394779f65 100755\n--- a/t/t5500-fetch-pack.sh\n+++ b/t/t5500-fetch-pack.sh\n@@ -259,7 +259,7 @@ test_expect_success 'clone shallow object count' '\n test_expect_success 'pull in shallow repo with missing merge base' '\n \t(\n \t\tcd shallow &&\n-\t\tgit fetch --depth 4 .. A\n+\t\tgit fetch --depth 4 .. A &&\n \t\ttest_must_fail git merge --allow-unrelated-histories FETCH_HEAD\n \t)\n '\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 3552b51b4c..11e14a1e0f 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -844,7 +844,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)'\n \t\tgit remote rename origin origin &&\n \t\ttest_path_is_missing .git/branches/origin &&\n \t\ttest \"$(git config remote.origin.url)\" = \"quux\" &&\n-\t\ttest \"$(git config remote.origin.fetch)\" = \"refs/heads/foom:refs/heads/origin\"\n+\t\ttest \"$(git config remote.origin.fetch)\" = \"refs/heads/foom:refs/heads/origin\" &&\n \t\ttest \"$(git config remote.origin.push)\" = \"HEAD:refs/heads/foom\"\n \t)\n '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 6a949484d0..ea020040e8 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -15,7 +15,7 @@ test_expect_success setup '\n \tgit tag mark1.10 &&\n \tgit show-ref --tags -d | sed -e \"s/ /\t/\" >expected.tag &&\n \t(\n-\t\techo \"$(git rev-parse HEAD)\tHEAD\"\n+\t\techo \"$(git rev-parse HEAD)\tHEAD\" &&\n \t\tgit show-ref -d\t| sed -e \"s/ /\t/\"\n \t) >expected.all &&\n \n@@ -105,7 +105,7 @@ test_expect_success 'use branch.<name>.remote if possible' '\n \tgit clone . other.git &&\n \t(\n \t\tcd other.git &&\n-\t\techo \"$(git rev-parse HEAD)\tHEAD\"\n+\t\techo \"$(git rev-parse HEAD)\tHEAD\" &&\n \t\tgit show-ref\t| sed -e \"s/ /\t/\"\n \t) >exp &&\n \ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex a5077d8b7c..bd8f23e430 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -923,7 +923,7 @@ test_expect_success 'push into aliased refs (consistent)' '\n \t(\n \t\tcd child1 &&\n \t\tgit branch foo &&\n-\t\tgit symbolic-ref refs/heads/bar refs/heads/foo\n+\t\tgit symbolic-ref refs/heads/bar refs/heads/foo &&\n \t\tgit config receive.denyCurrentBranch false\n \t) &&\n \t(\n@@ -945,7 +945,7 @@ test_expect_success 'push into aliased refs (inconsistent)' '\n \t(\n \t\tcd child1 &&\n \t\tgit branch foo &&\n-\t\tgit symbolic-ref refs/heads/bar refs/heads/foo\n+\t\tgit symbolic-ref refs/heads/bar refs/heads/foo &&\n \t\tgit config receive.denyCurrentBranch false\n \t) &&\n \t(\n@@ -1011,7 +1011,7 @@ test_expect_success 'push --porcelain rejected' '\n \tmk_empty testrepo &&\n \tgit push testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(cd testrepo &&\n-\t\tgit reset --hard origin/master^\n+\t\tgit reset --hard origin/master^ &&\n \t\tgit config receive.denyCurrentBranch true) &&\n \n \techo >.git/foo  \"To testrepo\"  &&\n@@ -1025,7 +1025,7 @@ test_expect_success 'push --porcelain --dry-run rejected' '\n \tmk_empty testrepo &&\n \tgit push testrepo refs/heads/master:refs/remotes/origin/master &&\n \t(cd testrepo &&\n-\t\tgit reset --hard origin/master\n+\t\tgit reset --hard origin/master &&\n \t\tgit config receive.denyCurrentBranch true) &&\n \n \techo >.git/foo  \"To testrepo\"  &&\n@@ -1333,7 +1333,7 @@ test_expect_success 'push --follow-tag only pushes relevant tags' '\n \t\tgit commit --allow-empty -m \"future commit\" &&\n \t\tgit tag -m \"future\" future &&\n \t\tgit checkout master &&\n-\t\tgit for-each-ref refs/heads/master refs/tags/tag >../expect\n+\t\tgit for-each-ref refs/heads/master refs/tags/tag >../expect &&\n \t\tgit push --follow-tag ../dst master\n \t) &&\n \t(\ndiff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh\nindex 02f160aae0..c05a661400 100755\n--- a/t/t5517-push-mirror.sh\n+++ b/t/t5517-push-mirror.sh\n@@ -71,7 +71,7 @@ test_expect_success 'push mirror force updates existing branches' '\n \t\tgit push --mirror up &&\n \t\techo two >foo && git add foo && git commit -m two &&\n \t\tgit push --mirror up &&\n-\t\tgit reset --hard HEAD^\n+\t\tgit reset --hard HEAD^ &&\n \t\tgit push --mirror up\n \t) &&\n \tmaster_master=$(cd master && git show-ref -s --verify refs/heads/master) &&\n@@ -88,7 +88,7 @@ test_expect_success 'push mirror removes branches' '\n \t\techo one >foo && git add foo && git commit -m one &&\n \t\tgit branch remove master &&\n \t\tgit push --mirror up &&\n-\t\tgit branch -D remove\n+\t\tgit branch -D remove &&\n \t\tgit push --mirror up\n \t) &&\n \t(\n@@ -170,7 +170,7 @@ test_expect_success 'push mirror force updates existing tags' '\n \t\techo two >foo && git add foo && git commit -m two &&\n \t\tgit tag -f tmaster master &&\n \t\tgit push --mirror up &&\n-\t\tgit reset --hard HEAD^\n+\t\tgit reset --hard HEAD^ &&\n \t\tgit tag -f tmaster master &&\n \t\tgit push --mirror up\n \t) &&\n@@ -188,7 +188,7 @@ test_expect_success 'push mirror removes tags' '\n \t\techo one >foo && git add foo && git commit -m one &&\n \t\tgit tag -f tremove master &&\n \t\tgit push --mirror up &&\n-\t\tgit tag -d tremove\n+\t\tgit tag -d tremove &&\n \t\tgit push --mirror up\n \t) &&\n \t(\n@@ -235,7 +235,7 @@ test_expect_success 'remote.foo.mirror adds and removes branches' '\n \t\tgit branch keep master &&\n \t\tgit branch remove master &&\n \t\tgit push up &&\n-\t\tgit branch -D remove\n+\t\tgit branch -D remove &&\n \t\tgit push up\n \t) &&\n \t(\ndiff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh\nindex 9cc4b569c0..143638c8cd 100755\n--- a/t/t5526-fetch-submodules.sh\n+++ b/t/t5526-fetch-submodules.sh\n@@ -379,7 +379,7 @@ test_expect_success \"'--recurse-submodules=on-demand' recurses as deep as necess\n \t\t\tgit config -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive false\n \t\t) &&\n \t\tgit fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err &&\n-\t\tgit config --unset fetch.recurseSubmodules\n+\t\tgit config --unset fetch.recurseSubmodules &&\n \t\t(\n \t\t\tcd submodule &&\n \t\t\tgit config --unset -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive\ndiff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh\nindex 39cb2c1c34..e2c37fd978 100755\n--- a/t/t5531-deep-submodule-push.sh\n+++ b/t/t5531-deep-submodule-push.sh\n@@ -354,7 +354,7 @@ test_expect_success 'push succeeds if submodule has no remote and is on the firs\n \tgit clone a a1 &&\n \t(\n \t\tcd a1 &&\n-\t\tgit init b\n+\t\tgit init b &&\n \t\t(\n \t\t\tcd b &&\n \t\t\t>junk &&\ndiff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh\nindex 3480b33007..7079bcf9a0 100755\n--- a/t/t5543-atomic-push.sh\n+++ b/t/t5543-atomic-push.sh\n@@ -178,7 +178,7 @@ test_expect_success 'atomic push obeys update hook preventing a branch to be pus\n test_expect_success 'atomic push is not advertised if configured' '\n \tmk_repo_pair &&\n \t(\n-\t\tcd upstream\n+\t\tcd upstream &&\n \t\tgit config receive.advertiseatomic 0\n \t) &&\n \t(\ndiff --git a/t/t5601-clone.sh b/t/t5601-clone.sh\nindex 0b62037744..ddaa96ac4f 100755\n--- a/t/t5601-clone.sh\n+++ b/t/t5601-clone.sh\n@@ -618,7 +618,7 @@ hex2oct () {\n test_expect_success 'clone on case-insensitive fs' '\n \tgit init icasefs &&\n \t(\n-\t\tcd icasefs\n+\t\tcd icasefs &&\n \t\to=$(git hash-object -w --stdin </dev/null | hex2oct) &&\n \t\tt=$(printf \"100644 X\\0${o}100644 x\\0${o}\" |\n \t\t\tgit hash-object -w -t tree --stdin) &&\ndiff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh\nindex 3c087e907c..af23419ebf 100755\n--- a/t/t5605-clone-local.sh\n+++ b/t/t5605-clone-local.sh\n@@ -94,7 +94,7 @@ test_expect_success 'clone empty repository' '\n \t git config receive.denyCurrentBranch warn) &&\n \tgit clone empty empty-clone &&\n \ttest_tick &&\n-\t(cd empty-clone\n+\t(cd empty-clone &&\n \t echo \"content\" >> foo &&\n \t git add foo &&\n \t git commit -m \"Initial commit\" &&\ndiff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh\nindex 362b1581e0..ee5757966f 100755\n--- a/t/t5801-remote-helpers.sh\n+++ b/t/t5801-remote-helpers.sh\n@@ -96,7 +96,7 @@ test_expect_success 'push new branch with old:new refspec' '\n \n test_expect_success 'push new branch with HEAD:new refspec' '\n \t(cd local &&\n-\t git checkout new-name\n+\t git checkout new-name &&\n \t git push origin HEAD:new-refspec-2\n \t) &&\n \tcompare_refs local HEAD server refs/heads/new-refspec-2\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350927","messageId":"20180626073001.6555-29-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 28/29] t9119: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:30:00Z","receivedAt":"2018-06-26T07:31:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t9119-git-svn-info.sh | 120 ++++++++++++++++++++--------------------\n 1 file changed, 60 insertions(+), 60 deletions(-)\n\ndiff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh\nindex 88241baee3..8201c3e808 100755\n--- a/t/t9119-git-svn-info.sh\n+++ b/t/t9119-git-svn-info.sh\n@@ -22,8 +22,8 @@ esac\n # same value as \"svn info\" (i.e. the commit timestamp that touched the\n # path most recently); do not expect that field to match.\n test_cmp_info () {\n-\tsed -e '/^Text Last Updated:/d' \"$1\" >tmp.expect\n-\tsed -e '/^Text Last Updated:/d' \"$2\" >tmp.actual\n+\tsed -e '/^Text Last Updated:/d' \"$1\" >tmp.expect &&\n+\tsed -e '/^Text Last Updated:/d' \"$2\" >tmp.actual &&\n \ttest_cmp tmp.expect tmp.actual &&\n \trm -f tmp.expect tmp.actual\n }\n@@ -59,24 +59,24 @@ test_expect_success 'setup repository and import' '\n \t'\n \n test_expect_success 'info' \"\n-\t(cd svnwc; svn info) > expected.info &&\n-\t(cd gitwc; git svn info) > actual.info &&\n+\t(cd svnwc && svn info) > expected.info &&\n+\t(cd gitwc && git svn info) > actual.info &&\n \ttest_cmp_info expected.info actual.info\n \t\"\n \n test_expect_success 'info --url' '\n-\ttest \"$(cd gitwc; git svn info --url)\" = \"$quoted_svnrepo\"\n+\ttest \"$(cd gitwc && git svn info --url)\" = \"$quoted_svnrepo\"\n \t'\n \n test_expect_success 'info .' \"\n-\t(cd svnwc; svn info .) > expected.info-dot &&\n-\t(cd gitwc; git svn info .) > actual.info-dot &&\n+\t(cd svnwc && svn info .) > expected.info-dot &&\n+\t(cd gitwc && git svn info .) > actual.info-dot &&\n \ttest_cmp_info expected.info-dot actual.info-dot\n \t\"\n \n test_expect_success 'info $(pwd)' '\n-\t(cd svnwc; svn info \"$(pwd)\") >expected.info-pwd &&\n-\t(cd gitwc; git svn info \"$(pwd)\") >actual.info-pwd &&\n+\t(cd svnwc && svn info \"$(pwd)\") >expected.info-pwd &&\n+\t(cd gitwc && git svn info \"$(pwd)\") >actual.info-pwd &&\n \tgrep -v ^Path: <expected.info-pwd >expected.info-np &&\n \tgrep -v ^Path: <actual.info-pwd >actual.info-np &&\n \ttest_cmp_info expected.info-np actual.info-np &&\n@@ -85,8 +85,8 @@ test_expect_success 'info $(pwd)' '\n \t'\n \n test_expect_success 'info $(pwd)/../___wc' '\n-\t(cd svnwc; svn info \"$(pwd)/../svnwc\") >expected.info-pwd &&\n-\t(cd gitwc; git svn info \"$(pwd)/../gitwc\") >actual.info-pwd &&\n+\t(cd svnwc && svn info \"$(pwd)/../svnwc\") >expected.info-pwd &&\n+\t(cd gitwc && git svn info \"$(pwd)/../gitwc\") >actual.info-pwd &&\n \tgrep -v ^Path: <expected.info-pwd >expected.info-np &&\n \tgrep -v ^Path: <actual.info-pwd >actual.info-np &&\n \ttest_cmp_info expected.info-np actual.info-np &&\n@@ -95,8 +95,8 @@ test_expect_success 'info $(pwd)/../___wc' '\n \t'\n \n test_expect_success 'info $(pwd)/../___wc//file' '\n-\t(cd svnwc; svn info \"$(pwd)/../svnwc//file\") >expected.info-pwd &&\n-\t(cd gitwc; git svn info \"$(pwd)/../gitwc//file\") >actual.info-pwd &&\n+\t(cd svnwc && svn info \"$(pwd)/../svnwc//file\") >expected.info-pwd &&\n+\t(cd gitwc && git svn info \"$(pwd)/../gitwc//file\") >actual.info-pwd &&\n \tgrep -v ^Path: <expected.info-pwd >expected.info-np &&\n \tgrep -v ^Path: <actual.info-pwd >actual.info-np &&\n \ttest_cmp_info expected.info-np actual.info-np &&\n@@ -105,56 +105,56 @@ test_expect_success 'info $(pwd)/../___wc//file' '\n \t'\n \n test_expect_success 'info --url .' '\n-\ttest \"$(cd gitwc; git svn info --url .)\" = \"$quoted_svnrepo\"\n+\ttest \"$(cd gitwc && git svn info --url .)\" = \"$quoted_svnrepo\"\n \t'\n \n test_expect_success 'info file' \"\n-\t(cd svnwc; svn info file) > expected.info-file &&\n-\t(cd gitwc; git svn info file) > actual.info-file &&\n+\t(cd svnwc && svn info file) > expected.info-file &&\n+\t(cd gitwc && git svn info file) > actual.info-file &&\n \ttest_cmp_info expected.info-file actual.info-file\n \t\"\n \n test_expect_success 'info --url file' '\n-\ttest \"$(cd gitwc; git svn info --url file)\" = \"$quoted_svnrepo/file\"\n+\ttest \"$(cd gitwc && git svn info --url file)\" = \"$quoted_svnrepo/file\"\n \t'\n \n test_expect_success 'info directory' \"\n-\t(cd svnwc; svn info directory) > expected.info-directory &&\n-\t(cd gitwc; git svn info directory) > actual.info-directory &&\n+\t(cd svnwc && svn info directory) > expected.info-directory &&\n+\t(cd gitwc && git svn info directory) > actual.info-directory &&\n \ttest_cmp_info expected.info-directory actual.info-directory\n \t\"\n \n test_expect_success 'info inside directory' \"\n-\t(cd svnwc/directory; svn info) > expected.info-inside-directory &&\n-\t(cd gitwc/directory; git svn info) > actual.info-inside-directory &&\n+\t(cd svnwc/directory && svn info) > expected.info-inside-directory &&\n+\t(cd gitwc/directory && git svn info) > actual.info-inside-directory &&\n \ttest_cmp_info expected.info-inside-directory actual.info-inside-directory\n \t\"\n \n test_expect_success 'info --url directory' '\n-\ttest \"$(cd gitwc; git svn info --url directory)\" = \"$quoted_svnrepo/directory\"\n+\ttest \"$(cd gitwc && git svn info --url directory)\" = \"$quoted_svnrepo/directory\"\n \t'\n \n test_expect_success 'info symlink-file' \"\n-\t(cd svnwc; svn info symlink-file) > expected.info-symlink-file &&\n-\t(cd gitwc; git svn info symlink-file) > actual.info-symlink-file &&\n+\t(cd svnwc && svn info symlink-file) > expected.info-symlink-file &&\n+\t(cd gitwc && git svn info symlink-file) > actual.info-symlink-file &&\n \ttest_cmp_info expected.info-symlink-file actual.info-symlink-file\n \t\"\n \n test_expect_success 'info --url symlink-file' '\n-\ttest \"$(cd gitwc; git svn info --url symlink-file)\" \\\n+\ttest \"$(cd gitwc && git svn info --url symlink-file)\" \\\n \t     = \"$quoted_svnrepo/symlink-file\"\n \t'\n \n test_expect_success 'info symlink-directory' \"\n-\t(cd svnwc; svn info symlink-directory) \\\n+\t(cd svnwc && svn info symlink-directory) \\\n \t\t> expected.info-symlink-directory &&\n-\t(cd gitwc; git svn info symlink-directory) \\\n+\t(cd gitwc && git svn info symlink-directory) \\\n \t\t> actual.info-symlink-directory &&\n \ttest_cmp_info expected.info-symlink-directory actual.info-symlink-directory\n \t\"\n \n test_expect_success 'info --url symlink-directory' '\n-\ttest \"$(cd gitwc; git svn info --url symlink-directory)\" \\\n+\ttest \"$(cd gitwc && git svn info --url symlink-directory)\" \\\n \t     = \"$quoted_svnrepo/symlink-directory\"\n \t'\n \n@@ -169,13 +169,13 @@ test_expect_success 'info added-file' \"\n \t\tcd svnwc &&\n \t\tsvn_cmd add added-file > /dev/null\n \t) &&\n-\t(cd svnwc; svn info added-file) > expected.info-added-file &&\n-\t(cd gitwc; git svn info added-file) > actual.info-added-file &&\n+\t(cd svnwc && svn info added-file) > expected.info-added-file &&\n+\t(cd gitwc && git svn info added-file) > actual.info-added-file &&\n \ttest_cmp_info expected.info-added-file actual.info-added-file\n \t\"\n \n test_expect_success 'info --url added-file' '\n-\ttest \"$(cd gitwc; git svn info --url added-file)\" \\\n+\ttest \"$(cd gitwc && git svn info --url added-file)\" \\\n \t     = \"$quoted_svnrepo/added-file\"\n \t'\n \n@@ -190,15 +190,15 @@ test_expect_success 'info added-directory' \"\n \t\tcd gitwc &&\n \t\tgit add added-directory\n \t) &&\n-\t(cd svnwc; svn info added-directory) \\\n+\t(cd svnwc && svn info added-directory) \\\n \t\t> expected.info-added-directory &&\n-\t(cd gitwc; git svn info added-directory) \\\n+\t(cd gitwc && git svn info added-directory) \\\n \t\t> actual.info-added-directory &&\n \ttest_cmp_info expected.info-added-directory actual.info-added-directory\n \t\"\n \n test_expect_success 'info --url added-directory' '\n-\ttest \"$(cd gitwc; git svn info --url added-directory)\" \\\n+\ttest \"$(cd gitwc && git svn info --url added-directory)\" \\\n \t     = \"$quoted_svnrepo/added-directory\"\n \t'\n \n@@ -213,16 +213,16 @@ test_expect_success 'info added-symlink-file' \"\n \t\tln -s added-file added-symlink-file &&\n \t\tsvn_cmd add added-symlink-file > /dev/null\n \t) &&\n-\t(cd svnwc; svn info added-symlink-file) \\\n+\t(cd svnwc && svn info added-symlink-file) \\\n \t\t> expected.info-added-symlink-file &&\n-\t(cd gitwc; git svn info added-symlink-file) \\\n+\t(cd gitwc && git svn info added-symlink-file) \\\n \t\t> actual.info-added-symlink-file &&\n \ttest_cmp_info expected.info-added-symlink-file \\\n \t\tactual.info-added-symlink-file\n \t\"\n \n test_expect_success 'info --url added-symlink-file' '\n-\ttest \"$(cd gitwc; git svn info --url added-symlink-file)\" \\\n+\ttest \"$(cd gitwc && git svn info --url added-symlink-file)\" \\\n \t     = \"$quoted_svnrepo/added-symlink-file\"\n \t'\n \n@@ -237,16 +237,16 @@ test_expect_success 'info added-symlink-directory' \"\n \t\tln -s added-directory added-symlink-directory &&\n \t\tsvn_cmd add added-symlink-directory > /dev/null\n \t) &&\n-\t(cd svnwc; svn info added-symlink-directory) \\\n+\t(cd svnwc && svn info added-symlink-directory) \\\n \t\t> expected.info-added-symlink-directory &&\n-\t(cd gitwc; git svn info added-symlink-directory) \\\n+\t(cd gitwc && git svn info added-symlink-directory) \\\n \t\t> actual.info-added-symlink-directory &&\n \ttest_cmp_info expected.info-added-symlink-directory \\\n \t\tactual.info-added-symlink-directory\n \t\"\n \n test_expect_success 'info --url added-symlink-directory' '\n-\ttest \"$(cd gitwc; git svn info --url added-symlink-directory)\" \\\n+\ttest \"$(cd gitwc && git svn info --url added-symlink-directory)\" \\\n \t     = \"$quoted_svnrepo/added-symlink-directory\"\n \t'\n \n@@ -259,13 +259,13 @@ test_expect_success 'info deleted-file' \"\n \t\tcd svnwc &&\n \t\tsvn_cmd rm --force file > /dev/null\n \t) &&\n-\t(cd svnwc; svn info file) >expected.info-deleted-file &&\n-\t(cd gitwc; git svn info file) >actual.info-deleted-file &&\n+\t(cd svnwc && svn info file) >expected.info-deleted-file &&\n+\t(cd gitwc && git svn info file) >actual.info-deleted-file &&\n \ttest_cmp_info expected.info-deleted-file actual.info-deleted-file\n \t\"\n \n test_expect_success 'info --url file (deleted)' '\n-\ttest \"$(cd gitwc; git svn info --url file)\" \\\n+\ttest \"$(cd gitwc && git svn info --url file)\" \\\n \t     = \"$quoted_svnrepo/file\"\n \t'\n \n@@ -278,13 +278,13 @@ test_expect_success 'info deleted-directory' \"\n \t\tcd svnwc &&\n \t\tsvn_cmd rm --force directory > /dev/null\n \t) &&\n-\t(cd svnwc; svn info directory) >expected.info-deleted-directory &&\n-\t(cd gitwc; git svn info directory) >actual.info-deleted-directory &&\n+\t(cd svnwc && svn info directory) >expected.info-deleted-directory &&\n+\t(cd gitwc && git svn info directory) >actual.info-deleted-directory &&\n \ttest_cmp_info expected.info-deleted-directory actual.info-deleted-directory\n \t\"\n \n test_expect_success 'info --url directory (deleted)' '\n-\ttest \"$(cd gitwc; git svn info --url directory)\" \\\n+\ttest \"$(cd gitwc && git svn info --url directory)\" \\\n \t     = \"$quoted_svnrepo/directory\"\n \t'\n \n@@ -297,13 +297,13 @@ test_expect_success 'info deleted-symlink-file' \"\n \t\tcd svnwc &&\n \t\tsvn_cmd rm --force symlink-file > /dev/null\n \t) &&\n-\t(cd svnwc; svn info symlink-file) >expected.info-deleted-symlink-file &&\n-\t(cd gitwc; git svn info symlink-file) >actual.info-deleted-symlink-file &&\n+\t(cd svnwc && svn info symlink-file) >expected.info-deleted-symlink-file &&\n+\t(cd gitwc && git svn info symlink-file) >actual.info-deleted-symlink-file &&\n \ttest_cmp_info expected.info-deleted-symlink-file actual.info-deleted-symlink-file\n \t\"\n \n test_expect_success 'info --url symlink-file (deleted)' '\n-\ttest \"$(cd gitwc; git svn info --url symlink-file)\" \\\n+\ttest \"$(cd gitwc && git svn info --url symlink-file)\" \\\n \t     = \"$quoted_svnrepo/symlink-file\"\n \t'\n \n@@ -316,13 +316,13 @@ test_expect_success 'info deleted-symlink-directory' \"\n \t\tcd svnwc &&\n \t\tsvn_cmd rm --force symlink-directory > /dev/null\n \t) &&\n-\t(cd svnwc; svn info symlink-directory) >expected.info-deleted-symlink-directory &&\n-\t(cd gitwc; git svn info symlink-directory) >actual.info-deleted-symlink-directory &&\n+\t(cd svnwc && svn info symlink-directory) >expected.info-deleted-symlink-directory &&\n+\t(cd gitwc && git svn info symlink-directory) >actual.info-deleted-symlink-directory &&\n \ttest_cmp_info expected.info-deleted-symlink-directory actual.info-deleted-symlink-directory\n \t\"\n \n test_expect_success 'info --url symlink-directory (deleted)' '\n-\ttest \"$(cd gitwc; git svn info --url symlink-directory)\" \\\n+\ttest \"$(cd gitwc && git svn info --url symlink-directory)\" \\\n \t     = \"$quoted_svnrepo/symlink-directory\"\n \t'\n \n@@ -331,27 +331,27 @@ test_expect_success 'info --url symlink-directory (deleted)' '\n \n test_expect_success 'info unknown-file' \"\n \techo two > gitwc/unknown-file &&\n-\t(cd gitwc; test_must_fail git svn info unknown-file) \\\n+\t(cd gitwc && test_must_fail git svn info unknown-file) \\\n \t\t 2> actual.info-unknown-file &&\n \tgrep unknown-file actual.info-unknown-file\n \t\"\n \n test_expect_success 'info --url unknown-file' '\n \techo two > gitwc/unknown-file &&\n-\t(cd gitwc; test_must_fail git svn info --url unknown-file) \\\n+\t(cd gitwc && test_must_fail git svn info --url unknown-file) \\\n \t\t 2> actual.info-url-unknown-file &&\n \tgrep unknown-file actual.info-url-unknown-file\n \t'\n \n test_expect_success 'info unknown-directory' \"\n \tmkdir gitwc/unknown-directory svnwc/unknown-directory &&\n-\t(cd gitwc; test_must_fail git svn info unknown-directory) \\\n+\t(cd gitwc && test_must_fail git svn info unknown-directory) \\\n \t\t 2> actual.info-unknown-directory &&\n \tgrep unknown-directory actual.info-unknown-directory\n \t\"\n \n test_expect_success 'info --url unknown-directory' '\n-\t(cd gitwc; test_must_fail git svn info --url unknown-directory) \\\n+\t(cd gitwc && test_must_fail git svn info --url unknown-directory) \\\n \t\t 2> actual.info-url-unknown-directory &&\n \tgrep unknown-directory actual.info-url-unknown-directory\n \t'\n@@ -361,13 +361,13 @@ test_expect_success 'info unknown-symlink-file' \"\n \t\tcd gitwc &&\n \t\tln -s unknown-file unknown-symlink-file\n \t) &&\n-\t(cd gitwc; test_must_fail git svn info unknown-symlink-file) \\\n+\t(cd gitwc && test_must_fail git svn info unknown-symlink-file) \\\n \t\t 2> actual.info-unknown-symlink-file &&\n \tgrep unknown-symlink-file actual.info-unknown-symlink-file\n \t\"\n \n test_expect_success 'info --url unknown-symlink-file' '\n-\t(cd gitwc; test_must_fail git svn info --url unknown-symlink-file) \\\n+\t(cd gitwc && test_must_fail git svn info --url unknown-symlink-file) \\\n \t\t 2> actual.info-url-unknown-symlink-file &&\n \tgrep unknown-symlink-file actual.info-url-unknown-symlink-file\n \t'\n@@ -377,13 +377,13 @@ test_expect_success 'info unknown-symlink-directory' \"\n \t\tcd gitwc &&\n \t\tln -s unknown-directory unknown-symlink-directory\n \t) &&\n-\t(cd gitwc; test_must_fail git svn info unknown-symlink-directory) \\\n+\t(cd gitwc && test_must_fail git svn info unknown-symlink-directory) \\\n \t\t 2> actual.info-unknown-symlink-directory &&\n \tgrep unknown-symlink-directory actual.info-unknown-symlink-directory\n \t\"\n \n test_expect_success 'info --url unknown-symlink-directory' '\n-\t(cd gitwc; test_must_fail git svn info --url unknown-symlink-directory) \\\n+\t(cd gitwc && test_must_fail git svn info --url unknown-symlink-directory) \\\n \t\t 2> actual.info-url-unknown-symlink-directory &&\n \tgrep unknown-symlink-directory actual.info-url-unknown-symlink-directory\n \t'\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350928","messageId":"20180626073001.6555-30-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:30:01Z","receivedAt":"2018-06-26T07:31:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option detects broken &&-chains by forcing the test to\nexit early (as the very first step) with a sentinel value. If that\nsentinel is the test's overall exit code, then the &&-chain is intact;\nif not, then the chain is broken. Unfortunately, this detection does not\nextend to &&-chains within subshells even when the subshell itself is\nproperly linked into the outer &&-chain.\n\nAddress this shortcoming by eliminating the subshell during the\n\"linting\" phase and incorporating its body directly into the surrounding\n&&-chain. To keep this transformation cheap, no attempt is made at\nproperly parsing shell code. Instead, the manipulations are purely\ntextual. For example:\n\n    statement1 &&\n    (\n        statement2 &&\n        statement3\n    ) &&\n    statement4\n\nis transformed to:\n\n    statement1 &&\n        statement2 &&\n        statement3 &&\n    statement4\n\nNotice how \"statement3\" gains the \"&&\" which dwelt originally on the\nclosing \") &&\" line. Since this manipulation is purely textual (in fact,\nline-by-line), special care is taken to ensure that the \"&&\" is moved to\nthe final _statement_ before the closing \")\", not necessarily the final\n_line_ of text within the subshell. For example, with a here-doc, the\n\"&&\" needs to be added to the opening \"<<EOF\" line, not to the \"EOF\"\nline which closes it.\n\nIn addition to modern subshell formatting shown above, old-style\nformatting is also recognized:\n\n    statement1 &&\n    (statement2 &&\n     statement3) &&\n    statement4\n\nHeuristics are employed to properly identify the extent of a subshell\nformatted in the old-style since a number of legitimate constructs may\nsuperficially appear to close the subshell even though they don't. For\ninstance, the \")\" in \"x=$(command) &&\" and \"case $x in *)\" is specially\nrecognized to avoid being falsely considered the end of a subshell.\n\nDue to the complexities of line-by-line detection (and limitations of\nthe tool, 'sed'), only subshells one level deep are handled, as well as\none-liner subshells one level below that. Subshells deeper than that or\nmulti-line subshells at level two are passed through as-is, thus\n&&-chains in their bodies are not checked.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/test-lib.sh | 245 +++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 244 insertions(+), 1 deletion(-)\n\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 28315706be..ade5066fff 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -664,6 +664,248 @@ test_eval_ () {\n \treturn $test_eval_ret_\n }\n \n+test_subshell_sed_='\n+# incomplete line -- slurp up next line\n+/[^\\\\]\\\\$/ {\n+      N\n+      s/\\\\\\n//\n+}\n+\n+# here-doc -- swallow it to avoid false hits within its body (but keep the\n+# command to which it was attached)\n+/<<[ \t]*[-\\\\]*EOF[ \t]*&&[ \t]*$/ {\n+\ts/<<[ \t]*[-\\\\]*EOF//\n+\th\n+\t:hereslurp\n+\tN\n+\ts/.*\\n//\n+\t/^[ \t]*EOF[ \t]*$/!bhereslurp\n+\tx\n+\t}\n+\n+# one-liner \"(... || git ...)\" or \"(... || test ...)\" -- short-hand for\n+# \"if ... then : else ...\", so leave untouched; contrast with \"(... || true)\"\n+# which ought to be replaced with \"test_might_fail ...\" to avoid breaking\n+# &&-chain\n+/^[ \t]*(..*||[ \t]*git[ \t]..*)[ \t]*&&[ \t]*$/b\n+/^[ \t]*(..*||[ \t]*git[ \t]..*)[ \t]*$/b\n+/^[ \t]*(..*||[ \t]*test..*)[ \t]*&&[ \t]*$/b\n+/^[ \t]*(..*||[ \t]*test..*)[ \t]*$/b\n+\n+# one-liner \"(... &) &&\" backgrounder -- needs to remain in subshell to avoid\n+# becoming syntactically invalid \"... & &&\"\n+/^[ \t]*(..*&[ \t]*)[ \t]*&&[ \t]*$/b\n+\n+# one-liner \"(...) &&\" -- strip \"(\" and \")\"\n+/^[ \t]*(..*)[ \t]*&&[ \t]*$/ {\n+\ts/(//\n+\ts/)[ \t]*&&[ \t]*$/ \\&\\&/\n+\tb\n+}\n+\n+# same as above but without trailing \"&&\"\n+/^[ \t]*(..*)[ \t]*$/ {\n+\ts/(//\n+\ts/)[ \t]*$//\n+\tb\n+}\n+\n+# one-liner \"(...) >x\" (or \"2>x\" or \"<x\" or \"|x\" or \"&\" -- strip \"(\" and \")\"\n+/^[ \t]*(..*)[ \t]*[0-9]*[<>|&]/ {\n+\ts/(//\n+\ts/)[ \t]*\\([0-9]*[<>|&]\\)/\\1/\n+\tb\n+}\n+\n+# multi-line \"(...\" -- strip \"(\" and pass-thru enclosed lines until \")\"\n+/^[ \t]*(/ {\n+\t# strip \"(\" and stash for later printing\n+\ts/(//\n+\th\n+\n+\t:discard\n+\tN\n+\ts/.*\\n//\n+\n+\t# loop: slurp enclosed lines\n+\t:slurp\n+\t# end-of-file\n+\t$beof\n+\t# incomplete line\n+\t/[^\\\\]\\\\$/bincomplete\n+\t# here-doc\n+\t/<<[ \t]*[-\\\\]*EOF/bheredoc\n+\t# comment or empty line -- discard since closing \") &&\" will need to\n+\t# add \"&&\" to final non-comment, non-empty subshell line\n+\t/^[ \t]*#/bdiscard\n+\t/^[ \t]*$/bdiscard\n+\t# one-liner \"case ... esac\"\n+\t/^[ \t]*case[ \t]*..*esac/bpassthru\n+\t# multi-line \"case ... esac\"\n+\t/^[ \t]*case[ \t]..*[ \t]in/bcase\n+\t# nested one-liner \"(...) &&\"\n+\t/^[ \t]*(.*)[ \t]*&&[ \t]*$/boneline\n+\t# nested one-liner \"(...)\"\n+\t/^[ \t]*(.*)[ \t]*$/boneline\n+\t# nested one-liner \"(...) >x\" (or \"2>x\" or \"<x\" or \"|x\")\n+\t/^[ \t]*(.*)[ \t]*[0-9]*[<>|]/bonelineredir\n+\t# nested multi-line \"(...\\n...)\"\n+\t/^[ \t]*(/bnest\n+\t# closing \") &&\" on own line\n+\t/^[ \t]*)[ \t]*&&[ \t]*$/bcloseamp\n+\t# closing \")\" on own line\n+\t/^[ \t]*)[ \t]*$/bclose\n+\t# closing \") >x\" (or \"2>x\" or \"<x\" or \"|x\") on own line\n+\t/^[ \t]*)[ \t]*[0-9]*[<>|]/bcloseredir\n+\t# \"$((...))\" -- not closing \")\"\n+\t/\\$(([^)][^)]*))[^)]*$/bpassthru\n+\t# \"$(...)\" -- not closing \")\"\n+\t/\\$([^)][^)]*)[^)]*$/bpassthru\n+\t# \"=(...)\" -- Bash array assignment; not closing \")\"\n+\t/=(/bpassthru\n+\t# closing \"...) &&\"\n+\t/)[ \t]*&&[ \t]*$/bcloseampx\n+\t# closing \"...)\"\n+\t/)[ \t]*$/bclosex\n+\t# closing \"...) >x\" (or \"2>x\" or \"<x\" or \"|x\")\n+\t/)[ \t]*[<>|]/bcloseredirx\n+\t:passthru\n+\t# retrieve and print previous line\n+\tx\n+\tn\n+\tbslurp\n+\n+\t# end-of-file -- must be closing \"...)\" line or empty line; if empty,\n+\t# strip \")\" from previous line, else strip \")\" from this line\n+\t:eof\n+\t/^[ \t]*$/bempty\n+\tx\n+\tp\n+\t:empty\n+\tx\n+\t/)[ \t]*[<>|]/s/[<>|]..*$//\n+\ts/)[ \t]*$//\n+\tb\n+\n+\t# found \"...\\\" -- slurp up next line\n+\t:incomplete\n+\tN\n+\ts/\\\\\\n//\n+\tbslurp\n+\n+\t# found here-doc inside subshell: when a subshell ends, we append\n+\t# \"&&\" to the final subshell line to chain it with lines outside the\n+\t# subshell, however, we cannot append \"&&\" to the ending EOF of a\n+\t# here-doc since \"&&\" belongs on the \"<<EOF\" opening line, so just\n+\t# discard the here-doc altogether (but keep the command to which it\n+\t# was attached)\n+\t:heredoc\n+\ts/<<[ \t]*[-\\\\]*EOF//\n+\tx\n+\tp\n+\t:hereslurpsub\n+\tN\n+\ts/.*\\n//\n+\t/^[ \t]*EOF[ \t]*$/bdiscard\n+\tbhereslurpsub\n+\n+\t# found \"case ... in\" -- pass-thru untouched to avoid \"...)\" arms\n+\t# being misidentified as subshell closing \")\"\n+\t:case\n+\tx\n+\tp\n+\tx\n+\t:caseslurp\n+\tn\n+\t/^[ \t]*esac/besac\n+\tbcaseslurp\n+\t:esac\n+\tx\n+\tbdiscard\n+\n+\t# found one-liner \"(...) &&\" or \"(...)\" -- strip \"(\" and \")\"\n+\t:oneline\n+\ts/(//\n+\ts/)[ \t]*\\(&&\\)*[ \t]*$/ \\1/\n+\tbpassthru\n+\n+\t# found one-liner \"(...) >x\" (or \"2>x\" or \"<x\" or \"|x\") -- strip\n+\t# \"(\" and \")\"\n+\t:onelineredir\n+\ts/(//\n+\ts/)[ \t]*\\([0-9]*[<>|]\\)/\\1/\n+\tbpassthru\n+\n+\t# found nested multi-line \"(...\\n...)\" -- pass-thru untouched\n+\t:nest\n+\tx\n+\tp\n+\tx\n+\t:nslurp\n+\tn\n+\t# closing \")\" on own line -- stop nested slurp\n+\t/^[ \t]*)/bnclose\n+\t# \"$((...))\" -- not closing \")\"\n+\t/\\$(([^)][^)]*))[^)]*$/bnslurp\n+\t# \"$(...)\" -- not closing \")\"\n+\t/\\$([^)][^)]*)[^)]*$/bnslurp\n+\t# closing \"...)\" -- stop nested slurp\n+\t/)/bnclose\n+\tbnslurp\n+\t:nclose\n+\t# stash \")\" (or \") &&\", etc.) line for later printing and drop\n+\t# leftover gunk from hold area\n+\tx\n+\tbdiscard\n+\n+\t# found \") &&\" -- drop \") &&\" and add \"&&\" to final subshell line\n+\t:closeamp\n+\tx\n+\ts/$/ \\&\\&/\n+\tb\n+\n+\t# found \")\" -- drop it and print final subshell line\n+\t:close\n+\tx\n+\tb\n+\n+\t# found \") >x\" (or \"2>x\" or \"<x\" or \"|x\" or \"|\") -- replace \")\" with\n+\t# \":\" to keep \")|\\n\" syntactically valid, and add \"&&\" to final\n+\t# subshell line\n+\t:closeredir\n+\tx\n+\ts/$/ \\&\\&/\n+\tp\n+\tx\n+\ts/)/:/\n+\tb\n+\n+\t# found \"...) &&\" -- drop \")\" but keep \"...\"\n+\t:closeampx\n+\tx\n+\tp\n+\tx\n+\ts/)[ \t]*&&[ \t]*$/ \\&\\&/\n+\tb\n+\n+\t# found \"...)\" -- drop \")\" but keep \"...\"\n+\t:closex\n+\tx\n+\tp\n+\tx\n+\ts/)[ \t]*$//\n+\tb\n+\n+\t# found \"...) >x\" (or \"2>x\" or \"<x\" or \"|x\") -- drop \")\" but keep \"...\"\n+\t:closeredirx\n+\tx\n+\tp\n+\tx\n+\ts/)[ \t]*\\([<>|]\\)/ \\&\\& : \\1/\n+\tb\n+}\n+'\n+\n test_run_ () {\n \ttest_cleanup=:\n \texpecting_failure=$2\n@@ -675,7 +917,8 @@ test_run_ () {\n \t\ttrace=\n \t\t# 117 is magic because it is unlikely to match the exit\n \t\t# code of other programs\n-\t\tif test \"OK-117\" != \"$(test_eval_ \"(exit 117) && $1${LF}${LF}echo OK-\\$?\" 3>&1)\"\n+\t\ttest_linter=$(printf '%s\\n' \"$1\" | sed -e \"$test_subshell_sed_\")\n+\t\tif test \"OK-117\" != \"$(test_eval_ \"(exit 117) && ${test_linter}${LF}${LF}echo OK-\\$?\" 3>&1)\"\n \t\tthen\n \t\t\terror \"bug in the test script: broken &&-chain or run-away HERE-DOC: $1\"\n \t\tfi\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350929","messageId":"20180626073001.6555-28-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 27/29] t9000-t9999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:59Z","receivedAt":"2018-06-26T07:31:40Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t9001-send-email.sh                           |  6 +++---\n t/t9100-git-svn-basic.sh                        |  2 +-\n t/t9101-git-svn-props.sh                        |  2 +-\n t/t9122-git-svn-author.sh                       |  6 +++---\n t/t9129-git-svn-i18n-commitencoding.sh          |  2 +-\n t/t9130-git-svn-authors-file.sh                 |  4 ++--\n t/t9134-git-svn-ignore-paths.sh                 |  6 +++---\n t/t9137-git-svn-dcommit-clobber-series.sh       |  2 +-\n t/t9138-git-svn-authors-prog.sh                 |  6 +++---\n t/t9146-git-svn-empty-dirs.sh                   | 12 ++++++------\n t/t9147-git-svn-include-paths.sh                |  6 +++---\n t/t9164-git-svn-dcommit-concurrent.sh           |  2 +-\n t/t9165-git-svn-fetch-merge-branch-of-branch.sh |  2 +-\n t/t9200-git-cvsexportcommit.sh                  |  6 +++---\n t/t9400-git-cvsserver-server.sh                 |  6 +++---\n t/t9600-cvsimport.sh                            |  2 +-\n t/t9806-git-p4-options.sh                       |  2 +-\n t/t9810-git-p4-rcs.sh                           |  2 +-\n t/t9811-git-p4-label-import.sh                  |  2 +-\n t/t9815-git-p4-submit-fail.sh                   |  2 +-\n t/t9830-git-p4-symlink-dir.sh                   |  2 +-\n t/t9831-git-p4-triggers.sh                      |  2 +-\n t/t9902-completion.sh                           |  4 ++--\n t/t9903-bash-prompt.sh                          |  2 +-\n 24 files changed, 45 insertions(+), 45 deletions(-)\n\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex 776769fe0d..53314ff54e 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -330,7 +330,7 @@ test_expect_success $PREREQ 'Show all headers' '\n \n test_expect_success $PREREQ 'Prompting works' '\n \tclean_fake_sendmail &&\n-\t(echo \"to@example.com\"\n+\t(echo \"to@example.com\" &&\n \t echo \"\"\n \t) | GIT_SEND_EMAIL_NOTTY=1 git send-email \\\n \t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\n@@ -470,8 +470,8 @@ test_expect_success $PREREQ 'Invalid In-Reply-To' '\n \n test_expect_success $PREREQ 'Valid In-Reply-To when prompting' '\n \tclean_fake_sendmail &&\n-\t(echo \"From Example <from@example.com>\"\n-\t echo \"To Example <to@example.com>\"\n+\t(echo \"From Example <from@example.com>\" &&\n+\t echo \"To Example <to@example.com>\" &&\n \t echo \"\"\n \t) | GIT_SEND_EMAIL_NOTTY=1 git send-email \\\n \t\t--smtp-server=\"$(pwd)/fake.sendmail\" \\\ndiff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh\nindex c937330a5f..9af6078844 100755\n--- a/t/t9100-git-svn-basic.sh\n+++ b/t/t9100-git-svn-basic.sh\n@@ -31,7 +31,7 @@ test_expect_success \\\n \t(\n \t\tcd import &&\n \t\techo foo >foo &&\n-\t\tln -s foo foo.link\n+\t\tln -s foo foo.link &&\n \t\tmkdir -p dir/a/b/c/d/e &&\n \t\techo \"deep dir\" >dir/a/b/c/d/e/file &&\n \t\tmkdir bar &&\ndiff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh\nindex 07bfb63777..8a5c8dc1aa 100755\n--- a/t/t9101-git-svn-props.sh\n+++ b/t/t9101-git-svn-props.sh\n@@ -149,7 +149,7 @@ test_expect_success 'test show-ignore' \"\n \t\tsvn_cmd up &&\n \t\tsvn_cmd propset -R svn:ignore '\n no-such-file*\n-' .\n+' . &&\n \t\tsvn_cmd commit -m 'propset svn:ignore'\n \t) &&\n \tgit svn show-ignore > show-ignore.got &&\ndiff --git a/t/t9122-git-svn-author.sh b/t/t9122-git-svn-author.sh\nindex 30013b7bb9..9e8fe38e7e 100755\n--- a/t/t9122-git-svn-author.sh\n+++ b/t/t9122-git-svn-author.sh\n@@ -7,8 +7,8 @@ test_expect_success 'setup svn repository' '\n \tsvn_cmd checkout \"$svnrepo\" work.svn &&\n \t(\n \t\tcd work.svn &&\n-\t\techo >file\n-\t\tsvn_cmd add file\n+\t\techo >file &&\n+\t\tsvn_cmd add file &&\n \t\tsvn_cmd commit -m \"first commit\" file\n \t)\n '\n@@ -17,7 +17,7 @@ test_expect_success 'interact with it via git svn' '\n \tmkdir work.git &&\n \t(\n \t\tcd work.git &&\n-\t\tgit svn init \"$svnrepo\"\n+\t\tgit svn init \"$svnrepo\" &&\n \t\tgit svn fetch &&\n \n \t\techo modification >file &&\ndiff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh\nindex 8dbd6476fa..2c213ae654 100755\n--- a/t/t9129-git-svn-i18n-commitencoding.sh\n+++ b/t/t9129-git-svn-i18n-commitencoding.sh\n@@ -51,7 +51,7 @@ do\n \t\tgit add F &&\n \t\tgit commit -a -F \"$TEST_DIRECTORY\"/t3900/$H.txt &&\n \t\tE=$(git cat-file commit HEAD | sed -ne \"s/^encoding //p\") &&\n-\t\ttest \"z$E\" = \"z$H\"\n+\t\ttest \"z$E\" = \"z$H\" &&\n \t\tcompare_git_head_with \"$TEST_DIRECTORY\"/t3900/$H.txt\n \t)\n \t'\ndiff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh\nindex d8262854bb..cb764bcadc 100755\n--- a/t/t9130-git-svn-authors-file.sh\n+++ b/t/t9130-git-svn-authors-file.sh\n@@ -25,7 +25,7 @@ test_expect_success 'start import with incomplete authors file' '\n \n test_expect_success 'imported 2 revisions successfully' '\n \t(\n-\t\tcd x\n+\t\tcd x &&\n \t\tgit rev-list refs/remotes/git-svn >actual &&\n \t\ttest_line_count = 2 actual &&\n \t\tgit rev-list -1 --pretty=raw refs/remotes/git-svn >actual &&\n@@ -42,7 +42,7 @@ EOF\n \n test_expect_success 'continues to import once authors have been added' '\n \t(\n-\t\tcd x\n+\t\tcd x &&\n \t\tgit svn fetch --authors-file=../svn-authors &&\n \t\tgit rev-list refs/remotes/git-svn >actual &&\n \t\ttest_line_count = 4 actual &&\ndiff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh\nindex 09ff10cd9b..fff49c4100 100755\n--- a/t/t9134-git-svn-ignore-paths.sh\n+++ b/t/t9134-git-svn-ignore-paths.sh\n@@ -82,7 +82,7 @@ test_expect_success 'update git svn-cloned repo (option ignore)' '\n test_expect_success 'SVN-side change inside of ignored www' '\n \t(\n \t\tcd s &&\n-\t\techo zaq >> www/test_www.txt\n+\t\techo zaq >> www/test_www.txt &&\n \t\tsvn_cmd commit -m \"SVN-side change inside of www/test_www.txt\" &&\n \t\tsvn_cmd up &&\n \t\tsvn_cmd log -v | fgrep \"SVN-side change inside of www/test_www.txt\"\n@@ -114,8 +114,8 @@ test_expect_success 'update git svn-cloned repo (option ignore)' '\n test_expect_success 'SVN-side change in and out of ignored www' '\n \t(\n \t\tcd s &&\n-\t\techo cvf >> www/test_www.txt\n-\t\techo ygg >> qqq/test_qqq.txt\n+\t\techo cvf >> www/test_www.txt &&\n+\t\techo ygg >> qqq/test_qqq.txt &&\n \t\tsvn_cmd commit -m \"SVN-side change in and out of ignored www\" &&\n \t\tsvn_cmd up &&\n \t\tsvn_cmd log -v | fgrep \"SVN-side change in and out of ignored www\"\ndiff --git a/t/t9137-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh\nindex 5fa07a369f..067b15bad2 100755\n--- a/t/t9137-git-svn-dcommit-clobber-series.sh\n+++ b/t/t9137-git-svn-dcommit-clobber-series.sh\n@@ -7,7 +7,7 @@ test_description='git svn dcommit clobber series'\n test_expect_success 'initialize repo' '\n \tmkdir import &&\n \t(cd import &&\n-\tawk \"BEGIN { for (i = 1; i < 64; i++) { print i } }\" > file\n+\tawk \"BEGIN { for (i = 1; i < 64; i++) { print i } }\" > file &&\n \tsvn_cmd import -m \"initial\" . \"$svnrepo\"\n \t) &&\n \tgit svn init \"$svnrepo\" &&\ndiff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh\nindex 93ef44fae8..027b416720 100755\n--- a/t/t9138-git-svn-authors-prog.sh\n+++ b/t/t9138-git-svn-authors-prog.sh\n@@ -38,7 +38,7 @@ test_expect_success 'import authors with prog and file' '\n \n test_expect_success 'imported 6 revisions successfully' '\n \t(\n-\t\tcd x\n+\t\tcd x &&\n \t\tgit rev-list refs/remotes/git-svn >actual &&\n \t\ttest_line_count = 6 actual\n \t)\n@@ -46,7 +46,7 @@ test_expect_success 'imported 6 revisions successfully' '\n \n test_expect_success 'authors-prog ran correctly' '\n \t(\n-\t\tcd x\n+\t\tcd x &&\n \t\tgit rev-list -1 --pretty=raw refs/remotes/git-svn~1 >actual &&\n \t\tgrep \"^author ee-foo <ee-foo@example\\.com> \" actual &&\n \t\tgit rev-list -1 --pretty=raw refs/remotes/git-svn~2 >actual &&\n@@ -62,7 +62,7 @@ test_expect_success 'authors-prog ran correctly' '\n \n test_expect_success 'authors-file overrode authors-prog' '\n \t(\n-\t\tcd x\n+\t\tcd x &&\n \t\tgit rev-list -1 --pretty=raw refs/remotes/git-svn >actual &&\n \t\tgrep \"^author FFFFFFF FFFFFFF <fFf@other\\.example\\.com> \" actual\n \t)\ndiff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh\nindex 6d3130e618..954948812c 100755\n--- a/t/t9146-git-svn-empty-dirs.sh\n+++ b/t/t9146-git-svn-empty-dirs.sh\n@@ -79,21 +79,21 @@ test_expect_success 'git svn mkdirs -r works' '\n \t\tdo\n \t\t\tif ! test -d \"$i\"\n \t\t\tthen\n-\t\t\t\techo >&2 \"$i does not exist\"\n+\t\t\t\techo >&2 \"$i does not exist\" &&\n \t\t\t\texit 1\n \t\t\tfi\n-\t\tdone\n+\t\tdone &&\n \n \t\tif test -d \"! !\"\n \t\tthen\n-\t\t\techo >&2 \"$i should not exist\"\n+\t\t\techo >&2 \"$i should not exist\" &&\n \t\t\texit 1\n-\t\tfi\n+\t\tfi &&\n \n \t\tgit svn mkdirs -r8 &&\n \t\tif ! test -d \"! !\"\n \t\tthen\n-\t\t\techo >&2 \"$i not exist\"\n+\t\t\techo >&2 \"$i not exist\" &&\n \t\t\texit 1\n \t\tfi\n \t)\n@@ -115,7 +115,7 @@ test_expect_success 'empty directories in trunk exist' '\n \t\tdo\n \t\t\tif ! test -d \"$i\"\n \t\t\tthen\n-\t\t\t\techo >&2 \"$i does not exist\"\n+\t\t\t\techo >&2 \"$i does not exist\" &&\n \t\t\t\texit 1\n \t\t\tfi\n \t\tdone\ndiff --git a/t/t9147-git-svn-include-paths.sh b/t/t9147-git-svn-include-paths.sh\nindex a90ff58629..d292bf9f55 100755\n--- a/t/t9147-git-svn-include-paths.sh\n+++ b/t/t9147-git-svn-include-paths.sh\n@@ -84,7 +84,7 @@ test_expect_success 'update git svn-cloned repo (option include)' '\n test_expect_success 'SVN-side change inside of ignored www' '\n \t(\n \t\tcd s &&\n-\t\techo zaq >> www/test_www.txt\n+\t\techo zaq >> www/test_www.txt &&\n \t\tsvn_cmd commit -m \"SVN-side change inside of www/test_www.txt\" &&\n \t\tsvn_cmd up &&\n \t\tsvn_cmd log -v | fgrep \"SVN-side change inside of www/test_www.txt\"\n@@ -116,8 +116,8 @@ test_expect_success 'update git svn-cloned repo (option include)' '\n test_expect_success 'SVN-side change in and out of included qqq' '\n \t(\n \t\tcd s &&\n-\t\techo cvf >> www/test_www.txt\n-\t\techo ygg >> qqq/test_qqq.txt\n+\t\techo cvf >> www/test_www.txt &&\n+\t\techo ygg >> qqq/test_qqq.txt &&\n \t\tsvn_cmd commit -m \"SVN-side change in and out of ignored www\" &&\n \t\tsvn_cmd up &&\n \t\tsvn_cmd log -v | fgrep \"SVN-side change in and out of ignored www\"\ndiff --git a/t/t9164-git-svn-dcommit-concurrent.sh b/t/t9164-git-svn-dcommit-concurrent.sh\nindex d8464d4218..90346ff4e9 100755\n--- a/t/t9164-git-svn-dcommit-concurrent.sh\n+++ b/t/t9164-git-svn-dcommit-concurrent.sh\n@@ -12,7 +12,7 @@ test_expect_success 'setup svn repository' '\n \tsvn_cmd checkout \"$svnrepo\" work.svn &&\n \t(\n \t\tcd work.svn &&\n-\t\techo >file && echo > auto_updated_file\n+\t\techo >file && echo > auto_updated_file &&\n \t\tsvn_cmd add file auto_updated_file &&\n \t\tsvn_cmd commit -m \"initial commit\"\n \t) &&\ndiff --git a/t/t9165-git-svn-fetch-merge-branch-of-branch.sh b/t/t9165-git-svn-fetch-merge-branch-of-branch.sh\nindex fa3ef3b1f7..a4813c2b09 100755\n--- a/t/t9165-git-svn-fetch-merge-branch-of-branch.sh\n+++ b/t/t9165-git-svn-fetch-merge-branch-of-branch.sh\n@@ -39,7 +39,7 @@ test_expect_success 'initialize source svn repo' '\n \t\tsvn_cmd commit -m trunk &&\n \t\tsvn_cmd switch \"$svnrepo\"/branches/branch2 &&\n \t\tsvn_cmd merge \"$svnrepo\"/trunk &&\n-\t\tsvn_cmd commit -m \"merge trunk\"\n+\t\tsvn_cmd commit -m \"merge trunk\" &&\n \t\tsvn_cmd switch \"$svnrepo\"/trunk &&\n \t\tsvn_cmd merge --reintegrate \"$svnrepo\"/branches/branch2 &&\n \t\tsvn_cmd commit -m \"merge branch2\"\ndiff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh\nindex 1319415ba8..cd61288aa1 100755\n--- a/t/t9200-git-cvsexportcommit.sh\n+++ b/t/t9200-git-cvsexportcommit.sh\n@@ -187,7 +187,7 @@ test_expect_success \\\n       git commit -a -m \"Update with spaces\" &&\n       id=$(git rev-list --max-count=1 HEAD) &&\n       (cd \"$CVSWORK\" &&\n-      git cvsexportcommit -c $id\n+      git cvsexportcommit -c $id &&\n       check_entries \"G g\" \"with spaces.png/1.2/-kb|with spaces.txt/1.2/\"\n       )'\n \n@@ -245,7 +245,7 @@ test_expect_success FILEMODE \\\n       git add G/off &&\n       git commit -a -m \"Execute test\" &&\n       (cd \"$CVSWORK\" &&\n-      git cvsexportcommit -c HEAD\n+      git cvsexportcommit -c HEAD &&\n       test -x G/on &&\n       ! test -x G/off\n       )'\n@@ -303,7 +303,7 @@ test_expect_success 're-commit a removed filename which remains in CVS attic' '\n     git add attic_gremlin &&\n     git commit -m \"Added attic_gremlin\" &&\n \tgit cvsexportcommit -w \"$CVSWORK\" -c HEAD &&\n-    (cd \"$CVSWORK\"; cvs -Q update -d) &&\n+    (cd \"$CVSWORK\" && cvs -Q update -d) &&\n     test -f \"$CVSWORK/attic_gremlin\"\n '\n \ndiff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh\nindex 06742748e9..4207e9caa5 100755\n--- a/t/t9400-git-cvsserver-server.sh\n+++ b/t/t9400-git-cvsserver-server.sh\n@@ -371,7 +371,7 @@ test_expect_success 'cvs update (merge)' \\\n   'echo Line 0 >expected &&\n    for i in 1 2 3 4 5 6 7\n    do\n-     echo Line $i >>merge\n+     echo Line $i >>merge &&\n      echo Line $i >>expected\n    done &&\n    echo Line 8 >>expected &&\n@@ -382,7 +382,7 @@ test_expect_success 'cvs update (merge)' \\\n    GIT_CONFIG=\"$git_config\" cvs -Q update &&\n    test \"$(echo $(grep merge CVS/Entries|cut -d/ -f2,3,5))\" = \"merge/1.1/\" &&\n    test_cmp merge ../merge &&\n-   ( echo Line 0; cat merge ) >merge.tmp &&\n+   ( echo Line 0 && cat merge ) >merge.tmp &&\n    mv merge.tmp merge &&\n    cd \"$WORKDIR\" &&\n    echo Line 8 >>merge &&\n@@ -410,7 +410,7 @@ do\n done\n \n test_expect_success 'cvs update (conflict merge)' \\\n-  '( echo LINE 0; cat merge ) >merge.tmp &&\n+  '( echo LINE 0 && cat merge ) >merge.tmp &&\n    mv merge.tmp merge &&\n    git add merge &&\n    git commit -q -m \"Merge test (conflict)\" &&\ndiff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh\nindex 804ce3850f..5dfee07d9a 100755\n--- a/t/t9600-cvsimport.sh\n+++ b/t/t9600-cvsimport.sh\n@@ -135,7 +135,7 @@ test_expect_success PERL 'second update has correct .git/cvs-revisions' '\n \n \t(cd module-git &&\n \t git log --format=\"o_fortuna 1.1 %H\" -1 HEAD^^ &&\n-\t git log --format=\"o_fortuna 1.2 %H\" -1 HEAD^\n+\t git log --format=\"o_fortuna 1.2 %H\" -1 HEAD^ &&\n \t git log --format=\"tick 1.1 %H\" -1 HEAD) > expected &&\n \ttest_cmp expected module-git/.git/cvs-revisions\n '\ndiff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh\nindex 1ab76c4246..3f5291b857 100755\n--- a/t/t9806-git-p4-options.sh\n+++ b/t/t9806-git-p4-options.sh\n@@ -134,7 +134,7 @@ test_expect_success 'clone --changesfile' '\n \t(\n \t\tcd \"$git\" &&\n \t\tgit log --oneline p4/master >lines &&\n-\t\ttest_line_count = 2 lines\n+\t\ttest_line_count = 2 lines &&\n \t\ttest_path_is_file file1 &&\n \t\ttest_path_is_missing file2 &&\n \t\ttest_path_is_file file3\ndiff --git a/t/t9810-git-p4-rcs.sh b/t/t9810-git-p4-rcs.sh\nindex 8134ab439b..cc53debe19 100755\n--- a/t/t9810-git-p4-rcs.sh\n+++ b/t/t9810-git-p4-rcs.sh\n@@ -161,7 +161,7 @@ test_expect_success 'cleanup after failure' '\n test_expect_success 'ktext expansion should not expand multi-line $File::' '\n \t(\n \t\tcd \"$cli\" &&\n-\t\tcat >lv.pm <<-\\EOF\n+\t\tcat >lv.pm <<-\\EOF &&\n \t\tmy $wanted = sub { my $f = $File::Find::name;\n \t\t\t\t    if ( -f && $f =~ /foo/ ) {\n \t\tEOF\ndiff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh\nindex decb66ba30..602b0a5d5c 100755\n--- a/t/t9811-git-p4-label-import.sh\n+++ b/t/t9811-git-p4-label-import.sh\n@@ -133,7 +133,7 @@ test_expect_success 'export git tags to p4' '\n \t\tp4 labels ... | grep LIGHTWEIGHT_TAG &&\n \t\tp4 label -o GIT_TAG_1 | grep \"tag created in git:xyzzy\" &&\n \t\tp4 sync ...@GIT_TAG_1 &&\n-\t\t! test -f main/f10\n+\t\t! test -f main/f10 &&\n \t\tp4 sync ...@GIT_TAG_2 &&\n \t\ttest -f main/f10\n \t)\ndiff --git a/t/t9815-git-p4-submit-fail.sh b/t/t9815-git-p4-submit-fail.sh\nindex 37b42d03a2..eaf03a6563 100755\n--- a/t/t9815-git-p4-submit-fail.sh\n+++ b/t/t9815-git-p4-submit-fail.sh\n@@ -394,7 +394,7 @@ test_expect_success 'cleanup rename after submit cancel' '\n \t(\n \t\tcd \"$cli\" &&\n \t\ttest_path_is_missing text2 &&\n-\t\tp4 fstat -T action text2 2>&1 | grep \"no such file\"\n+\t\tp4 fstat -T action text2 2>&1 | grep \"no such file\" &&\n \t\ttest_path_is_file text &&\n \t\t! p4 fstat -T action text\n \t)\ndiff --git a/t/t9830-git-p4-symlink-dir.sh b/t/t9830-git-p4-symlink-dir.sh\nindex 3dc528bb1e..2ad1b0810d 100755\n--- a/t/t9830-git-p4-symlink-dir.sh\n+++ b/t/t9830-git-p4-symlink-dir.sh\n@@ -30,7 +30,7 @@ test_expect_success 'symlinked directory' '\n \t(\n \t\tcd \"$cli\" &&\n \t\tp4 sync &&\n-\t\ttest -L some/sub/directory/subdir2\n+\t\ttest -L some/sub/directory/subdir2 &&\n \t\ttest_path_is_file some/sub/directory/subdir2/file.t\n \t)\n \ndiff --git a/t/t9831-git-p4-triggers.sh b/t/t9831-git-p4-triggers.sh\nindex bbcf14c664..be44c9751a 100755\n--- a/t/t9831-git-p4-triggers.sh\n+++ b/t/t9831-git-p4-triggers.sh\n@@ -13,7 +13,7 @@ test_expect_success 'init depot' '\n \t\tcd \"$cli\" &&\n \t\techo file1 >file1 &&\n \t\tp4 add file1 &&\n-\t\tp4 submit -d \"change 1\"\n+\t\tp4 submit -d \"change 1\" &&\n \t\techo file2 >file2 &&\n \t\tp4 add file2 &&\n \t\tp4 submit -d \"change 2\"\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex a28640ce1a..69a5810555 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -1067,7 +1067,7 @@ test_expect_success '__git_complete_refs - remote' '\n \tmaster-in-other Z\n \tEOF\n \t(\n-\t\tcur=\n+\t\tcur= &&\n \t\t__git_complete_refs --remote=other &&\n \t\tprint_comp\n \t) &&\n@@ -1086,7 +1086,7 @@ test_expect_success '__git_complete_refs - track' '\n \tmaster-in-other Z\n \tEOF\n \t(\n-\t\tcur=\n+\t\tcur= &&\n \t\t__git_complete_refs --track &&\n \t\tprint_comp\n \t) &&\ndiff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh\nindex c3b89ae783..04440685a6 100755\n--- a/t/t9903-bash-prompt.sh\n+++ b/t/t9903-bash-prompt.sh\n@@ -529,7 +529,7 @@ test_expect_success 'prompt - bash color pc mode - branch name' '\n \tprintf \"BEFORE: (${c_green}\\${__git_ps1_branch_name}${c_clear}):AFTER\\\\nmaster\" >expected &&\n \t(\n \t\tGIT_PS1_SHOWCOLORHINTS=y &&\n-\t\t__git_ps1 \"BEFORE:\" \":AFTER\" >\"$actual\"\n+\t\t__git_ps1 \"BEFORE:\" \":AFTER\" >\"$actual\" &&\n \t\tprintf \"%s\\\\n%s\" \"$PS1\" \"${__git_ps1_branch_name}\" >\"$actual\"\n \t) &&\n \ttest_cmp expected \"$actual\"\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350930","messageId":"20180626073001.6555-27-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 26/29] t7000-t7999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:58Z","receivedAt":"2018-06-26T07:32:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t7001-mv.sh                  |  2 +-\n t/t7201-co.sh                  | 40 +++++++++++++-------------\n t/t7400-submodule-basic.sh     |  2 +-\n t/t7406-submodule-update.sh    |  4 +--\n t/t7408-submodule-reference.sh |  2 +-\n t/t7501-commit.sh              | 52 +++++++++++++++++-----------------\n t/t7506-status-submodule.sh    | 10 +++----\n 7 files changed, 56 insertions(+), 56 deletions(-)\n\ndiff --git a/t/t7001-mv.sh b/t/t7001-mv.sh\nindex cc3fd2baf2..9e59e5a5dd 100755\n--- a/t/t7001-mv.sh\n+++ b/t/t7001-mv.sh\n@@ -509,7 +509,7 @@ test_expect_success 'moving nested submodules' '\n \t\ttouch nested_level1 &&\n \t\tgit init &&\n \t\tgit add . &&\n-\t\tgit commit -m \"nested level 1\"\n+\t\tgit commit -m \"nested level 1\" &&\n \t\tgit submodule add ../sub_nested_nested &&\n \t\tgit commit -m \"add nested level 2\"\n \t) &&\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 8d8a63a24b..94cb039a03 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -528,10 +528,10 @@ test_expect_success 'checkout with --merge' '\n \tcat sample >filf &&\n \tgit checkout -m -- fild file filf &&\n \t(\n-\t\techo \"<<<<<<< ours\"\n-\t\techo ourside\n-\t\techo \"=======\"\n-\t\techo theirside\n+\t\techo \"<<<<<<< ours\" &&\n+\t\techo ourside &&\n+\t\techo \"=======\" &&\n+\t\techo theirside &&\n \t\techo \">>>>>>> theirs\"\n \t) >merged &&\n \ttest_cmp expect fild &&\n@@ -549,12 +549,12 @@ test_expect_success 'checkout with --merge, in diff3 -m style' '\n \tcat sample >filf &&\n \tgit checkout -m -- fild file filf &&\n \t(\n-\t\techo \"<<<<<<< ours\"\n-\t\techo ourside\n-\t\techo \"||||||| base\"\n-\t\techo original\n-\t\techo \"=======\"\n-\t\techo theirside\n+\t\techo \"<<<<<<< ours\" &&\n+\t\techo ourside &&\n+\t\techo \"||||||| base\" &&\n+\t\techo original &&\n+\t\techo \"=======\" &&\n+\t\techo theirside &&\n \t\techo \">>>>>>> theirs\"\n \t) >merged &&\n \ttest_cmp expect fild &&\n@@ -572,10 +572,10 @@ test_expect_success 'checkout --conflict=merge, overriding config' '\n \tcat sample >filf &&\n \tgit checkout --conflict=merge -- fild file filf &&\n \t(\n-\t\techo \"<<<<<<< ours\"\n-\t\techo ourside\n-\t\techo \"=======\"\n-\t\techo theirside\n+\t\techo \"<<<<<<< ours\" &&\n+\t\techo ourside &&\n+\t\techo \"=======\" &&\n+\t\techo theirside &&\n \t\techo \">>>>>>> theirs\"\n \t) >merged &&\n \ttest_cmp expect fild &&\n@@ -593,12 +593,12 @@ test_expect_success 'checkout --conflict=diff3' '\n \tcat sample >filf &&\n \tgit checkout --conflict=diff3 -- fild file filf &&\n \t(\n-\t\techo \"<<<<<<< ours\"\n-\t\techo ourside\n-\t\techo \"||||||| base\"\n-\t\techo original\n-\t\techo \"=======\"\n-\t\techo theirside\n+\t\techo \"<<<<<<< ours\" &&\n+\t\techo ourside &&\n+\t\techo \"||||||| base\" &&\n+\t\techo original &&\n+\t\techo \"=======\" &&\n+\t\techo theirside &&\n \t\techo \">>>>>>> theirs\"\n \t) >merged &&\n \ttest_cmp expect fild &&\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex 401adaed32..76cf522a08 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -818,7 +818,7 @@ test_expect_success '../bar/a/b/c works with relative local path - ../foo/bar.gi\n \t\tcp pristine-.git-config .git/config &&\n \t\tcp pristine-.gitmodules .gitmodules &&\n \t\tmkdir -p a/b/c &&\n-\t\t(cd a/b/c; git init) &&\n+\t\t(cd a/b/c && git init) &&\n \t\tgit config remote.origin.url ../foo/bar.git &&\n \t\tgit submodule add ../bar/a/b/c ./a/b/c &&\n \t\tgit submodule init &&\ndiff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\nindex 9e0d31700e..b141145fc2 100755\n--- a/t/t7406-submodule-update.sh\n+++ b/t/t7406-submodule-update.sh\n@@ -865,9 +865,9 @@ test_expect_success 'submodule update places git-dir in superprojects git-dir re\n \t (cd submodule/subsubmodule &&\n \t  git log > ../../expected\n \t ) &&\n-\t (cd .git/modules/submodule/modules/subsubmodule\n+\t (cd .git/modules/submodule/modules/subsubmodule &&\n \t  git log > ../../../../../actual\n-\t )\n+\t ) &&\n \t test_cmp actual expected\n \t)\n '\ndiff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh\nindex 08d9add05e..34ac28c056 100755\n--- a/t/t7408-submodule-reference.sh\n+++ b/t/t7408-submodule-reference.sh\n@@ -148,7 +148,7 @@ test_expect_success 'preparing second superproject with a nested submodule plus\n \t\tcd supersuper &&\n \t\techo \"I am super super.\" >file &&\n \t\tgit add file &&\n-\t\tgit commit -m B-super-super-initial\n+\t\tgit commit -m B-super-super-initial &&\n \t\tgit submodule add \"file://$base_dir/super\" subwithsub &&\n \t\tgit commit -m B-super-super-added &&\n \t\tgit submodule update --init --recursive &&\ndiff --git a/t/t7501-commit.sh b/t/t7501-commit.sh\nindex 589b6cea23..39b14b2bdb 100755\n--- a/t/t7501-commit.sh\n+++ b/t/t7501-commit.sh\n@@ -411,8 +411,8 @@ test_expect_success 'sign off (1)' '\n \tgit commit -s -m \"thank you\" &&\n \tgit cat-file commit HEAD | sed -e \"1,/^\\$/d\" >actual &&\n \t(\n-\t\techo thank you\n-\t\techo\n+\t\techo thank you &&\n+\t\techo &&\n \t\tgit var GIT_COMMITTER_IDENT |\n \t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\"\n \t) >expected &&\n@@ -430,9 +430,9 @@ test_expect_success 'sign off (2)' '\n $existing\" &&\n \tgit cat-file commit HEAD | sed -e \"1,/^\\$/d\" >actual &&\n \t(\n-\t\techo thank you\n-\t\techo\n-\t\techo $existing\n+\t\techo thank you &&\n+\t\techo &&\n+\t\techo $existing &&\n \t\tgit var GIT_COMMITTER_IDENT |\n \t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\"\n \t) >expected &&\n@@ -450,9 +450,9 @@ test_expect_success 'signoff gap' '\n $alt\" &&\n \tgit cat-file commit HEAD | sed -e \"1,/^\\$/d\" > actual &&\n \t(\n-\t\techo welcome\n-\t\techo\n-\t\techo $alt\n+\t\techo welcome &&\n+\t\techo &&\n+\t\techo $alt &&\n \t\tgit var GIT_COMMITTER_IDENT |\n \t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\"\n \t) >expected &&\n@@ -470,11 +470,11 @@ We have now\n $alt\" &&\n \tgit cat-file commit HEAD | sed -e \"1,/^\\$/d\" > actual &&\n \t(\n-\t\techo welcome\n-\t\techo\n-\t\techo We have now\n-\t\techo $alt\n-\t\techo\n+\t\techo welcome &&\n+\t\techo &&\n+\t\techo We have now &&\n+\t\techo $alt &&\n+\t\techo &&\n \t\tgit var GIT_COMMITTER_IDENT |\n \t\tsed -e \"s/>.*/>/\" -e \"s/^/Signed-off-by: /\"\n \t) >expected &&\n@@ -491,11 +491,11 @@ non-trailer line\n Myfooter: x\" &&\n \tgit cat-file commit HEAD | sed -e \"1,/^\\$/d\" > actual &&\n \t(\n-\t\techo subject\n-\t\techo\n-\t\techo non-trailer line\n-\t\techo Myfooter: x\n-\t\techo\n+\t\techo subject &&\n+\t\techo &&\n+\t\techo non-trailer line &&\n+\t\techo Myfooter: x &&\n+\t\techo &&\n \t\techo \"Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -508,10 +508,10 @@ non-trailer line\n Myfooter: x\" &&\n \tgit cat-file commit HEAD | sed -e \"1,/^\\$/d\" > actual &&\n \t(\n-\t\techo subject\n-\t\techo\n-\t\techo non-trailer line\n-\t\techo Myfooter: x\n+\t\techo subject &&\n+\t\techo &&\n+\t\techo non-trailer line &&\n+\t\techo Myfooter: x &&\n \t\techo \"Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -524,10 +524,10 @@ test_expect_success 'multiple -m' '\n \tgit commit -m \"one\" -m \"two\" -m \"three\" &&\n \tgit cat-file commit HEAD | sed -e \"1,/^\\$/d\" >actual &&\n \t(\n-\t\techo one\n-\t\techo\n-\t\techo two\n-\t\techo\n+\t\techo one &&\n+\t\techo &&\n+\t\techo two &&\n+\t\techo &&\n \t\techo three\n \t) >expected &&\n \ttest_cmp expected actual\ndiff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh\nindex b4b74dbe29..943708fb04 100755\n--- a/t/t7506-status-submodule.sh\n+++ b/t/t7506-status-submodule.sh\n@@ -193,9 +193,9 @@ test_expect_success 'status with added and untracked file in modified submodule\n \n test_expect_success 'setup .git file for sub' '\n \t(cd sub &&\n-\t rm -f new-file\n+\t rm -f new-file &&\n \t REAL=\"$(pwd)/../.real\" &&\n-\t mv .git \"$REAL\"\n+\t mv .git \"$REAL\" &&\n \t echo \"gitdir: $REAL\" >.git) &&\n \t echo .real >>.gitignore &&\n \t git commit -m \"added .real to .gitignore\" .gitignore\n@@ -209,12 +209,12 @@ test_expect_success 'status with added file in modified submodule with .git file\n \n test_expect_success 'status with a lot of untracked files in the submodule' '\n \t(\n-\t\tcd sub\n+\t\tcd sub &&\n \t\ti=0 &&\n \t\twhile test $i -lt 1024\n \t\tdo\n-\t\t\t>some-file-$i\n-\t\t\ti=$(( $i + 1 ))\n+\t\t\t>some-file-$i &&\n+\t\t\ti=$(( $i + 1 )) || exit 1\n \t\tdone\n \t) &&\n \tgit status --porcelain sub 2>err.actual &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350931","messageId":"20180626073001.6555-26-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 25/29] t6000-t6999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:57Z","receivedAt":"2018-06-26T07:32:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t6010-merge-base.sh                |  2 +-\n t/t6029-merge-subtree.sh             | 16 ++++++++--------\n t/t6036-recursive-corner-cases.sh    |  6 +++---\n t/t6042-merge-rename-corner-cases.sh |  8 ++++----\n t/t6043-merge-rename-directories.sh  |  2 +-\n 5 files changed, 17 insertions(+), 17 deletions(-)\n\ndiff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh\nindex aa2d360ce3..44c726ea39 100755\n--- a/t/t6010-merge-base.sh\n+++ b/t/t6010-merge-base.sh\n@@ -245,7 +245,7 @@ test_expect_success 'using reflog to find the fork point' '\n \t\t\tgit commit --allow-empty -m \"Derived #$count\" &&\n \t\t\tgit rev-parse HEAD >derived$count &&\n \t\t\tgit checkout -B base $E || exit 1\n-\t\tdone\n+\t\tdone &&\n \n \t\tfor count in 1 2 3\n \t\tdo\ndiff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh\nindex 3e692454a7..7d5bc78472 100755\n--- a/t/t6029-merge-subtree.sh\n+++ b/t/t6029-merge-subtree.sh\n@@ -55,7 +55,7 @@ test_expect_success 'initial merge' '\n \tgit checkout -b work &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\"\n+\t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\" &&\n \t\techo \"100644 $o2 0\tgit.c\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -72,7 +72,7 @@ test_expect_success 'merge update' '\n \tgit pull -s subtree gui master2 &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\"\n+\t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n \t\techo \"100644 $o2 0\tgit.c\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -88,8 +88,8 @@ test_expect_success 'initial ambiguous subtree' '\n \tgit checkout -b work2 &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\"\n-\t\techo \"100644 $o1 0\tgit-gui2/git-gui.sh\"\n+\t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\" &&\n+\t\techo \"100644 $o1 0\tgit-gui2/git-gui.sh\" &&\n \t\techo \"100644 $o2 0\tgit.c\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -101,8 +101,8 @@ test_expect_success 'merge using explicit' '\n \tgit pull -Xsubtree=git-gui gui master2 &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\"\n-\t\techo \"100644 $o1 0\tgit-gui2/git-gui.sh\"\n+\t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n+\t\techo \"100644 $o1 0\tgit-gui2/git-gui.sh\" &&\n \t\techo \"100644 $o2 0\tgit.c\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -114,8 +114,8 @@ test_expect_success 'merge2 using explicit' '\n \tgit pull -Xsubtree=git-gui2 gui master2 &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\"\n-\t\techo \"100644 $o3 0\tgit-gui2/git-gui.sh\"\n+\t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\" &&\n+\t\techo \"100644 $o3 0\tgit-gui2/git-gui.sh\" &&\n \t\techo \"100644 $o2 0\tgit.c\"\n \t) >expected &&\n \ttest_cmp expected actual\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex b32ff8e1db..892cf08743 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -72,7 +72,7 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n \t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0\n+\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -148,7 +148,7 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n \t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0\n+\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -228,7 +228,7 @@ test_expect_success 'git detects differently handled merges conflict' '\n \t\t\tD:new_a  E:new_a &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:new_a :3:new_a &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n \n \t\tgit cat-file -p B:new_a >ours &&\n \t\tgit cat-file -p C:new_a >theirs &&\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 1cbd946fc2..661b633478 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -352,7 +352,7 @@ test_expect_success 'rename/directory conflict + content merge conflict' '\n \t\t\tbase:file   left-conflict:newfile  right:file &&\n \t\tgit rev-parse >actual                                 \\\n \t\t\t:1:newfile  :2:newfile             :3:newfile &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_file newfile/realfile &&\n \t\ttest_path_is_file newfile~HEAD\n@@ -580,7 +580,7 @@ test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge'\n \t\t\tC:a   A:a   B:b   C:C &&\n \t\tgit rev-parse >actual          \\\n \t\t\t:3:a  :1:a  :2:b  :3:c &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_file a &&\n \t\ttest_path_is_file b &&\n@@ -680,13 +680,13 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\t\tA:a   C:b   B:b   C:c   B:c &&\n \t\tgit rev-parse >actual                \\\n \t\t\t:1:a  :2:b  :3:b  :2:c  :3:c &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n \n \t\tgit rev-parse >expect               \\\n \t\t\tC:c     B:c     C:b     B:b &&\n \t\tgit hash-object >actual                \\\n \t\t\tc~HEAD  c~B\\^0  b~HEAD  b~B\\^0 &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing b &&\n \t\ttest_path_is_missing c\ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex 2e28f2908d..4a71f17edd 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -3583,7 +3583,7 @@ test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conf\n \t\tgrep -q stuff z/c &&\n \t\ttest_seq 1 10 >expected &&\n \t\techo stuff >>expected &&\n-\t\ttest_cmp expected z/c\n+\t\ttest_cmp expected z/c &&\n \n \t\tgit ls-files -s >out &&\n \t\ttest_line_count = 4 out &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350932","messageId":"20180626073001.6555-20-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 19/29] t1000-t1999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:51Z","receivedAt":"2018-06-26T07:32:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t1004-read-tree-m-u-wf.sh         |  8 ++++----\n t/t1005-read-tree-reset.sh          | 10 +++++-----\n t/t1008-read-tree-overlay.sh        |  2 +-\n t/t1020-subdirectory.sh             |  2 +-\n t/t1050-large.sh                    |  6 +++---\n t/t1411-reflog-show.sh              |  6 +++---\n t/t1512-rev-parse-disambiguation.sh |  6 +++---\n 7 files changed, 20 insertions(+), 20 deletions(-)\n\ndiff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh\nindex c7ce5d8bb5..a479549fb6 100755\n--- a/t/t1004-read-tree-m-u-wf.sh\n+++ b/t/t1004-read-tree-m-u-wf.sh\n@@ -212,10 +212,10 @@ test_expect_success 'D/F' '\n \tread_tree_u_must_succeed -m -u branch-point side-b side-a &&\n \tgit ls-files -u >actual &&\n \t(\n-\t\ta=$(git rev-parse branch-point:subdir/file2)\n-\t\tb=$(git rev-parse side-a:subdir/file2/another)\n-\t\techo \"100644 $a 1\tsubdir/file2\"\n-\t\techo \"100644 $a 2\tsubdir/file2\"\n+\t\ta=$(git rev-parse branch-point:subdir/file2) &&\n+\t\tb=$(git rev-parse side-a:subdir/file2/another) &&\n+\t\techo \"100644 $a 1\tsubdir/file2\" &&\n+\t\techo \"100644 $a 2\tsubdir/file2\" &&\n \t\techo \"100644 $b 3\tsubdir/file2/another\"\n \t) >expect &&\n \ttest_cmp expect actual\ndiff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh\nindex 074568500a..83b09e1310 100755\n--- a/t/t1005-read-tree-reset.sh\n+++ b/t/t1005-read-tree-reset.sh\n@@ -33,7 +33,7 @@ test_expect_success 'reset should remove remnants from a failed merge' '\n \tgit ls-files -s >expect &&\n \tsha1=$(git rev-parse :new) &&\n \t(\n-\t\techo \"100644 $sha1 1\told\"\n+\t\techo \"100644 $sha1 1\told\" &&\n \t\techo \"100644 $sha1 3\told\"\n \t) | git update-index --index-info &&\n \t>old &&\n@@ -48,7 +48,7 @@ test_expect_success 'two-way reset should remove remnants too' '\n \tgit ls-files -s >expect &&\n \tsha1=$(git rev-parse :new) &&\n \t(\n-\t\techo \"100644 $sha1 1\told\"\n+\t\techo \"100644 $sha1 1\told\" &&\n \t\techo \"100644 $sha1 3\told\"\n \t) | git update-index --index-info &&\n \t>old &&\n@@ -63,7 +63,7 @@ test_expect_success 'Porcelain reset should remove remnants too' '\n \tgit ls-files -s >expect &&\n \tsha1=$(git rev-parse :new) &&\n \t(\n-\t\techo \"100644 $sha1 1\told\"\n+\t\techo \"100644 $sha1 1\told\" &&\n \t\techo \"100644 $sha1 3\told\"\n \t) | git update-index --index-info &&\n \t>old &&\n@@ -78,7 +78,7 @@ test_expect_success 'Porcelain checkout -f should remove remnants too' '\n \tgit ls-files -s >expect &&\n \tsha1=$(git rev-parse :new) &&\n \t(\n-\t\techo \"100644 $sha1 1\told\"\n+\t\techo \"100644 $sha1 1\told\" &&\n \t\techo \"100644 $sha1 3\told\"\n \t) | git update-index --index-info &&\n \t>old &&\n@@ -93,7 +93,7 @@ test_expect_success 'Porcelain checkout -f HEAD should remove remnants too' '\n \tgit ls-files -s >expect &&\n \tsha1=$(git rev-parse :new) &&\n \t(\n-\t\techo \"100644 $sha1 1\told\"\n+\t\techo \"100644 $sha1 1\told\" &&\n \t\techo \"100644 $sha1 3\told\"\n \t) | git update-index --index-info &&\n \t>old &&\ndiff --git a/t/t1008-read-tree-overlay.sh b/t/t1008-read-tree-overlay.sh\nindex 4c50ed955e..e74b185b6c 100755\n--- a/t/t1008-read-tree-overlay.sh\n+++ b/t/t1008-read-tree-overlay.sh\n@@ -23,7 +23,7 @@ test_expect_success setup '\n \n test_expect_success 'multi-read' '\n \tread_tree_must_succeed initial master side &&\n-\t(echo a; echo b/c) >expect &&\n+\t(echo a && echo b/c) >expect &&\n \tgit ls-files >actual &&\n \ttest_cmp expect actual\n '\ndiff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh\nindex df3183ea1a..c2df75e495 100755\n--- a/t/t1020-subdirectory.sh\n+++ b/t/t1020-subdirectory.sh\n@@ -148,7 +148,7 @@ test_expect_success 'GIT_PREFIX for built-ins' '\n \t(\n \t\tcd dir &&\n \t\techo \"change\" >two &&\n-\t\tGIT_EXTERNAL_DIFF=./diff git diff >../actual\n+\t\tGIT_EXTERNAL_DIFF=./diff git diff >../actual &&\n \t\tgit checkout -- two\n \t) &&\n \ttest_cmp expect actual\ndiff --git a/t/t1050-large.sh b/t/t1050-large.sh\nindex f9eb143f43..1a9b21b293 100755\n--- a/t/t1050-large.sh\n+++ b/t/t1050-large.sh\n@@ -108,7 +108,7 @@ test_expect_success 'packsize limit' '\n \t\ttest-tool genrandom \"c\" $(( 128 * 1024 )) >mid3 &&\n \t\tgit add mid1 mid2 mid3 &&\n \n-\t\tcount=0\n+\t\tcount=0 &&\n \t\tfor pi in .git/objects/pack/pack-*.idx\n \t\tdo\n \t\t\ttest -f \"$pi\" && count=$(( $count + 1 ))\n@@ -116,8 +116,8 @@ test_expect_success 'packsize limit' '\n \t\ttest $count = 2 &&\n \n \t\t(\n-\t\t\tgit hash-object --stdin <mid1\n-\t\t\tgit hash-object --stdin <mid2\n+\t\t\tgit hash-object --stdin <mid1 &&\n+\t\t\tgit hash-object --stdin <mid2 &&\n \t\t\tgit hash-object --stdin <mid3\n \t\t) |\n \t\tsort >expect &&\ndiff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh\nindex 596907758d..4d62ceef9c 100755\n--- a/t/t1411-reflog-show.sh\n+++ b/t/t1411-reflog-show.sh\n@@ -159,9 +159,9 @@ test_expect_success 'git log -g -p shows diffs vs. parents' '\n \tgit log -1 -p HEAD^ >log.one &&\n \tgit log -1 -p HEAD >log.two &&\n \t(\n-\t\tcat log.one; echo\n-\t\tcat log.two; echo\n-\t\tcat log.one; echo\n+\t\tcat log.one && echo &&\n+\t\tcat log.two && echo &&\n+\t\tcat log.one && echo &&\n \t\tcat log.two\n \t) >expect &&\n \ttest_cmp expect actual\ndiff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh\nindex 96fe3754c8..e4d5b56014 100755\n--- a/t/t1512-rev-parse-disambiguation.sh\n+++ b/t/t1512-rev-parse-disambiguation.sh\n@@ -34,8 +34,8 @@ test_expect_success 'blob and tree' '\n \t\tfor i in 0 1 2 3 4 5 6 7 8 9\n \t\tdo\n \t\t\techo $i\n-\t\tdone\n-\t\techo\n+\t\tdone &&\n+\t\techo &&\n \t\techo b1rwzyc3\n \t) >a0blgqsjc &&\n \n@@ -222,7 +222,7 @@ test_expect_success 'more history' '\n \n \ttest_might_fail git rm -f a0blgqsjc &&\n \t(\n-\t\tgit cat-file blob $side:f5518nwu\n+\t\tgit cat-file blob $side:f5518nwu &&\n \t\techo j3l0i9s6\n \t) >ab2gs879 &&\n \tgit add ab2gs879 &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350933","messageId":"20180626073001.6555-23-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 22/29] t3030: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:54Z","receivedAt":"2018-06-26T07:32:26Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t3030-merge-recursive.sh | 340 ++++++++++++++++++-------------------\n 1 file changed, 170 insertions(+), 170 deletions(-)\n\ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex 3563e77b37..ff641b348a 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -36,15 +36,15 @@ test_expect_success 'setup 1' '\n \ttest_tick &&\n \tgit commit -m \"master modifies a and d/e\" &&\n \tc1=$(git rev-parse --verify HEAD) &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o1\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o1\td/e\"\n-\t\techo \"100644 $o1 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o1\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o1\td/e\" &&\n+\t\techo \"100644 $o1 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -54,15 +54,15 @@ test_expect_success 'setup 2' '\n \n \trm -rf [abcd] &&\n \tgit checkout side &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o0 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o0\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o0 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -75,15 +75,15 @@ test_expect_success 'setup 2' '\n \ttest_tick &&\n \tgit commit -m \"side modifies a\" &&\n \tc2=$(git rev-parse --verify HEAD) &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o2\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o2 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o2\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o2 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -93,15 +93,15 @@ test_expect_success 'setup 3' '\n \n \trm -rf [abcd] &&\n \tgit checkout df-1 &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o0 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o0\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o0 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -112,15 +112,15 @@ test_expect_success 'setup 3' '\n \ttest_tick &&\n \tgit commit -m \"df-1 makes b/c\" &&\n \tc3=$(git rev-parse --verify HEAD) &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\ta\"\n-\t\techo \"100644 blob $o3\tb/c\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o0 0\ta\"\n-\t\techo \"100644 $o3 0\tb/c\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o0\ta\" &&\n+\t\techo \"100644 blob $o3\tb/c\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o0 0\ta\" &&\n+\t\techo \"100644 $o3 0\tb/c\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -130,15 +130,15 @@ test_expect_success 'setup 4' '\n \n \trm -rf [abcd] &&\n \tgit checkout df-2 &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o0 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o0\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o0 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -149,15 +149,15 @@ test_expect_success 'setup 4' '\n \ttest_tick &&\n \tgit commit -m \"df-2 makes a/c\" &&\n \tc4=$(git rev-parse --verify HEAD) &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o4\ta/c\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o4 0\ta/c\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o4\ta/c\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o4 0\ta/c\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -167,15 +167,15 @@ test_expect_success 'setup 5' '\n \n \trm -rf [abcd] &&\n \tgit checkout remove &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o0 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o0\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o0 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -190,13 +190,13 @@ test_expect_success 'setup 5' '\n \ttest_tick &&\n \tgit commit -m \"remove removes b and modifies a\" &&\n \tc5=$(git rev-parse --verify HEAD) &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o5\ta\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o5 0\ta\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o5\ta\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o5 0\ta\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -207,15 +207,15 @@ test_expect_success 'setup 6' '\n \n \trm -rf [abcd] &&\n \tgit checkout df-3 &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 $o0 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o0\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 $o0 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o0 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -226,15 +226,15 @@ test_expect_success 'setup 6' '\n \ttest_tick &&\n \tgit commit -m \"df-3 makes d\" &&\n \tc6=$(git rev-parse --verify HEAD) &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o6\td\"\n-\t\techo \"100644 $o0 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 blob $o0\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o6\td\" &&\n+\t\techo \"100644 $o0 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o6 0\td\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -286,11 +286,11 @@ test_expect_success 'merge-recursive result' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o0 1\ta\"\n-\t\techo \"100644 $o2 2\ta\"\n-\t\techo \"100644 $o1 3\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o0 1\ta\" &&\n+\t\techo \"100644 $o2 2\ta\" &&\n+\t\techo \"100644 $o1 3\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -325,10 +325,10 @@ test_expect_success 'merge-recursive remove conflict' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o0 1\ta\"\n-\t\techo \"100644 $o1 2\ta\"\n-\t\techo \"100644 $o5 3\ta\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o0 1\ta\" &&\n+\t\techo \"100644 $o1 2\ta\" &&\n+\t\techo \"100644 $o5 3\ta\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -347,9 +347,9 @@ test_expect_success 'merge-recursive result' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\ta\"\n-\t\techo \"100644 $o3 0\tb/c\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o1 0\ta\" &&\n+\t\techo \"100644 $o3 0\tb/c\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -369,11 +369,11 @@ test_expect_success 'merge-recursive d/f conflict result' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o0 1\ta\"\n-\t\techo \"100644 $o1 2\ta\"\n-\t\techo \"100644 $o4 0\ta/c\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o0 1\ta\" &&\n+\t\techo \"100644 $o1 2\ta\" &&\n+\t\techo \"100644 $o4 0\ta/c\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -393,11 +393,11 @@ test_expect_success 'merge-recursive d/f conflict result the other way' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o0 1\ta\"\n-\t\techo \"100644 $o1 3\ta\"\n-\t\techo \"100644 $o4 0\ta/c\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o0 1\ta\" &&\n+\t\techo \"100644 $o1 3\ta\" &&\n+\t\techo \"100644 $o4 0\ta/c\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -417,11 +417,11 @@ test_expect_success 'merge-recursive d/f conflict result' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n-\t\techo \"100644 $o6 3\td\"\n-\t\techo \"100644 $o0 1\td/e\"\n+\t\techo \"100644 $o1 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n+\t\techo \"100644 $o6 3\td\" &&\n+\t\techo \"100644 $o0 1\td/e\" &&\n \t\techo \"100644 $o1 2\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -441,11 +441,11 @@ test_expect_success 'merge-recursive d/f conflict result' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n-\t\techo \"100644 $o6 2\td\"\n-\t\techo \"100644 $o0 1\td/e\"\n+\t\techo \"100644 $o1 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n+\t\techo \"100644 $o6 2\td\" &&\n+\t\techo \"100644 $o0 1\td/e\" &&\n \t\techo \"100644 $o1 3\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -465,13 +465,13 @@ test_expect_success 'reset and bind merge' '\n \tgit read-tree --prefix=M/ master &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\tM/a\"\n-\t\techo \"100644 $o0 0\tM/b\"\n-\t\techo \"100644 $o0 0\tM/c\"\n-\t\techo \"100644 $o1 0\tM/d/e\"\n-\t\techo \"100644 $o1 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o1 0\tM/a\" &&\n+\t\techo \"100644 $o0 0\tM/b\" &&\n+\t\techo \"100644 $o0 0\tM/c\" &&\n+\t\techo \"100644 $o1 0\tM/d/e\" &&\n+\t\techo \"100644 $o1 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -479,17 +479,17 @@ test_expect_success 'reset and bind merge' '\n \tgit read-tree --prefix=a1/ master &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\tM/a\"\n-\t\techo \"100644 $o0 0\tM/b\"\n-\t\techo \"100644 $o0 0\tM/c\"\n-\t\techo \"100644 $o1 0\tM/d/e\"\n-\t\techo \"100644 $o1 0\ta\"\n-\t\techo \"100644 $o1 0\ta1/a\"\n-\t\techo \"100644 $o0 0\ta1/b\"\n-\t\techo \"100644 $o0 0\ta1/c\"\n-\t\techo \"100644 $o1 0\ta1/d/e\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o1 0\tM/a\" &&\n+\t\techo \"100644 $o0 0\tM/b\" &&\n+\t\techo \"100644 $o0 0\tM/c\" &&\n+\t\techo \"100644 $o1 0\tM/d/e\" &&\n+\t\techo \"100644 $o1 0\ta\" &&\n+\t\techo \"100644 $o1 0\ta1/a\" &&\n+\t\techo \"100644 $o0 0\ta1/b\" &&\n+\t\techo \"100644 $o0 0\ta1/c\" &&\n+\t\techo \"100644 $o1 0\ta1/d/e\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"100644 $o1 0\td/e\"\n \t) >expected &&\n \ttest_cmp expected actual &&\n@@ -497,21 +497,21 @@ test_expect_success 'reset and bind merge' '\n \tgit read-tree --prefix=z/ master &&\n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o1 0\tM/a\"\n-\t\techo \"100644 $o0 0\tM/b\"\n-\t\techo \"100644 $o0 0\tM/c\"\n-\t\techo \"100644 $o1 0\tM/d/e\"\n-\t\techo \"100644 $o1 0\ta\"\n-\t\techo \"100644 $o1 0\ta1/a\"\n-\t\techo \"100644 $o0 0\ta1/b\"\n-\t\techo \"100644 $o0 0\ta1/c\"\n-\t\techo \"100644 $o1 0\ta1/d/e\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n-\t\techo \"100644 $o1 0\td/e\"\n-\t\techo \"100644 $o1 0\tz/a\"\n-\t\techo \"100644 $o0 0\tz/b\"\n-\t\techo \"100644 $o0 0\tz/c\"\n+\t\techo \"100644 $o1 0\tM/a\" &&\n+\t\techo \"100644 $o0 0\tM/b\" &&\n+\t\techo \"100644 $o0 0\tM/c\" &&\n+\t\techo \"100644 $o1 0\tM/d/e\" &&\n+\t\techo \"100644 $o1 0\ta\" &&\n+\t\techo \"100644 $o1 0\ta1/a\" &&\n+\t\techo \"100644 $o0 0\ta1/b\" &&\n+\t\techo \"100644 $o0 0\ta1/c\" &&\n+\t\techo \"100644 $o1 0\ta1/d/e\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n+\t\techo \"100644 $o1 0\td/e\" &&\n+\t\techo \"100644 $o1 0\tz/a\" &&\n+\t\techo \"100644 $o0 0\tz/b\" &&\n+\t\techo \"100644 $o0 0\tz/c\" &&\n \t\techo \"100644 $o1 0\tz/d/e\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -589,8 +589,8 @@ test_expect_success 'merge-recursive simple w/submodule result' '\n \n \tgit ls-files -s >actual &&\n \t(\n-\t\techo \"100644 $o5 0\ta\"\n-\t\techo \"100644 $o0 0\tc\"\n+\t\techo \"100644 $o5 0\ta\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n \t\techo \"160000 $c1 0\td\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -601,13 +601,13 @@ test_expect_success 'merge-recursive copy vs. rename' '\n \tgit merge rename &&\n \t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 blob $o0\te\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n-\t\techo \"100644 $o0 0\td/e\"\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 blob $o0\te\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n+\t\techo \"100644 $o0 0\td/e\" &&\n \t\techo \"100644 $o0 0\te\"\n \t) >expected &&\n \ttest_cmp expected actual\n@@ -617,17 +617,17 @@ test_expect_failure 'merge-recursive rename vs. rename/symlink' '\n \n \tgit checkout -f rename &&\n \tgit merge rename-ln &&\n-\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t( git ls-tree -r HEAD && git ls-files -s ) >actual &&\n \t(\n-\t\techo \"120000 blob $oln\ta\"\n-\t\techo \"100644 blob $o0\tb\"\n-\t\techo \"100644 blob $o0\tc\"\n-\t\techo \"100644 blob $o0\td/e\"\n-\t\techo \"100644 blob $o0\te\"\n-\t\techo \"120000 $oln 0\ta\"\n-\t\techo \"100644 $o0 0\tb\"\n-\t\techo \"100644 $o0 0\tc\"\n-\t\techo \"100644 $o0 0\td/e\"\n+\t\techo \"120000 blob $oln\ta\" &&\n+\t\techo \"100644 blob $o0\tb\" &&\n+\t\techo \"100644 blob $o0\tc\" &&\n+\t\techo \"100644 blob $o0\td/e\" &&\n+\t\techo \"100644 blob $o0\te\" &&\n+\t\techo \"120000 $oln 0\ta\" &&\n+\t\techo \"100644 $o0 0\tb\" &&\n+\t\techo \"100644 $o0 0\tc\" &&\n+\t\techo \"100644 $o0 0\td/e\" &&\n \t\techo \"100644 $o0 0\te\"\n \t) >expected &&\n \ttest_cmp expected actual\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350934","messageId":"20180626073001.6555-24-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 23/29] t4000-t4999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:55Z","receivedAt":"2018-06-26T07:32:28Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t4001-diff-rename.sh                       | 2 +-\n t/t4025-hunk-header.sh                       | 8 ++++----\n t/t4041-diff-submodule-option.sh             | 4 ++--\n t/t4060-diff-submodule-option-diff-format.sh | 2 +-\n t/t4121-apply-diffs.sh                       | 2 +-\n 5 files changed, 9 insertions(+), 9 deletions(-)\n\ndiff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh\nindex bf4030371a..c16486a9d4 100755\n--- a/t/t4001-diff-rename.sh\n+++ b/t/t4001-diff-rename.sh\n@@ -180,7 +180,7 @@ test_expect_success 'setup for many rename source candidates' '\n \tgit add \"path??\" &&\n \ttest_tick &&\n \tgit commit -m \"hundred\" &&\n-\t(cat path1; echo new) >new-path &&\n+\t(cat path1 && echo new) >new-path &&\n \techo old >>path1 &&\n \tgit add new-path path1 &&\n \tgit diff -l 4 -C -C --cached --name-status >actual 2>actual.err &&\ndiff --git a/t/t4025-hunk-header.sh b/t/t4025-hunk-header.sh\nindex 7a3dbc1ea2..fa44e78869 100755\n--- a/t/t4025-hunk-header.sh\n+++ b/t/t4025-hunk-header.sh\n@@ -12,12 +12,12 @@ NS=\"$N$N$N$N$N$N$N$N$N$N$N$N$N\"\n test_expect_success setup '\n \n \t(\n-\t\techo \"A $NS\"\n+\t\techo \"A $NS\" &&\n \t\tfor c in B C D E F G H I J K\n \t\tdo\n \t\t\techo \"  $c\"\n-\t\tdone\n-\t\techo \"L  $NS\"\n+\t\tdone &&\n+\t\techo \"L  $NS\" &&\n \t\tfor c in M N O P Q R S T U V\n \t\tdo\n \t\t\techo \"  $c\"\n@@ -34,7 +34,7 @@ test_expect_success 'hunk header truncation with an overly long line' '\n \n \tgit diff | sed -n -e \"s/^.*@@//p\" >actual &&\n \t(\n-\t\techo \" A $N$N$N$N$N$N$N$N$N2\"\n+\t\techo \" A $N$N$N$N$N$N$N$N$N2\" &&\n \t\techo \" L  $N$N$N$N$N$N$N$N$N1\"\n \t) >expected &&\n \ttest_cmp actual expected\ndiff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh\nindex 058ee0829d..4e3499ef84 100755\n--- a/t/t4041-diff-submodule-option.sh\n+++ b/t/t4041-diff-submodule-option.sh\n@@ -498,7 +498,7 @@ test_expect_success 'given commit --submodule=short' '\n test_expect_success 'setup .git file for sm2' '\n \t(cd sm2 &&\n \t REAL=\"$(pwd)/../.real\" &&\n-\t mv .git \"$REAL\"\n+\t mv .git \"$REAL\" &&\n \t echo \"gitdir: $REAL\" >.git)\n '\n \n@@ -527,7 +527,7 @@ test_expect_success 'diff --submodule with objects referenced by alternates' '\n \t\tgit commit -m \"sub a\"\n \t) &&\n \t(cd sub_alt &&\n-\t\tsha1_before=$(git rev-parse --short HEAD)\n+\t\tsha1_before=$(git rev-parse --short HEAD) &&\n \t\techo b >b &&\n \t\tgit add b &&\n \t\tgit commit -m b &&\ndiff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh\nindex 4b168d0ed7..0eba4620f0 100755\n--- a/t/t4060-diff-submodule-option-diff-format.sh\n+++ b/t/t4060-diff-submodule-option-diff-format.sh\n@@ -721,7 +721,7 @@ test_expect_success 'given commit' '\n test_expect_success 'setup .git file for sm2' '\n \t(cd sm2 &&\n \t REAL=\"$(pwd)/../.real\" &&\n-\t mv .git \"$REAL\"\n+\t mv .git \"$REAL\" &&\n \t echo \"gitdir: $REAL\" >.git)\n '\n \ndiff --git a/t/t4121-apply-diffs.sh b/t/t4121-apply-diffs.sh\nindex aff551a1d7..66368effd5 100755\n--- a/t/t4121-apply-diffs.sh\n+++ b/t/t4121-apply-diffs.sh\n@@ -27,6 +27,6 @@ test_expect_success 'setup' \\\n \n test_expect_success \\\n \t'check if contextually independent diffs for the same file apply' \\\n-\t'( git diff test~2 test~1; git diff test~1 test~0 )| git apply'\n+\t'( git diff test~2 test~1 && git diff test~1 test~0 )| git apply'\n \n test_done\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350935","messageId":"20180626073001.6555-22-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 21/29] t3000-t3999: fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:53Z","receivedAt":"2018-06-26T07:32:31Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t3000-ls-files-others.sh              | 2 +-\n t/t3006-ls-files-long.sh                | 2 +-\n t/t3008-ls-files-lazy-init-name-hash.sh | 8 ++++----\n t/t3050-subprojects-fetch.sh            | 8 ++++----\n t/t3301-notes.sh                        | 8 ++++----\n t/t3400-rebase.sh                       | 8 ++++----\n t/t3402-rebase-merge.sh                 | 4 ++--\n t/t3418-rebase-continue.sh              | 4 ++--\n t/t3700-add.sh                          | 8 ++++----\n 9 files changed, 26 insertions(+), 26 deletions(-)\n\ndiff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh\nindex c525656b2c..afd4756134 100755\n--- a/t/t3000-ls-files-others.sh\n+++ b/t/t3000-ls-files-others.sh\n@@ -84,7 +84,7 @@ test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' '\n \t) &&\n \t(\n \t\tcd super &&\n-\t\t\"$SHELL_PATH\" \"$TEST_DIRECTORY/../contrib/workdir/git-new-workdir\" ../sub sub\n+\t\t\"$SHELL_PATH\" \"$TEST_DIRECTORY/../contrib/workdir/git-new-workdir\" ../sub sub &&\n \t\tgit ls-files --others --exclude-standard >../actual\n \t) &&\n \techo sub/ >expect &&\ndiff --git a/t/t3006-ls-files-long.sh b/t/t3006-ls-files-long.sh\nindex 202ad658b8..e109c3fbfb 100755\n--- a/t/t3006-ls-files-long.sh\n+++ b/t/t3006-ls-files-long.sh\n@@ -29,7 +29,7 @@ test_expect_success 'overly-long path does not replace another by mistake' '\n \tprintf \"$pat\" \"$blob_a\" \"$path_a\" \"$blob_z\" \"$path_z\" |\n \tgit update-index --add --index-info &&\n \t(\n-\t\techo \"$path_a\"\n+\t\techo \"$path_a\" &&\n \t\techo \"$path_z\"\n \t) >expect &&\n \tgit ls-files >actual &&\ndiff --git a/t/t3008-ls-files-lazy-init-name-hash.sh b/t/t3008-ls-files-lazy-init-name-hash.sh\nindex 08af596ba6..64f047332b 100755\n--- a/t/t3008-ls-files-lazy-init-name-hash.sh\n+++ b/t/t3008-ls-files-lazy-init-name-hash.sh\n@@ -14,10 +14,10 @@ LAZY_THREAD_COST=2000\n \n test_expect_success 'no buffer overflow in lazy_init_name_hash' '\n \t(\n-\t    test_seq $LAZY_THREAD_COST | sed \"s/^/a_/\"\n-\t    echo b/b/b\n-\t    test_seq $LAZY_THREAD_COST | sed \"s/^/c_/\"\n-\t    test_seq 50 | sed \"s/^/d_/\" | tr \"\\n\" \"/\"; echo d\n+\t    test_seq $LAZY_THREAD_COST | sed \"s/^/a_/\" &&\n+\t    echo b/b/b &&\n+\t    test_seq $LAZY_THREAD_COST | sed \"s/^/c_/\" &&\n+\t    test_seq 50 | sed \"s/^/d_/\" | tr \"\\n\" \"/\" && echo d\n \t) |\n \tsed \"s/^/100644 $EMPTY_BLOB\t/\" |\n \tgit update-index --index-info &&\ndiff --git a/t/t3050-subprojects-fetch.sh b/t/t3050-subprojects-fetch.sh\nindex 2f5f41a012..f1f09abdd9 100755\n--- a/t/t3050-subprojects-fetch.sh\n+++ b/t/t3050-subprojects-fetch.sh\n@@ -21,10 +21,10 @@ test_expect_success setup '\n \n test_expect_success clone '\n \tgit clone \"file://$(pwd)/.git\" cloned &&\n-\t(git rev-parse HEAD; git ls-files -s) >expected &&\n+\t(git rev-parse HEAD && git ls-files -s) >expected &&\n \t(\n \t\tcd cloned &&\n-\t\t(git rev-parse HEAD; git ls-files -s) >../actual\n+\t\t(git rev-parse HEAD && git ls-files -s) >../actual\n \t) &&\n \ttest_cmp expected actual\n '\n@@ -40,11 +40,11 @@ test_expect_success advance '\n '\n \n test_expect_success fetch '\n-\t(git rev-parse HEAD; git ls-files -s) >expected &&\n+\t(git rev-parse HEAD && git ls-files -s) >expected &&\n \t(\n \t\tcd cloned &&\n \t\tgit pull &&\n-\t\t(git rev-parse HEAD; git ls-files -s) >../actual\n+\t\t(git rev-parse HEAD && git ls-files -s) >../actual\n \t) &&\n \ttest_cmp expected actual\n '\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex 2d200fdf36..ac62dc0e8f 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -914,7 +914,7 @@ test_expect_success 'git notes copy --stdin' '\n \t\t${indent}\n \t\t${indent}yet another note\n \tEOF\n-\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \\\n+\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) &&\n \techo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |\n \tgit notes copy --stdin &&\n \tgit log -2 >actual &&\n@@ -939,7 +939,7 @@ test_expect_success 'git notes copy --for-rewrite (unconfigured)' '\n \tEOF\n \ttest_commit 14th &&\n \ttest_commit 15th &&\n-\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \\\n+\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) &&\n \techo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |\n \tgit notes copy --for-rewrite=foo &&\n \tgit log -2 >actual &&\n@@ -972,7 +972,7 @@ test_expect_success 'git notes copy --for-rewrite (enabled)' '\n \tEOF\n \ttest_config notes.rewriteMode overwrite &&\n \ttest_config notes.rewriteRef \"refs/notes/*\" &&\n-\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \\\n+\t(echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) &&\n \techo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) |\n \tgit notes copy --for-rewrite=foo &&\n \tgit log -2 >actual &&\n@@ -1059,7 +1059,7 @@ test_expect_success 'git notes copy --for-rewrite (append two to one)' '\n \tgit notes add -f -m\"append 2\" HEAD^^ &&\n \ttest_config notes.rewriteMode concatenate &&\n \ttest_config notes.rewriteRef \"refs/notes/*\" &&\n-\t(echo $(git rev-parse HEAD^) $(git rev-parse HEAD);\n+\t(echo $(git rev-parse HEAD^) $(git rev-parse HEAD) &&\n \techo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) |\n \tgit notes copy --for-rewrite=foo &&\n \tgit log -1 >actual &&\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 72d9564747..3996ee0135 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -200,10 +200,10 @@ test_expect_success 'rebase -q is quiet' '\n \n test_expect_success 'Rebase a commit that sprinkles CRs in' '\n \t(\n-\t\techo \"One\"\n-\t\techo \"TwoQ\"\n-\t\techo \"Three\"\n-\t\techo \"FQur\"\n+\t\techo \"One\" &&\n+\t\techo \"TwoQ\" &&\n+\t\techo \"Three\" &&\n+\t\techo \"FQur\" &&\n \t\techo \"Five\"\n \t) | q_to_cr >CR &&\n \tgit add CR &&\ndiff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh\nindex 488945e007..a1ec501a87 100755\n--- a/t/t3402-rebase-merge.sh\n+++ b/t/t3402-rebase-merge.sh\n@@ -25,7 +25,7 @@ test_expect_success setup '\n \tgit commit -a -m\"master updates a bit more.\" &&\n \n \tgit checkout side &&\n-\t(echo \"0 $T\" ; cat original) >renamed &&\n+\t(echo \"0 $T\" && cat original) >renamed &&\n \tgit add renamed &&\n \tgit update-index --force-remove original &&\n \tgit commit -a -m\"side renames and edits.\" &&\n@@ -143,7 +143,7 @@ test_expect_success 'rebase -s funny -Xopt' '\n \tgit checkout -b test-funny master^ &&\n \ttest_commit funny &&\n \t(\n-\t\tPATH=./test-bin:$PATH\n+\t\tPATH=./test-bin:$PATH &&\n \t\tgit rebase -s funny -Xopt master\n \t) &&\n \ttest -f funny.was.run\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 03bf1b8a3b..853e015839 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -60,7 +60,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '\n \tEOF\n \tchmod +x test-bin/git-merge-funny &&\n \t(\n-\t\tPATH=./test-bin:$PATH\n+\t\tPATH=./test-bin:$PATH &&\n \t\ttest_must_fail git rebase -s funny -Xopt master topic\n \t) &&\n \ttest -f funny.was.run &&\n@@ -68,7 +68,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' '\n \techo \"Resolved\" >F2 &&\n \tgit add F2 &&\n \t(\n-\t\tPATH=./test-bin:$PATH\n+\t\tPATH=./test-bin:$PATH &&\n \t\tgit rebase --continue\n \t) &&\n \ttest -f funny.was.run\ndiff --git a/t/t3700-add.sh b/t/t3700-add.sh\nindex 07af05d7ae..618750167a 100755\n--- a/t/t3700-add.sh\n+++ b/t/t3700-add.sh\n@@ -156,9 +156,9 @@ test_expect_success 'git add with filemode=0, symlinks=0, and unmerged entries'\n test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over stage 1' '\n \tgit rm --cached -f file symlink &&\n \t(\n-\t\techo \"100644 $(git hash-object -w stage1) 1\tfile\"\n-\t\techo \"100755 $(git hash-object -w stage2) 2\tfile\"\n-\t\techo \"100644 $(printf 1 | git hash-object -w -t blob --stdin) 1\tsymlink\"\n+\t\techo \"100644 $(git hash-object -w stage1) 1\tfile\" &&\n+\t\techo \"100755 $(git hash-object -w stage2) 2\tfile\" &&\n+\t\techo \"100644 $(printf 1 | git hash-object -w -t blob --stdin) 1\tsymlink\" &&\n \t\techo \"120000 $(printf 2 | git hash-object -w -t blob --stdin) 2\tsymlink\"\n \t) | git update-index --index-info &&\n \tgit config core.filemode 0 &&\n@@ -265,7 +265,7 @@ test_expect_success 'git add to resolve conflicts on otherwise ignored path' '\n \tgit reset --hard &&\n \tH=$(git rev-parse :1/2/a) &&\n \t(\n-\t\techo \"100644 $H 1\ttrack-this\"\n+\t\techo \"100644 $H 1\ttrack-this\" &&\n \t\techo \"100644 $H 3\ttrack-this\"\n \t) | git update-index --index-info &&\n \techo track-this >>.gitignore &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350936","messageId":"20180626073001.6555-14-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 13/29] t/lib-submodule-update: fix broken \"replace submodule must-fail\" test","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:45Z","receivedAt":"2018-06-26T07:32:38Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test has been dysfunctional since it was added by 259f3ee296\n(lib-submodule-update.sh: define tests for recursing into submodules,\n2017-03-14), however, problems went unnoticed due to a broken &&-chain\ntoward the end of the test.\n\nThe test wants to verify that replacing a submodule containing a .git\ndirectory must fail. All other \"must fail\" tests in this script invoke\nthe supplied command as 'test_must_fail', however, this test neglects to\ndo so.\n\nTo make matters worse, the command actually succeeds even though it's\nnot supposed to. Presumably, this is a \"known breakage\", which means\nthat the entire test should be marked 'test_expect_failure', however, it\nis instead marked 'test_expect_success'.\n\nFix both problems, as well as the broken &&-chain behind which these\nproblems hid.\n\nWhile at it, fix broken &&-chains in a couple neighboring tests.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/lib-submodule-update.sh | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh\nindex 1f38a85371..8a2edee1cb 100755\n--- a/t/lib-submodule-update.sh\n+++ b/t/lib-submodule-update.sh\n@@ -755,7 +755,7 @@ test_submodule_recursing_with_args_common() {\n \t\t\t: >sub1/untrackedfile &&\n \t\t\ttest_must_fail $command replace_sub1_with_file &&\n \t\t\ttest_superproject_content origin/add_sub1 &&\n-\t\t\ttest_submodule_content sub1 origin/add_sub1\n+\t\t\ttest_submodule_content sub1 origin/add_sub1 &&\n \t\t\ttest -f sub1/untracked_file\n \t\t)\n \t'\n@@ -842,7 +842,7 @@ test_submodule_switch_recursing_with_args () {\n \t\t\tcd submodule_update &&\n \t\t\tgit branch -t add_sub1 origin/add_sub1 &&\n \t\t\t: >sub1 &&\n-\t\t\techo sub1 >.git/info/exclude\n+\t\t\techo sub1 >.git/info/exclude &&\n \t\t\t$command add_sub1 &&\n \t\t\ttest_superproject_content origin/add_sub1 &&\n \t\t\ttest_submodule_content sub1 origin/add_sub1\n@@ -959,7 +959,7 @@ test_submodule_forced_switch_recursing_with_args () {\n \t\t)\n \t'\n \t# ... absorbing a .git directory.\n-\ttest_expect_success \"$command: replace submodule containing a .git directory with a directory must fail\" '\n+\ttest_expect_failure \"$command: replace submodule containing a .git directory with a directory must fail\" '\n \t\tprolog &&\n \t\treset_work_tree_to_interested add_sub1 &&\n \t\t(\n@@ -967,9 +967,9 @@ test_submodule_forced_switch_recursing_with_args () {\n \t\t\tgit branch -t replace_sub1_with_directory origin/replace_sub1_with_directory &&\n \t\t\treplace_gitfile_with_git_dir sub1 &&\n \t\t\trm -rf .git/modules/sub1 &&\n-\t\t\t$command replace_sub1_with_directory &&\n+\t\t\ttest_must_fail $command replace_sub1_with_directory &&\n \t\t\ttest_superproject_content origin/replace_sub1_with_directory &&\n-\t\t\ttest_submodule_content sub1 origin/modify_sub1\n+\t\t\ttest_submodule_content sub1 origin/modify_sub1 &&\n \t\t\ttest_git_directory_exists sub1\n \t\t)\n \t'\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350937","messageId":"20180626073001.6555-17-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 16/29] t: use test_might_fail() instead of manipulating exit code manually","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:48Z","receivedAt":"2018-06-26T07:32:40Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"These tests manually coerce the exit code of invoked commands to\n\"success\" when they don't care if the command succeeds or fails since\nfailure of those commands should not cause the test to fail overall.\nSuch manual management predates the invention of test_might_fail().\n\nAn upcoming change will teach --chain-lint to check the &&-chain inside\nsubshells. This sort of manual exit code manipulation will trip up\n--chain-lint due to the intentional break in the &&-chain. Therefore,\nreplace manual exit code management with test_might_fail() and a normal\n&&-chain.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t1507-rev-parse-upstream.sh | 6 +++---\n t/t1700-split-index.sh        | 2 +-\n t/t4012-diff-binary.sh        | 6 ++----\n t/t5400-send-pack.sh          | 4 ++--\n 4 files changed, 8 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh\nindex 93c77eac45..349f6e10af 100755\n--- a/t/t1507-rev-parse-upstream.sh\n+++ b/t/t1507-rev-parse-upstream.sh\n@@ -123,9 +123,9 @@ test_expect_success 'checkout -b new my-side@{u} forks from the same' '\n \n test_expect_success 'merge my-side@{u} records the correct name' '\n (\n-\tcd clone || exit\n-\tgit checkout master || exit\n-\tgit branch -D new ;# can fail but is ok\n+\tcd clone &&\n+\tgit checkout master &&\n+\ttest_might_fail git branch -D new &&\n \tgit branch -t new my-side@{u} &&\n \tgit merge -s ours new@{u} &&\n \tgit show -s --pretty=tformat:%s >actual &&\ndiff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh\nindex 1e81b33b2e..39133bcbc8 100755\n--- a/t/t1700-split-index.sh\n+++ b/t/t1700-split-index.sh\n@@ -435,7 +435,7 @@ test_expect_success 'writing split index with null sha1 does not write cache tre\n \tcommit=$(git commit-tree $tree -p HEAD <msg) &&\n \tgit update-ref HEAD \"$commit\" &&\n \tGIT_ALLOW_NULL_SHA1=1 git reset --hard &&\n-\t(test-tool dump-cache-tree >cache-tree.out || true) &&\n+\ttest_might_fail test-tool dump-cache-tree >cache-tree.out &&\n \ttest_line_count = 0 cache-tree.out\n '\n \ndiff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh\nindex 0a8af76aab..6579c81216 100755\n--- a/t/t4012-diff-binary.sh\n+++ b/t/t4012-diff-binary.sh\n@@ -102,10 +102,8 @@ test_expect_success 'apply binary patch' '\n \n test_expect_success 'diff --no-index with binary creation' '\n \techo Q | q_to_nul >binary &&\n-\t(: hide error code from diff, which just indicates differences\n-\t git diff --binary --no-index /dev/null binary >current ||\n-\t true\n-\t) &&\n+\t# hide error code from diff, which just indicates differences\n+\ttest_might_fail git diff --binary --no-index /dev/null binary >current &&\n \trm binary &&\n \tgit apply --binary <current &&\n \techo Q >expected &&\ndiff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh\nindex 911eae1bf7..f1932ea431 100755\n--- a/t/t5400-send-pack.sh\n+++ b/t/t5400-send-pack.sh\n@@ -86,7 +86,7 @@ test_expect_success 'push can be used to delete a ref' '\n test_expect_success 'refuse deleting push with denyDeletes' '\n \t(\n \t    cd victim &&\n-\t    ( git branch -D extra || : ) &&\n+\t    test_might_fail git branch -D extra &&\n \t    git config receive.denyDeletes true &&\n \t    git branch extra master\n \t) &&\n@@ -119,7 +119,7 @@ test_expect_success 'override denyDeletes with git -c receive-pack' '\n test_expect_success 'denyNonFastforwards trumps --force' '\n \t(\n \t    cd victim &&\n-\t    ( git branch -D extra || : ) &&\n+\t    test_might_fail git branch -D extra &&\n \t    git config receive.denyNonFastforwards true\n \t) &&\n \tvictim_orig=$(cd victim && git rev-parse --verify master) &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350938","messageId":"20180626073001.6555-18-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 17/29] t: use test_must_fail() instead of checking exit code manually","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:49Z","receivedAt":"2018-06-26T07:33:02Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"These tests intentionally break the &&-chain to manually check the exit\ncode of invoked commands which they expect to fail, and invert that\nlocal expected failure into a successful exit code for the test overall.\nSuch manual exit code manipulation predates the invention of\ntest_must_fail().\n\nAn upcoming change will teach --chain-lint to check the &&-chain inside\nsubshells. This sort of manual exit code checking will trip up\n--chain-lint due to the intentional break in the &&-chain. Therefore,\nreplace the manual exit code management with test_must_fail() and a\nnormal &&-chain.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t5405-send-pack-rewind.sh |  3 +--\n t/t9814-git-p4-rename.sh    | 16 ++--------------\n 2 files changed, 3 insertions(+), 16 deletions(-)\n\ndiff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh\nindex 4bda18a662..235fb7686a 100755\n--- a/t/t5405-send-pack-rewind.sh\n+++ b/t/t5405-send-pack-rewind.sh\n@@ -25,8 +25,7 @@ test_expect_success 'non forced push should die not segfault' '\n \n \t(\n \t\tcd another &&\n-\t\tgit push .. master:master\n-\t\ttest $? = 1\n+\t\ttest_must_fail git push .. master:master\n \t)\n \n '\ndiff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh\nindex e7e0268e98..80aac5ab16 100755\n--- a/t/t9814-git-p4-rename.sh\n+++ b/t/t9814-git-p4-rename.sh\n@@ -12,20 +12,8 @@ test_expect_success 'start p4d' '\n test_expect_success 'p4 help unknown returns 1' '\n \t(\n \t\tcd \"$cli\" &&\n-\t\t(\n-\t\t\tp4 help client >errs 2>&1\n-\t\t\techo $? >retval\n-\t\t)\n-\t\techo 0 >expected &&\n-\t\ttest_cmp expected retval &&\n-\t\trm retval &&\n-\t\t(\n-\t\t\tp4 help nosuchcommand >errs 2>&1\n-\t\t\techo $? >retval\n-\t\t)\n-\t\techo 1 >expected &&\n-\t\ttest_cmp expected retval &&\n-\t\trm retval\n+\t\tp4 help client &&\n+\t\ttest_must_fail p4 help nosuchcommand\n \t)\n '\n \n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350939","messageId":"20180626073001.6555-8-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 07/29] t7201: drop pointless \"exit 0\" at end of subshell","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:39Z","receivedAt":"2018-06-26T07:33:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test employs a for-loop inside a subshell and correctly aborts the\nloop and fails the test overall (via \"exit 1\") if any iteration of the\nfor-loop fails. Otherwise, it exits the subshell with an explicit\n\"exit 0\", presumably to indicate that all iterations of the for-loop\nsucceeded. The &&-chain is (perhaps deliberately) broken between the\nfor-loop and the \"exit 0\".\n\nAn upcoming change will teach --chain-lint to detect &&-chain breakage\ninside subshells, thus the missing &&-chain link will run afoul of\n--chain-lint. Rather than fixing the &&-chain breakage, instead just\ndrop the unnecessary \"exit 0\".\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t7201-co.sh | 1 -\n 1 file changed, 1 deletion(-)\n\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex ab9da61da3..8d8a63a24b 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -673,7 +673,6 @@ test_expect_success 'custom merge driver with checkout -m' '\n \t\tdo\n \t\t\tgrep $t arm || exit 1\n \t\tdone\n-\t\texit 0\n \t) &&\n \n \tmv arm expect &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350940","messageId":"20180626073001.6555-9-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 08/29] t7400: fix broken \"submodule add/reconfigure --force\" test","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:40Z","receivedAt":"2018-06-26T07:33:10Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test has been dysfunctional since it was added by 619acfc78c\n(submodule add: extend force flag to add existing repos, 2016-10-06),\nhowever, two problems early in the test went unnoticed due to a broken\n&&-chain later in the test.\n\nFirst, it tries configuring the submodule with repository \"bogus-url\",\nhowever, \"git submodule add\" insists that the repository be either an\nabsolute URL or a relative pathname requiring prefix \"./\" or \"../\" (this\nis true even with --force), but \"bogus-url\" does not meet those\ncriteria, thus the command fails.\n\nSecond, it then tries configuring a submodule with a path which is\n.gitignore'd, which is disallowed. This restriction can be overridden\nwith --force, but the test neglects to use that option.\n\nFix both problems, as well as the broken &&-chain behind which they hid.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t7400-submodule-basic.sh | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\nindex 812db137b8..401adaed32 100755\n--- a/t/t7400-submodule-basic.sh\n+++ b/t/t7400-submodule-basic.sh\n@@ -171,12 +171,12 @@ test_expect_success 'submodule add to .gitignored path with --force' '\n test_expect_success 'submodule add to reconfigure existing submodule with --force' '\n \t(\n \t\tcd addtest-ignore &&\n-\t\tgit submodule add --force bogus-url submod &&\n-\t\tgit submodule add -b initial \"$submodurl\" submod-branch &&\n-\t\ttest \"bogus-url\" = \"$(git config -f .gitmodules submodule.submod.url)\" &&\n-\t\ttest \"bogus-url\" = \"$(git config submodule.submod.url)\" &&\n+\t\tgit submodule add --force /bogus-url submod &&\n+\t\tgit submodule add --force -b initial \"$submodurl\" submod-branch &&\n+\t\ttest \"/bogus-url\" = \"$(git config -f .gitmodules submodule.submod.url)\" &&\n+\t\ttest \"/bogus-url\" = \"$(git config submodule.submod.url)\" &&\n \t\t# Restore the url\n-\t\tgit submodule add --force \"$submodurl\" submod\n+\t\tgit submodule add --force \"$submodurl\" submod &&\n \t\ttest \"$submodurl\" = \"$(git config -f .gitmodules submodule.submod.url)\" &&\n \t\ttest \"$submodurl\" = \"$(git config submodule.submod.url)\"\n \t)\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350941","messageId":"20180626073001.6555-11-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 10/29] t9001: fix broken \"invoke hook\" test","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:42Z","receivedAt":"2018-06-26T07:33:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test has been dysfunctional since it was added by 6489660b4b\n(send-email: support validate hook, 2017-05-12), however, the problem\nwent unnoticed due to a broken &&-chain late in the test.\n\nThe test wants to verify that a non-zero exit code from the\n'sendemail-validate' hook causes git-send-email to abort with a\nparticular error message. A command which is expected to fail should be\nrun with 'test_must_fail', however, the tests neglects to do so.\n\nFix this problem, as well as the broken &&-chain behind which the\nproblem hid.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t9001-send-email.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\nindex e80eacbb1b..776769fe0d 100755\n--- a/t/t9001-send-email.sh\n+++ b/t/t9001-send-email.sh\n@@ -1966,11 +1966,11 @@ test_expect_success $PREREQ 'invoke hook' '\n \n \t\t# Verify error message when a patch is rejected by the hook\n \t\tsed -e \"s/add master/x/\" ../0001-add-master.patch >../another.patch &&\n-\t\tgit send-email \\\n+\t\ttest_must_fail git send-email \\\n \t\t\t--from=\"Example <nobody@example.com>\" \\\n \t\t\t--to=nobody@example.com \\\n \t\t\t--smtp-server=\"$(pwd)/../fake.sendmail\" \\\n-\t\t\t../another.patch 2>err\n+\t\t\t../another.patch 2>err &&\n \t\ttest_i18ngrep \"rejected by sendemail-validate hook\" err\n \t)\n '\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350942","messageId":"20180626073001.6555-12-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 11/29] t9104: use \"{...}\" block around \"||\" expression rather than subshell","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:43Z","receivedAt":"2018-06-26T07:33:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test uses \"(... || svn ...)\" as a shorthand for an if-then-else\nstatement. The subshell prevents it from breaking the top-level\n&&-chain.\n\nHowever, an upcoming change will teach --chain-lint to check the\n&&-chain inside subshells. Although it takes special care to allow\n\"||\" inside subshells, it only recognizes \"(... || git ...)\" and\n\"(... || test*), so the \"||\" in this test will trip up --chain-lint.\n\nA test later in this same script employs the same \"... || svn ...\"\nconstruct, however, it wraps it in a \"{...}\" block instead of a\nsubshell. Therefore, rather than adding \"(... || svn ...)\" as a yet\nanother --chain-lint special case, follow suit and make this test use\n\"{...}\", as well.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t9104-git-svn-follow-parent.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh\nindex 5e0ad19177..f9aa734d4e 100755\n--- a/t/t9104-git-svn-follow-parent.sh\n+++ b/t/t9104-git-svn-follow-parent.sh\n@@ -53,10 +53,10 @@ test_expect_success 'init and fetch from one svn-remote' '\n         '\n \n test_expect_success 'follow deleted parent' '\n-        (svn_cmd cp -m \"resurrecting trunk as junk\" \\\n+\t{ svn_cmd cp -m \"resurrecting trunk as junk\" \\\n                \"$svnrepo\"/trunk@2 \"$svnrepo\"/junk ||\n          svn cp -m \"resurrecting trunk as junk\" \\\n-               -r2 \"$svnrepo\"/trunk \"$svnrepo\"/junk) &&\n+\t\t-r2 \"$svnrepo\"/trunk \"$svnrepo\"/junk; } &&\n         git config --add svn-remote.svn.fetch \\\n           junk:refs/remotes/svn/junk &&\n         git svn fetch -i svn/thunk &&\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350943","messageId":"20180626073001.6555-7-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 06/29] t6036: fix broken \"merge fails but has appropriate contents\" tests","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:38Z","receivedAt":"2018-06-26T07:33:22Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"These tests reference non-existent object \"c\" when they really mean to\nbe referencing \"C\", however, this error went unnoticed due to a broken\n&&-chain later in the test. Fix these errors, as well as the broken\n&&-chains behind which they hid.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t6036-recursive-corner-cases.sh | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex b5621303d6..b32ff8e1db 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -506,10 +506,10 @@ test_expect_success 'merge of D & E2 fails but has appropriate contents' '\n \t\ttest_line_count = 2 out &&\n \n \t\tgit rev-parse >expect    \\\n-\t\t\tB:a   E2:a/file  c:a/file   A:ignore-me &&\n+\t\t\tB:a   E2:a/file  C:a/file   A:ignore-me &&\n \t\tgit rev-parse   >actual   \\\n \t\t\t:2:a  :3:a/file  :1:a/file  :0:ignore-me &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_file a~HEAD\n \t)\n@@ -533,10 +533,10 @@ test_expect_success 'merge of E2 & D fails but has appropriate contents' '\n \t\ttest_line_count = 2 out &&\n \n \t\tgit rev-parse >expect    \\\n-\t\t\tB:a   E2:a/file  c:a/file   A:ignore-me &&\n+\t\t\tB:a   E2:a/file  C:a/file   A:ignore-me &&\n \t\tgit rev-parse   >actual   \\\n \t\t\t:3:a  :2:a/file  :1:a/file  :0:ignore-me &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_file a~D^0\n \t)\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350944","messageId":"20180626073001.6555-6-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH 05/29] t5505: modernize and simplify hard-to-digest test","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T07:29:37Z","receivedAt":"2018-06-26T07:33:29Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This test uses a subshell within a subshell but is formatted in such a\nway as to suggests that the inner subshell is a sibling rather than a\nchild, which makes it difficult to digest the test's structure and\nintent.\n\nWorse, the inner subshell performs cleanup of actions from earlier in\nthe test, however, a failure between the initial actions and the cleanup\nwill prevent the cleanup from taking place.\n\nFix these problems by modernizing and simplifying the test and by using\ntest_when_finished() for the cleanup action.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/t5505-remote.sh | 8 ++------\n 1 file changed, 2 insertions(+), 6 deletions(-)\n\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex a6c0178f3a..3552b51b4c 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -348,17 +348,13 @@ URL: $(pwd)/one\n EOF\n \n test_expect_success 'prune --dry-run' '\n-\t(\n-\t\tcd one &&\n-\t\tgit branch -m side2 side) &&\n+\tgit -C one branch -m side2 side &&\n+\ttest_when_finished \"git -C one branch -m side side2\" &&\n \t(\n \t\tcd test &&\n \t\tgit remote prune --dry-run origin >output &&\n \t\tgit rev-parse refs/remotes/origin/side2 &&\n \t\ttest_must_fail git rev-parse refs/remotes/origin/side &&\n-\t(\n-\t\tcd ../one &&\n-\t\tgit branch -m side side2) &&\n \t\ttest_i18ncmp expect output\n \t)\n '\n-- \n2.18.0.419.gfe4b301394\n\n"},{"id":"350945","messageId":"CAE5ih79-eXhx8d9Fsgo-4gFQ6A-Deje1ky3ge5X-vEuZcSePJA@mail.gmail.com","threadId":"48778","inReplyTo":"20180626073001.6555-18-sunshine@sunshineco.com","subject":"Re: [PATCH 17/29] t: use test_must_fail() instead of checking exit code manually","fromName":"Luke Diamand","fromEmail":"luke@diamand.org","sentAt":"2018-06-26T07:59:27Z","receivedAt":"2018-06-26T07:59:31Z","isPatch":true,"sender":{"key":"luke@diamand.org","avatar":"https://avatars.githubusercontent.com/u/5330967?v=4"},"body":"On 26 June 2018 at 08:29, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> These tests intentionally break the &&-chain to manually check the exit\n> code of invoked commands which they expect to fail, and invert that\n> local expected failure into a successful exit code for the test overall.\n> Such manual exit code manipulation predates the invention of\n> test_must_fail().\n>\n> An upcoming change will teach --chain-lint to check the &&-chain inside\n> subshells. This sort of manual exit code checking will trip up\n> --chain-lint due to the intentional break in the &&-chain. Therefore,\n> replace the manual exit code management with test_must_fail() and a\n> normal &&-chain.\n>\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n>  t/t5405-send-pack-rewind.sh |  3 +--\n>  t/t9814-git-p4-rename.sh    | 16 ++--------------\n>  2 files changed, 3 insertions(+), 16 deletions(-)\n>\n> diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh\n> index 4bda18a662..235fb7686a 100755\n> --- a/t/t5405-send-pack-rewind.sh\n> +++ b/t/t5405-send-pack-rewind.sh\n> @@ -25,8 +25,7 @@ test_expect_success 'non forced push should die not segfault' '\n>\n>         (\n>                 cd another &&\n> -               git push .. master:master\n> -               test $? = 1\n> +               test_must_fail git push .. master:master\n>         )\n>\n>  '\n> diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh\n> index e7e0268e98..80aac5ab16 100755\n> --- a/t/t9814-git-p4-rename.sh\n> +++ b/t/t9814-git-p4-rename.sh\n> @@ -12,20 +12,8 @@ test_expect_success 'start p4d' '\n>  test_expect_success 'p4 help unknown returns 1' '\n>         (\n>                 cd \"$cli\" &&\n> -               (\n> -                       p4 help client >errs 2>&1\n> -                       echo $? >retval\n> -               )\n> -               echo 0 >expected &&\n> -               test_cmp expected retval &&\n> -               rm retval &&\n> -               (\n> -                       p4 help nosuchcommand >errs 2>&1\n> -                       echo $? >retval\n> -               )\n> -               echo 1 >expected &&\n> -               test_cmp expected retval &&\n> -               rm retval\n> +               p4 help client &&\n> +               test_must_fail p4 help nosuchcommand\n\nThis seems a lot more sensible. It works fine in my tests.\n\nThanks,\nLuke\n"},{"id":"350946","messageId":"CABPp-BG2LJMQs6=hvyhKOdQE=dSwADsCKrkf16_stEVnUZr_vA@mail.gmail.com","threadId":"48778","inReplyTo":"20180626073001.6555-7-sunshine@sunshineco.com","subject":"Re: [PATCH 06/29] t6036: fix broken \"merge fails but has appropriate contents\" tests","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-06-26T08:44:46Z","receivedAt":"2018-06-26T08:44:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> These tests reference non-existent object \"c\" when they really mean to\n> be referencing \"C\", however, this error went unnoticed due to a broken\n> &&-chain later in the test. Fix these errors, as well as the broken\n> &&-chains behind which they hid.\n>\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n>  t/t6036-recursive-corner-cases.sh | 8 ++++----\n>  1 file changed, 4 insertions(+), 4 deletions(-)\n>\n> diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\n> index b5621303d6..b32ff8e1db 100755\n> --- a/t/t6036-recursive-corner-cases.sh\n> +++ b/t/t6036-recursive-corner-cases.sh\n> @@ -506,10 +506,10 @@ test_expect_success 'merge of D & E2 fails but has appropriate contents' '\n>                 test_line_count = 2 out &&\n>\n>                 git rev-parse >expect    \\\n> -                       B:a   E2:a/file  c:a/file   A:ignore-me &&\n> +                       B:a   E2:a/file  C:a/file   A:ignore-me &&\n>                 git rev-parse   >actual   \\\n>                         :2:a  :3:a/file  :1:a/file  :0:ignore-me &&\n> -               test_cmp expect actual\n> +               test_cmp expect actual &&\n>\n>                 test_path_is_file a~HEAD\n>         )\n> @@ -533,10 +533,10 @@ test_expect_success 'merge of E2 & D fails but has appropriate contents' '\n>                 test_line_count = 2 out &&\n>\n>                 git rev-parse >expect    \\\n> -                       B:a   E2:a/file  c:a/file   A:ignore-me &&\n> +                       B:a   E2:a/file  C:a/file   A:ignore-me &&\n>                 git rev-parse   >actual   \\\n>                         :3:a  :2:a/file  :1:a/file  :0:ignore-me &&\n> -               test_cmp expect actual\n> +               test_cmp expect actual &&\n>\n>                 test_path_is_file a~D^0\n>         )\n\nEek, how did that become c:a/file when it was originally C:a/file?\nThanks for spotting the regression and fixing.\n\nReviewed-by: Elijah Newren <newren@gmail.com>\n"},{"id":"350947","messageId":"CABPp-BFmfN6=E+3BAKt-NH5hmU-368shgDnrnkrnMRvKnx07BQ@mail.gmail.com","threadId":"48778","inReplyTo":"20180626073001.6555-18-sunshine@sunshineco.com","subject":"Re: [PATCH 17/29] t: use test_must_fail() instead of checking exit code manually","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-06-26T08:58:04Z","receivedAt":"2018-06-26T08:58:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> These tests intentionally break the &&-chain to manually check the exit\n> code of invoked commands which they expect to fail, and invert that\n> local expected failure into a successful exit code for the test overall.\n> Such manual exit code manipulation predates the invention of\n> test_must_fail().\n>\n> An upcoming change will teach --chain-lint to check the &&-chain inside\n> subshells. This sort of manual exit code checking will trip up\n> --chain-lint due to the intentional break in the &&-chain. Therefore,\n> replace the manual exit code management with test_must_fail() and a\n> normal &&-chain.\n>\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n>  t/t5405-send-pack-rewind.sh |  3 +--\n>  t/t9814-git-p4-rename.sh    | 16 ++--------------\n>  2 files changed, 3 insertions(+), 16 deletions(-)\n>\n> diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh\n> index 4bda18a662..235fb7686a 100755\n> --- a/t/t5405-send-pack-rewind.sh\n> +++ b/t/t5405-send-pack-rewind.sh\n> @@ -25,8 +25,7 @@ test_expect_success 'non forced push should die not segfault' '\n>\n>         (\n>                 cd another &&\n> -               git push .. master:master\n> -               test $? = 1\n> +               test_must_fail git push .. master:master\n\ntest_must_fail or test_expect_code 1?\n\n>         )\n>\n>  '\n> diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh\n> index e7e0268e98..80aac5ab16 100755\n> --- a/t/t9814-git-p4-rename.sh\n> +++ b/t/t9814-git-p4-rename.sh\n> @@ -12,20 +12,8 @@ test_expect_success 'start p4d' '\n>  test_expect_success 'p4 help unknown returns 1' '\n>         (\n>                 cd \"$cli\" &&\n> -               (\n> -                       p4 help client >errs 2>&1\n> -                       echo $? >retval\n> -               )\n> -               echo 0 >expected &&\n> -               test_cmp expected retval &&\n> -               rm retval &&\n> -               (\n> -                       p4 help nosuchcommand >errs 2>&1\n> -                       echo $? >retval\n> -               )\n> -               echo 1 >expected &&\n> -               test_cmp expected retval &&\n> -               rm retval\n> +               p4 help client &&\n> +               test_must_fail p4 help nosuchcommand\n\nsame question?\n\n>         )\n>  '\n\nOverall, looks much nicer.\n"},{"id":"350949","messageId":"CABPp-BEz=mLHsg1NizJUKxOKM=dKFSAVec=x0mcs=4cJ73k_+Q@mail.gmail.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"Re: [PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-06-26T09:20:39Z","receivedAt":"2018-06-26T09:20:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> The --chain-lint[1] option detects breakage in the top-level &&-chain of\n> tests. This series undertakes the more complex task of teaching it to\n> also detect &&-chain breakage within subshells. See patch 29/29 for the\n> gory details of how that's done.\n\nThis is awesome.  It'll replace a hacky script I had that attempted to\nremind me when I messed up, and fix its false positives to boot.\nPlus, I won't forget to run it.\n\n> Aside from identifying a rather significant number of &&-chain breaks,\n> repairing those broken chains uncovered genuine bugs in several tests\n> which were hidden by missing &&-chain links. Those bugs are also fixed\n> by this series. I would appreciate if the following people would\n> double-check my fixes:\n>\n> Stefan Bellar - 8/29 \"t7400\" and (especially) 13/29 \"lib-submodule-update\"\n> Jonathan Tan - 10/29 \"t9001\"\n> Elijah Newren - 6/29 \"t6036\"\n\nCommented on the patch in question; 6/29 looks good.\n\nI also looked over the rest of the series.  Apart from the ones you\nspecifically called out as needing review by others besides me, and\nthe final patch which makes me feel like a sed neophyte, all but one\npatch looked good to me.  I just have a small question for that\nremaining patch, which I posted there.\n\nThanks,\nElijah\n"},{"id":"350950","messageId":"CAPig+cRTG625H3CF1Zw30vQt2W8uKf1xLxVaQni2YbJ=xAif2g@mail.gmail.com","threadId":"48778","inReplyTo":"CABPp-BFmfN6=E+3BAKt-NH5hmU-368shgDnrnkrnMRvKnx07BQ@mail.gmail.com","subject":"Re: [PATCH 17/29] t: use test_must_fail() instead of checking exit code manually","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T09:21:17Z","receivedAt":"2018-06-26T09:21:34Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 4:58 AM Elijah Newren <newren@gmail.com> wrote:\n> On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > [...] Therefore,\n> > replace the manual exit code management with test_must_fail() and a\n> > normal &&-chain.\n> >\n> > Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> > ---\n> > diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh\n> > @@ -25,8 +25,7 @@ test_expect_success 'non forced push should die not segfault' '> > -               git push .. master:master\n> > -               test $? = 1\n> > +               test_must_fail git push .. master:master\n>\n> test_must_fail or test_expect_code 1?\n\nA legitimate question, and the answer is that it's a judgment call\nbased upon the spirit of the test.\n\nAlthough test_expect_code() would make for a faithful literal\nconversion, I don't think it agrees with the spirit of this test,\nwhich wants to verify that the command correctly exits with an error\n(in the general sense), as opposed to outright crashing (which it did\nat one time). test_must_fail() is tailor-made for this use case.\n\nContrast this with patch 09/29 \"t7810: use test_expect_code() instead\nof hand-rolled comparison\". Different exit codes from git-grep have\ngenuine different meanings, and that test is checking for a very\nspecific exit code, for which test_expect_code() is tailor-made.\n\n> > diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh\n> > @@ -12,20 +12,8 @@ test_expect_success 'start p4d' '\n> >  test_expect_success 'p4 help unknown returns 1' '\n> >         (\n> >                 cd \"$cli\" &&\n> > -               (\n> > -                       p4 help client >errs 2>&1\n> > -                       echo $? >retval\n> > -               )\n> > -               echo 0 >expected &&\n> > -               test_cmp expected retval &&\n> > -               rm retval &&\n> > -               (\n> > -                       p4 help nosuchcommand >errs 2>&1\n> > -                       echo $? >retval\n> > -               )\n> > -               echo 1 >expected &&\n> > -               test_cmp expected retval &&\n> > -               rm retval\n> > +               p4 help client &&\n> > +               test_must_fail p4 help nosuchcommand\n>\n> same question?\n\nSame answer. Not shown in this patch, but just above the context lines\nyou will find this comment in the file:\n\n    # We rely on this behavior to detect for p4 move availability.\n\nwhich means that the test is really interested in being able to\nreliably detect if a sub-command is or is not available. So, despite\nthe (somewhat) misleading test title, this test doesn't care about the\nexact error code but rather cares only that \"p4 help nosuchcommand\"\nerrors out, period. Hence, test_must_fail() again agrees with the\nspirit of the test.\n"},{"id":"350952","messageId":"CAPig+cRBN5-80HOvajG8WXqZUHmOX5SCrO93T9G4ePzgbGftZA@mail.gmail.com","threadId":"48778","inReplyTo":"CABPp-BEz=mLHsg1NizJUKxOKM=dKFSAVec=x0mcs=4cJ73k_+Q@mail.gmail.com","subject":"Re: [PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T09:31:42Z","receivedAt":"2018-06-26T09:32:00Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 5:20 AM Elijah Newren <newren@gmail.com> wrote:\n> On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > Aside from identifying a rather significant number of &&-chain breaks,\n> > repairing those broken chains uncovered genuine bugs in several tests\n> > which were hidden by missing &&-chain links. Those bugs are also fixed\n> > by this series. I would appreciate if the following people would\n> > double-check my fixes:\n> >\n> > Stefan Bellar - 8/29 \"t7400\" and (especially) 13/29 \"lib-submodule-update\"\n> > Jonathan Tan - 10/29 \"t9001\"\n> > Elijah Newren - 6/29 \"t6036\"\n>\n> Commented on the patch in question; 6/29 looks good.\n>\n> I also looked over the rest of the series.  Apart from the ones you\n> specifically called out as needing review by others besides me, and\n> the final patch which makes me feel like a sed neophyte, all but one\n> patch looked good to me.  I just have a small question for that\n> remaining patch, which I posted there.\n\nI guess you refer to your question[1] about whether test_must_fail()\nis the correct choice over test_expect_code(). I just responded[2]\nwith a hopefully satisfactory answer.\n\n[1]: https://public-inbox.org/git/CABPp-BFmfN6=E+3BAKt-NH5hmU-368shgDnrnkrnMRvKnx07BQ@mail.gmail.com/\n[2]: https://public-inbox.org/git/CAPig+cRTG625H3CF1Zw30vQt2W8uKf1xLxVaQni2YbJ=xAif2g@mail.gmail.com/\n"},{"id":"350970","messageId":"CABPp-BHr52=Yg4Ugxmfw0=5XhOus2sNEMZ+wxzfef0sTFfU_Og@mail.gmail.com","threadId":"48778","inReplyTo":"CAPig+cRBN5-80HOvajG8WXqZUHmOX5SCrO93T9G4ePzgbGftZA@mail.gmail.com","subject":"Re: [PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-06-26T15:34:04Z","receivedAt":"2018-06-26T15:34:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Eric,\n\nOn Tue, Jun 26, 2018, 2:31 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Tue, Jun 26, 2018 at 5:20 AM Elijah Newren <newren@gmail.com> wrote:\n> > On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > > Aside from identifying a rather significant number of &&-chain breaks,\n> > > repairing those broken chains uncovered genuine bugs in several tests\n> > > which were hidden by missing &&-chain links. Those bugs are also fixed\n> > > by this series. I would appreciate if the following people would\n> > > double-check my fixes:\n> > >\n> > > Stefan Bellar - 8/29 \"t7400\" and (especially) 13/29 \"lib-submodule-update\"\n> > > Jonathan Tan - 10/29 \"t9001\"\n> > > Elijah Newren - 6/29 \"t6036\"\n> >\n> > Commented on the patch in question; 6/29 looks good.\n> >\n> > I also looked over the rest of the series.  Apart from the ones you\n> > specifically called out as needing review by others besides me, and\n> > the final patch which makes me feel like a sed neophyte, all but one\n> > patch looked good to me.  I just have a small question for that\n> > remaining patch, which I posted there.\n>\n> I guess you refer to your question[1] about whether test_must_fail()\n> is the correct choice over test_expect_code(). I just responded[2]\n> with a hopefully satisfactory answer.\n\nYes, it does.  Thanks!\n"},{"id":"350982","messageId":"20180626170755.134929-1-jonathantanmy@google.com","threadId":"48778","inReplyTo":"20180626073001.6555-11-sunshine@sunshineco.com","subject":"Re: [PATCH 10/29] t9001: fix broken \"invoke hook\" test","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2018-06-26T17:07:55Z","receivedAt":"2018-06-26T17:08:02Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh\n> index e80eacbb1b..776769fe0d 100755\n> --- a/t/t9001-send-email.sh\n> +++ b/t/t9001-send-email.sh\n> @@ -1966,11 +1966,11 @@ test_expect_success $PREREQ 'invoke hook' '\n>  \n>  \t\t# Verify error message when a patch is rejected by the hook\n>  \t\tsed -e \"s/add master/x/\" ../0001-add-master.patch >../another.patch &&\n> -\t\tgit send-email \\\n> +\t\ttest_must_fail git send-email \\\n>  \t\t\t--from=\"Example <nobody@example.com>\" \\\n>  \t\t\t--to=nobody@example.com \\\n>  \t\t\t--smtp-server=\"$(pwd)/../fake.sendmail\" \\\n> -\t\t\t../another.patch 2>err\n> +\t\t\t../another.patch 2>err &&\n>  \t\ttest_i18ngrep \"rejected by sendemail-validate hook\" err\n\nThanks for catching this. Indeed, \"git send-email\" is supposed to fail\nbecause the validate hook greps for the string \"add master\", which does\nnot exist in the e-mail to be sent. (Above this is a test that shows\nthat the same validate hook succeeds if the e-mail contains \"add\nmaster\".) This looks correct to me.\n"},{"id":"350988","messageId":"29d090a2-bbe4-0d87-6dda-037ae675d4a3@kdbg.org","threadId":"48778","inReplyTo":"CAPig+cRTG625H3CF1Zw30vQt2W8uKf1xLxVaQni2YbJ=xAif2g@mail.gmail.com","subject":"Re: [PATCH 17/29] t: use test_must_fail() instead of checking exit code manually","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2018-06-26T18:05:55Z","receivedAt":"2018-06-26T18:06:01Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 26.06.2018 um 11:21 schrieb Eric Sunshine:\n> On Tue, Jun 26, 2018 at 4:58 AM Elijah Newren <newren@gmail.com> wrote:\n>> On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n>>> +               p4 help client &&\n>>> +               test_must_fail p4 help nosuchcommand\n>>\n>> same question?\n> \n> Same answer. Not shown in this patch, but just above the context lines\n> you will find this comment in the file:\n> \n>      # We rely on this behavior to detect for p4 move availability.\n> \n> which means that the test is really interested in being able to\n> reliably detect if a sub-command is or is not available. So, despite\n> the (somewhat) misleading test title, this test doesn't care about the\n> exact error code but rather cares only that \"p4 help nosuchcommand\"\n> errors out, period. Hence, test_must_fail() again agrees with the\n> spirit of the test.\n\ntest_must_fail ensures that only \"proper\" failures are diagnosed as \nexpected; failures due to signals such as SEGV are not expected failures.\n\nIn the test suite we expect all programs that are not our \"git\" to work \ncorrectly; in particular, that they do not crash on anything that we ask \nthem to operate on. Under this assumption, the protection given by \ntest_must_fail is not needed.\n\nHence, these lines should actually be\n\n\t\tp4 help client &&\n\t\t! p4 help nosuchcommand\n\n-- Hannes\n"},{"id":"350989","messageId":"CAPig+cRnbW9fRh68ZorNQHjDFHQBWmqfEPSO+H7qgMJVOcbJmQ@mail.gmail.com","threadId":"48778","inReplyTo":"29d090a2-bbe4-0d87-6dda-037ae675d4a3@kdbg.org","subject":"Re: [PATCH 17/29] t: use test_must_fail() instead of checking exit code manually","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T18:14:58Z","receivedAt":"2018-06-26T18:15:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 2:06 PM Johannes Sixt <j6t@kdbg.org> wrote:\n> Am 26.06.2018 um 11:21 schrieb Eric Sunshine:\n> >> On Tue, Jun 26, 2018 at 12:29 AM, Eric Sunshine <sunshine@sunshineco.com> wrote:\n> >>> +               p4 help client &&\n> >>> +               test_must_fail p4 help nosuchcommand\n> > [...] So, despite\n> > the (somewhat) misleading test title, this test doesn't care about the\n> > exact error code but rather cares only that \"p4 help nosuchcommand\"\n> > errors out, period. Hence, test_must_fail() again agrees with the\n> > spirit of the test.\n>\n> test_must_fail ensures that only \"proper\" failures are diagnosed as\n> expected; failures due to signals such as SEGV are not expected failures.\n>\n> In the test suite we expect all programs that are not our \"git\" to work\n> correctly; in particular, that they do not crash on anything that we ask\n> them to operate on. Under this assumption, the protection given by\n> test_must_fail is not needed.\n>\n> Hence, these lines should actually be\n>\n>                 p4 help client &&\n>                 ! p4 help nosuchcommand\n\nThanks for the comment; you're right, of course. I'll certainly make\nthis change if I have to re-roll for some other reason, but do you\nfeel that this itself is worth a re-roll?\n"},{"id":"350994","messageId":"xmqqwouljr5e.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180626073001.6555-30-sunshine@sunshineco.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-06-26T19:15:25Z","receivedAt":"2018-06-26T19:15:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> The --chain-lint option detects broken &&-chains by forcing the test to\n> exit early (as the very first step) with a sentinel value. If that\n> sentinel is the test's overall exit code, then the &&-chain is intact;\n> if not, then the chain is broken. Unfortunately, this detection does not\n> extend to &&-chains within subshells even when the subshell itself is\n> properly linked into the outer &&-chain.\n>\n> Address this shortcoming by eliminating the subshell during the\n> \"linting\" phase and incorporating its body directly into the surrounding\n> &&-chain. To keep this transformation cheap, no attempt is made at\n> properly parsing shell code. Instead, the manipulations are purely\n> textual. For example:\n>\n>     statement1 &&\n>     (\n>         statement2 &&\n>         statement3\n>     ) &&\n>     statement4\n>\n> is transformed to:\n>\n>     statement1 &&\n>         statement2 &&\n>         statement3 &&\n>     statement4\n\nso, with --chain-lint, we would transform this\n\n\tmkdir -p a/b/c &&\n\t(\n\t\tcd a/b/c\n\t\trm -fr ../../*\n\t) &&\n\tstatement 4\n\ninto this sequence\n\n\t(exit $sentinel) &&\n\tmkdir -p a/b/c &&\n\t\tcd a/b/c\n\t\trm -fr ../../* &&\n\tstatement 4\n\nand then rely on the non-zero exit to cancel all the remainder?\n\nWe didn't create nor cd to the t/trash$num/a/b/c thanks to the &&\nchain, and end up running rm -fr ../../* from inside t/trash$num?\n\nHmmmmm....\n"},{"id":"350996","messageId":"xmqqsh59jqea.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180626073001.6555-15-sunshine@sunshineco.com","subject":"Re: [PATCH 14/29] t: drop subshell with missing &&-chain in favor of simpler construct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-06-26T19:31:41Z","receivedAt":"2018-06-26T19:31:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> These tests employ a noisy subshell (with missing &&-chain) to feed\n> input into Git commands:\n>\n>     (echo a; echo b; echo c) | git some-command ...\n>\n> Drop the subshell in favor of a simple 'printf':\n>\n>     printf \"%s\\n\" a b c | git some-command ...\n\nThat's called test_write_lines, I think.\n"},{"id":"350998","messageId":"xmqqo9fxjq39.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"Re: [PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-06-26T19:38:18Z","receivedAt":"2018-06-26T19:38:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> The --chain-lint[1] option detects breakage in the top-level &&-chain of\n> tests. This series undertakes the more complex task of teaching it to\n> also detect &&-chain breakage within subshells. See patch 29/29 for the\n> gory details of how that's done.\n\nI first looked at 29/29 and got heavily inclined to reject that\nstep, and then continued reading from 1/29 to around 15/29.  \n\nI like these earlier changes that fix existing breakage, of course.\nI also like many of the changes that simplify and/or modernise the\ntest scripts very much, but they are unusable as-is as long as their\njustification is \"chain-lint will start barfing on these constructs\".\n"},{"id":"350999","messageId":"CAPig+cSLyie8mr+u8Thv9cJ0J12nCA+RU6Mg3S5F8U68q1+nzQ@mail.gmail.com","threadId":"48778","inReplyTo":"xmqqwouljr5e.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T19:52:54Z","receivedAt":"2018-06-26T19:53:08Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 3:15 PM Junio C Hamano <gitster@pobox.com> wrote:\n> so, with --chain-lint, we would transform this\n>\n>         mkdir -p a/b/c &&\n>         (\n>                 cd a/b/c\n>                 rm -fr ../../*\n>         ) &&\n>         statement 4\n>\n> into this sequence\n>\n>         (exit $sentinel) &&\n>         mkdir -p a/b/c &&\n>                 cd a/b/c\n>                 rm -fr ../../* &&\n>         statement 4\n>\n> and then rely on the non-zero exit to cancel all the remainder?\n>\n> We didn't create nor cd to the t/trash$num/a/b/c thanks to the &&\n> chain, and end up running rm -fr ../../* from inside t/trash$num?\n\nYes, I did take that into account and, no, I don't have a good answer\nto the issue.\n\nThe existing --chain-lint already suffers the same shortcoming. Older\n(or even new poorly-written) tests, even without subshells, can fall\nvictim already:\n\n    (exit $sentinel) &&\n    mkdir -p a/b/c &&\n    cd a/b/c\n    rm -fr ../../* &&\n    cd ../../.. &&\n    statement4\n\nAs in your example, 'mkdir' and 'cd' are skipped, but 'rm -fr ../../*' is not.\n\nThis snippet from the commit message of bb79af9d09 (t/test-lib:\nintroduce --chain-lint option, 2015-03-20):\n\n    When we encounter a failure of this check, we abort the test\n    script entirely. For one thing, we have no clue which subset\n    of the commands in the test snippet were actually run.\n\nsuggests that the issue was considered, in some form, even then\n(though, it doesn't say explicitly that Peff had the 'rm -fr' case in\nmind).\n\nSo, this isn't a new problem introduced by this series, though this\nseries may exacerbate it.\n\nThanks for thinking critically about it.\n"},{"id":"351002","messageId":"CAPig+cSJe+bHK+P6_8Q7u_t65bFhVk9zEyQY7L_yVL7g0J-j3w@mail.gmail.com","threadId":"48778","inReplyTo":"xmqqsh59jqea.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 14/29] t: drop subshell with missing &&-chain in favor of simpler construct","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T20:06:39Z","receivedAt":"2018-06-26T20:06:53Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 3:31 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> > These tests employ a noisy subshell (with missing &&-chain) to feed\n> > input into Git commands:\n> >\n> >     (echo a; echo b; echo c) | git some-command ...\n> >\n> > Drop the subshell in favor of a simple 'printf':\n> >\n> >     printf \"%s\\n\" a b c | git some-command ...\n>\n> That's called test_write_lines, I think.\n\nYep, that's better. Thanks.\n"},{"id":"351006","messageId":"20180626201708.GA2341@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CAPig+cSLyie8mr+u8Thv9cJ0J12nCA+RU6Mg3S5F8U68q1+nzQ@mail.gmail.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-06-26T20:17:08Z","receivedAt":"2018-06-26T20:17:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 26, 2018 at 03:52:54PM -0400, Eric Sunshine wrote:\n\n> The existing --chain-lint already suffers the same shortcoming. Older\n> (or even new poorly-written) tests, even without subshells, can fall\n> victim already:\n> \n>     (exit $sentinel) &&\n>     mkdir -p a/b/c &&\n>     cd a/b/c\n>     rm -fr ../../* &&\n>     cd ../../.. &&\n>     statement4\n> \n> As in your example, 'mkdir' and 'cd' are skipped, but 'rm -fr ../../*' is not.\n> \n> This snippet from the commit message of bb79af9d09 (t/test-lib:\n> introduce --chain-lint option, 2015-03-20):\n> \n>     When we encounter a failure of this check, we abort the test\n>     script entirely. For one thing, we have no clue which subset\n>     of the commands in the test snippet were actually run.\n> \n> suggests that the issue was considered, in some form, even then\n> (though, it doesn't say explicitly that Peff had the 'rm -fr' case in\n> mind).\n>\n> So, this isn't a new problem introduced by this series, though this\n> series may exacerbate it.\n\nOne way this series might be worse in practice is that we tend not to\nchange process state too much outside of the subshells. So if we skip\nsome early commands and execute a later \"rm\", for example, it tends to\nbe in the same directory (and I think as time goes on we have been\ncleaning up old tests which did a \"cd foo && bar && cd ..\" into using a\nsubshell).\n\nWhereas once you start collapsing subshells into the main logic chain,\nthere's a very high chance that the subshell is doing a \"cd\", since\nthat's typically the main reason for the subshell in the first place.\nAnd with the current --chain-lint logic, that subshell is either\nexecuted or not executed as a unit.\n\nObviously that's a bit of a hand-waving argument. If you've fixed all of\nthe existing cases without accidentally deleting your home directory,\nthen maybe it's not so likely to be a problem after all.\n\nI'm not sure if there's a good solution, though. Even if you retained\nthe subshells and instead did a chain-lint inside each subshell, like\nthis:\n\n  (exit 117) &&\n  one &&\n  (\n\t(exit 117) &&\n\tcd foo\n\ttwo\n  ) &&\n  three\n\n\nthat doesn't really help. The fundamental issue is that we may skip the\n\"cd\" inside the subshell. Whether it's in a subshell or not, that's\ndangerous. True, we don't run \"three\" in this case, which is slightly\nbetter. But it didn't expect to be in a different directory anyway. It's\nrunning \"two\" that is dangerous.\n\n-Peff\n"},{"id":"351007","messageId":"20180626202244.GB2341@sigill.intra.peff.net","threadId":"48778","inReplyTo":"20180626201708.GA2341@sigill.intra.peff.net","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-06-26T20:22:45Z","receivedAt":"2018-06-26T20:22:49Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 26, 2018 at 04:17:08PM -0400, Jeff King wrote:\n\n> I'm not sure if there's a good solution, though. Even if you retained\n> the subshells and instead did a chain-lint inside each subshell, like\n> this:\n\nSo obviously that means \"I don't think there's a good solution with this\napproach\".\n\nThat whole final patch simultaneously impresses and nauseates me. Your\ncommit message says \"no attempt is made at properly parsing shell code\",\nbut we come pretty darn close. I almost wonder if we'd be better off\njust parsing some heuristic subset and making sure (via review or\nlinting) that our tests conform.\n\nAnother option is to not enable this slightly-more-dangerous linting by\ndefault. But that would probably rob it of its usefulness, since it\nwould just fall to some brave soul to later crank up the linting and fix\neverybody else's mistakes.\n\n-Peff\n"},{"id":"351009","messageId":"CAPig+cRvgsu-6f+mzjGVDWTVhFrhLY5MsNxEQBJYckKDpeaMAg@mail.gmail.com","threadId":"48778","inReplyTo":"20180626201708.GA2341@sigill.intra.peff.net","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T20:46:18Z","receivedAt":"2018-06-26T20:46:33Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 4:17 PM Jeff King <peff@peff.net> wrote:\n> On Tue, Jun 26, 2018 at 03:52:54PM -0400, Eric Sunshine wrote:\n> > So, this isn't a new problem introduced by this series, though this\n> > series may exacerbate it.\n>\n> Whereas once you start collapsing subshells into the main logic chain,\n> there's a very high chance that the subshell is doing a \"cd\", since\n> that's typically the main reason for the subshell in the first place.\n> And with the current --chain-lint logic, that subshell is either\n> executed or not executed as a unit.\n>\n> Obviously that's a bit of a hand-waving argument. If you've fixed all of\n> the existing cases without accidentally deleting your home directory,\n> then maybe it's not so likely to be a problem after all.\n\nIndeed, it could be that the \"rm -fr\" worry is tending toward the\nhypothetical. Seasoned developers tend to be pretty careful and\nusually avoid indiscriminately loose \"rm -fr\" invocations, so I'm\nsomewhat less worried about them. I do share the concern, though, that\nnewcomers crafting or extending tests could shoot themselves in the\nfoot with this. However, newcomers are also the ones most likely to\nuse the \"cd foo && bar && cd ..\" idiom, so they are already at risk.\n\n(As for not blasting my home directory when fixing all the existing\ntests, I did run into a few cases where one or two \"foreign\" files\nwere deposited into the \"t/\" directory, but nothing was deleted or\noverwritten.)\n\n> I'm not sure if there's a good solution, though. Even if you retained\n> the subshells and instead did a chain-lint inside each subshell, like\n> this:\n>\n>   (exit 117) &&\n>   one &&\n>   (\n>         (exit 117) &&\n>         cd foo\n>         two\n>   ) &&\n>   three\n\nI thought of that too, but the inner (exit 117) doesn't even get\ninvoked unless there is &&-chain breakage somewhere above that point\n(for instance, if \"one\" lacks \"&&\"), so the inner (exit 117) doesn't\nparticipate in the linting process at all.\n\n> that doesn't really help. The fundamental issue is that we may skip the\n> \"cd\" inside the subshell. Whether it's in a subshell or not, that's\n> dangerous. True, we don't run \"three\" in this case, which is slightly\n> better. But it didn't expect to be in a different directory anyway. It's\n> running \"two\" that is dangerous.\n\nJust thinking aloud...\n\nAside from \"rm -fr\", there are numerous ways to clobber files\nunexpectedly when the \"cd\" is skipped:\n\n    echo x >../git.c\n    cp x ../git.c\n    mv x ../git.c\n    ln [-s] x ../git.c\n    /bin/rm ../git.c\n    some-cmd -o ../git.c\n\nSome of these dangers can be de-thoothed during the linting phase by\ndefining do-nothing shell functions:\n\n    cp () { :; }\n    mv () { :; }\n    ln () { :; }\n\nThat, at least, makes the scariest case (\"rm\") much less so.\n"},{"id":"351021","messageId":"CAPig+cR1Jxdo_YGSCQRyFD4AMq2wrBzpESHhHkjTEnVG1mMRbQ@mail.gmail.com","threadId":"48778","inReplyTo":"20180626202244.GB2341@sigill.intra.peff.net","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T20:59:30Z","receivedAt":"2018-06-26T20:59:45Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 4:22 PM Jeff King <peff@peff.net> wrote:\n> So obviously that means \"I don't think there's a good solution with this\n> approach\".\n>\n> That whole final patch simultaneously impresses and nauseates me. Your\n> commit message says \"no attempt is made at properly parsing shell code\",\n> but we come pretty darn close. I almost wonder if we'd be better off\n> just parsing some heuristic subset and making sure (via review or\n> linting) that our tests conform.\n\nI'm not sure I agree with \"come pretty darn close\", but your idea is\nan interesting one. It would sidestep the concern with \"rm -fr\" and\nfriends (though it will probably still nauseate you). Let me cogitate\nabout it a bit...\n\n> Another option is to not enable this slightly-more-dangerous linting by\n> default. But that would probably rob it of its usefulness, since it\n> would just fall to some brave soul to later crank up the linting and fix\n> everybody else's mistakes.\n\nI considered that, as well, and came to the same conclusion.\n"},{"id":"351022","messageId":"07314f8f-e242-b9b9-fd20-7e1ef8df8e47@kdbg.org","threadId":"48778","inReplyTo":"CAPig+cRnbW9fRh68ZorNQHjDFHQBWmqfEPSO+H7qgMJVOcbJmQ@mail.gmail.com","subject":"Re: [PATCH 17/29] t: use test_must_fail() instead of checking exit code manually","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2018-06-26T21:00:24Z","receivedAt":"2018-06-26T21:00:29Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 26.06.2018 um 20:14 schrieb Eric Sunshine:\n> On Tue, Jun 26, 2018 at 2:06 PM Johannes Sixt <j6t@kdbg.org> wrote:\n>> Hence, these lines should actually be\n>>\n>>                  p4 help client &&\n>>                  ! p4 help nosuchcommand\n> \n> Thanks for the comment; you're right, of course. I'll certainly make\n> this change if I have to re-roll for some other reason, but do you\n> feel that this itself is worth a re-roll?\n\nNot worth a re-roll IMO.\n\n-- Hannes\n"},{"id":"351023","messageId":"20180626210100.GA3682@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CAPig+cRvgsu-6f+mzjGVDWTVhFrhLY5MsNxEQBJYckKDpeaMAg@mail.gmail.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-06-26T21:01:00Z","receivedAt":"2018-06-26T21:01:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 26, 2018 at 04:46:18PM -0400, Eric Sunshine wrote:\n\n> > I'm not sure if there's a good solution, though. Even if you retained\n> > the subshells and instead did a chain-lint inside each subshell, like\n> > this:\n> >\n> >   (exit 117) &&\n> >   one &&\n> >   (\n> >         (exit 117) &&\n> >         cd foo\n> >         two\n> >   ) &&\n> >   three\n> \n> I thought of that too, but the inner (exit 117) doesn't even get\n> invoked unless there is &&-chain breakage somewhere above that point\n> (for instance, if \"one\" lacks \"&&\"), so the inner (exit 117) doesn't\n> participate in the linting process at all.\n\nOh, right. Not only does it not fix the problem, it's totally\nunworkable. :)\n\n> Some of these dangers can be de-thoothed during the linting phase by\n> defining do-nothing shell functions:\n> \n>     cp () { :; }\n>     mv () { :; }\n>     ln () { :; }\n> \n> That, at least, makes the scariest case (\"rm\") much less so.\n\nNow that's an interesting idea. We can't catch every dangerous action\n(notably \">\" would be hard to override), but it should be pretty cheap\nto cover some obvious ones.\n\n-Peff\n"},{"id":"351024","messageId":"xmqqy3f1i7bi.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180626201708.GA2341@sigill.intra.peff.net","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-06-26T21:09:05Z","receivedAt":"2018-06-26T21:09:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> One way this series might be worse in practice is that we tend not to\n> change process state too much outside of the subshells.\n> ...\n> Whereas once you start collapsing subshells into the main logic chain,\n> there's a very high chance that the subshell is doing a \"cd\", since\n> that's typically the main reason for the subshell in the first place.\n\nExactly.  I should have mentioned this when I responded to save a\nround-trip.\n\n"},{"id":"351025","messageId":"CAPig+cRzZ-Nd2b-M8v0NUsLX_gkKwLqyakgcRhwGf3CzsWwxBA@mail.gmail.com","threadId":"48778","inReplyTo":"20180626210100.GA3682@sigill.intra.peff.net","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T21:13:05Z","receivedAt":"2018-06-26T21:13:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 5:01 PM Jeff King <peff@peff.net> wrote:\n> On Tue, Jun 26, 2018 at 04:46:18PM -0400, Eric Sunshine wrote:\n> > Some of these dangers can be de-thoothed during the linting phase by\n> > defining do-nothing shell functions:\n> >\n> >     cp () { :; }\n> >     mv () { :; }\n> >     ln () { :; }\n> >\n> > That, at least, makes the scariest case (\"rm\") much less so.\n>\n> Now that's an interesting idea. We can't catch every dangerous action\n> (notably \">\" would be hard to override), but it should be pretty cheap\n> to cover some obvious ones.\n\nTaking the idea a bit further, the 'sed' script could also throw away\nstrings of \"../\" inside subshells, which would help defang the more\ndifficult cases, like \"echo x >../git.c\". There are pathological\ncases, of course, which it wouldn't catch:\n\n    P=../git.c\n    test_expect_success 'foo' '\n        (\n            cd dir &&\n            echo x >$P\n        )\n    '\n\nbut it does help mitigate the issue for the most typical cases.\n"},{"id":"351028","messageId":"CAPig+cSW6jP3FtYpwf5bB4SM=qw6A3K9H3JPranJ_KMqH-AwZw@mail.gmail.com","threadId":"48778","inReplyTo":"xmqqo9fxjq39.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T21:25:34Z","receivedAt":"2018-06-26T21:25:48Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 3:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n> I first looked at 29/29 and got heavily inclined to reject that\n> step, and then continued reading from 1/29 to around 15/29.\n>\n> I like these earlier changes that fix existing breakage, of course.\n> I also like many of the changes that simplify and/or modernise the\n> test scripts very much, but they are unusable as-is as long as their\n> justification is \"chain-lint will start barfing on these constructs\".\n\nSorry, I'm having difficulty understanding.\n\nAre you saying that you don't want patches which exist merely to\npacify --chain-lint? (For instance, 2/29 \"t0001: use \"{...}\" block\naround \"||\" expression rather than subshell\".)\n\nOr are you saying that you don't like how the commit messages are\nworded, and that they should instead emphasize that the change is good\nfor its own sake, without mentioning --chain-lint?\n"},{"id":"351030","messageId":"CABPp-BGP2OFYgP5Xm0PMj_NPH2f_UmtMpoHYzcgo544V0e-_Zg@mail.gmail.com","threadId":"48778","inReplyTo":"20180626202244.GB2341@sigill.intra.peff.net","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-06-26T21:33:11Z","receivedAt":"2018-06-26T21:33:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 26, 2018 at 1:22 PM, Jeff King <peff@peff.net> wrote:\n> On Tue, Jun 26, 2018 at 04:17:08PM -0400, Jeff King wrote:\n>\n>> I'm not sure if there's a good solution, though. Even if you retained\n>> the subshells and instead did a chain-lint inside each subshell, like\n>> this:\n>\n> So obviously that means \"I don't think there's a good solution with this\n> approach\".\n>\n> That whole final patch simultaneously impresses and nauseates me. Your\n> commit message says \"no attempt is made at properly parsing shell code\",\n> but we come pretty darn close. I almost wonder if we'd be better off\n> just parsing some heuristic subset and making sure (via review or\n> linting) that our tests conform.\n>\n> Another option is to not enable this slightly-more-dangerous linting by\n> default. But that would probably rob it of its usefulness, since it\n> would just fall to some brave soul to later crank up the linting and fix\n> everybody else's mistakes.\n\nThis may be a dumb question, but why can't we run under errexit?  If\nwe could do that, we wouldn't need the &&-chaining, and bash would\nparse the shell for us and exit whenever one command failed.  (Is the\nreason for this documented somewhere?  I couldn't find it...)\n"},{"id":"351037","messageId":"CAPig+cTc2XVX18ocv6ALOSA2CwM8aQLWW+mRWL6Pj1Wu_QHDHw@mail.gmail.com","threadId":"48778","inReplyTo":"CABPp-BGP2OFYgP5Xm0PMj_NPH2f_UmtMpoHYzcgo544V0e-_Zg@mail.gmail.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-26T21:42:35Z","receivedAt":"2018-06-26T21:42:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jun 26, 2018 at 5:33 PM Elijah Newren <newren@gmail.com> wrote:\n> On Tue, Jun 26, 2018 at 1:22 PM, Jeff King <peff@peff.net> wrote:\n> > Another option is to not enable this slightly-more-dangerous linting by\n> > default. But that would probably rob it of its usefulness, since it\n> > would just fall to some brave soul to later crank up the linting and fix\n> > everybody else's mistakes.\n>\n> This may be a dumb question, but why can't we run under errexit?  If\n> we could do that, we wouldn't need the &&-chaining, and bash would\n> parse the shell for us and exit whenever one command failed.  (Is the\n> reason for this documented somewhere?  I couldn't find it...)\n\nI'm not sure if it's documented anywhere, but it has been discussed.\nIn particular, see [1], especially [2], and [3]. Peff summed up by\nsaying:\n\n    So I dunno. I think \"set -e\" is kind of a dangerous lure. It works\n    so well _most_ of the time that you start to rely on it, but it\n    really does have some funny corner cases (even on modern shells,\n    and for all I know, the behavior above is mandated by POSIX).\n\n[1]: https://public-inbox.org/git/xmqq384zha6s.fsf@gitster.dls.corp.google.com/\n[2]: https://public-inbox.org/git/20150320172406.GA15172@peff.net/\n[3]: https://public-inbox.org/git/xmqqoannfu84.fsf@gitster.dls.corp.google.com/\n"},{"id":"351053","messageId":"xmqq6025i3io.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"CAPig+cSW6jP3FtYpwf5bB4SM=qw6A3K9H3JPranJ_KMqH-AwZw@mail.gmail.com","subject":"Re: [PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-06-26T22:31:11Z","receivedAt":"2018-06-26T22:31:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> On Tue, Jun 26, 2018 at 3:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n>> I first looked at 29/29 and got heavily inclined to reject that\n>> step, and then continued reading from 1/29 to around 15/29.\n>>\n>> I like these earlier changes that fix existing breakage, of course.\n>> I also like many of the changes that simplify and/or modernise the\n>> test scripts very much, but they are unusable as-is as long as their\n>> justification is \"chain-lint will start barfing on these constructs\".\n>\n> Sorry, I'm having difficulty understanding.\n>\n> Are you saying that you don't want patches which exist merely to\n> pacify --chain-lint? (For instance, 2/29 \"t0001: use \"{...}\" block\n> around \"||\" expression rather than subshell\".)\n\nYes.\n\n> Or are you saying that you don't like how the commit messages are\n> worded, and that they should instead emphasize that the change is good\n> for its own sake, without mentioning --chain-lint?\n\nYes, too.\n\nFor example, 03/29 is a good clean-up, and its value is not\ndiminished even if we reject the subprocess munging --chain-lint in\n29/29.\n\nAs opposed to 02/29 which mostly is about appeasing the \"shell\nparser\" in 29/29 (or you could justify it saying \"one less fork and\nprocess\" if that gives us a measurable benefit).\n"},{"id":"351057","messageId":"20180627002255.GC12488@aiede.svl.corp.google.com","threadId":"48778","inReplyTo":"xmqq6025i3io.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 00/29] t: detect and fix broken &&-chains in subshells","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-06-27T00:22:55Z","receivedAt":"2018-06-27T00:23:02Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Jun 26, 2018 at 03:31:11PM -0700, Junio C Hamano wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n>> On Tue, Jun 26, 2018 at 3:38 PM Junio C Hamano <gitster@pobox.com> wrote:\n\n>>> I like these earlier changes that fix existing breakage, of course.\n>>> I also like many of the changes that simplify and/or modernise the\n>>> test scripts very much, but they are unusable as-is as long as their\n>>> justification is \"chain-lint will start barfing on these constructs\".\n>>\n>> Sorry, I'm having difficulty understanding.\n>>\n>> Are you saying that you don't want patches which exist merely to\n>> pacify --chain-lint? (For instance, 2/29 \"t0001: use \"{...}\" block\n>> around \"||\" expression rather than subshell\".)\n>\n> Yes.\n>\n>> Or are you saying that you don't like how the commit messages are\n>> worded, and that they should instead emphasize that the change is good\n>> for its own sake, without mentioning --chain-lint?\n>\n> Yes, too.\n>\n> For example, 03/29 is a good clean-up, and its value is not\n> diminished even if we reject the subprocess munging --chain-lint in\n> 29/29.\n>\n> As opposed to 02/29 which mostly is about appeasing the \"shell\n> parser\" in 29/29 (or you could justify it saying \"one less fork and\n> process\" if that gives us a measurable benefit).\n\nThis is a lighter-weight example of the practice described at\nhttps://lkml.kernel.org/r/alpine.LFD.2.00.1001251002430.3574@localhost.localdomain/.\nIn my opinion it's good advice, often worth repeating.\n\nThanks,\nJonathan\n"},{"id":"351058","messageId":"CABPp-BHgPgkgQj7i3rZtdbinVZvqH35uGraUK9doL-E8Y5QuSQ@mail.gmail.com","threadId":"48778","inReplyTo":"20180626210100.GA3682@sigill.intra.peff.net","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-06-27T02:15:45Z","receivedAt":"2018-06-27T02:15:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 26, 2018 at 2:01 PM, Jeff King <peff@peff.net> wrote:\n> On Tue, Jun 26, 2018 at 04:46:18PM -0400, Eric Sunshine wrote:\n\n>> Some of these dangers can be de-thoothed during the linting phase by\n>> defining do-nothing shell functions:\n>>\n>>     cp () { :; }\n>>     mv () { :; }\n>>     ln () { :; }\n>>\n>> That, at least, makes the scariest case (\"rm\") much less so.\n>\n> Now that's an interesting idea. We can't catch every dangerous action\n> (notably \">\" would be hard to override), but it should be pretty cheap\n> to cover some obvious ones.\n>\n> -Peff\n\nCrazy idea: maybe we could defang it a little more thoroughly with\nsomething like the following (apologies in advance if gmail whitespace\ndamages this):\n\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 28315706be..7fda08a90a 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -675,7 +675,7 @@ test_run_ () {\n                trace=\n                # 117 is magic because it is unlikely to match the exit\n                # code of other programs\n-               if test \"OK-117\" != \"$(test_eval_ \"(exit 117) &&\n$1${LF}${LF}echo OK-\\$?\" 3>&1)\"\n+               if test \"OK-117\" != \"$(test_eval_ \"cd() { return 0; }\n&& PATH=/dev/null && export PATH && (exit 117) && $1${LF}${LF}echo\nOK-\\$?\" 3>&1)\"\n                then\n                        error \"bug in the test script: broken &&-chain\nor run-away HERE-DOC: $1\"\n                fi\n"},{"id":"351062","messageId":"67110b53-690c-abd4-9988-33278ee8c413@kdbg.org","threadId":"48778","inReplyTo":"CABPp-BHgPgkgQj7i3rZtdbinVZvqH35uGraUK9doL-E8Y5QuSQ@mail.gmail.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2018-06-27T06:27:34Z","receivedAt":"2018-06-27T06:27:40Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 27.06.2018 um 04:15 schrieb Elijah Newren:\n> On Tue, Jun 26, 2018 at 2:01 PM, Jeff King <peff@peff.net> wrote:\n>> On Tue, Jun 26, 2018 at 04:46:18PM -0400, Eric Sunshine wrote:\n> \n>>> Some of these dangers can be de-thoothed during the linting phase by\n>>> defining do-nothing shell functions:\n>>>\n>>>      cp () { :; }\n>>>      mv () { :; }\n>>>      ln () { :; }\n>>>\n>>> That, at least, makes the scariest case (\"rm\") much less so.\n>>\n>> Now that's an interesting idea. We can't catch every dangerous action\n>> (notably \">\" would be hard to override), but it should be pretty cheap\n>> to cover some obvious ones.\n>>\n>> -Peff\n> \n> Crazy idea: maybe we could defang it a little more thoroughly with\n> something like the following (apologies in advance if gmail whitespace\n> damages this):\n> \n> diff --git a/t/test-lib.sh b/t/test-lib.sh\n> index 28315706be..7fda08a90a 100644\n> --- a/t/test-lib.sh\n> +++ b/t/test-lib.sh\n> @@ -675,7 +675,7 @@ test_run_ () {\n>                  trace=\n>                  # 117 is magic because it is unlikely to match the exit\n>                  # code of other programs\n> -               if test \"OK-117\" != \"$(test_eval_ \"(exit 117) &&\n> $1${LF}${LF}echo OK-\\$?\" 3>&1)\"\n> +               if test \"OK-117\" != \"$(test_eval_ \"cd() { return 0; }\n> && PATH=/dev/null && export PATH && (exit 117) && $1${LF}${LF}echo\n> OK-\\$?\" 3>&1)\"\n>                  then\n>                          error \"bug in the test script: broken &&-chain\n> or run-away HERE-DOC: $1\"\n>                  fi\n\nI'd define all these functions as { return 1; } because we want to stop \nany && chain as early as possible (and with an exit code that is not the \nsentinel value).\n\n-- Hannes\n"},{"id":"351063","messageId":"CAPig+cQR48oPmTkO1YsDpSm-MYhyRuBwNdEJp_4+MhavP7CpXA@mail.gmail.com","threadId":"48778","inReplyTo":"67110b53-690c-abd4-9988-33278ee8c413@kdbg.org","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-27T06:48:22Z","receivedAt":"2018-06-27T06:48:37Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Jun 27, 2018 at 2:27 AM Johannes Sixt <j6t@kdbg.org> wrote:\n> Am 27.06.2018 um 04:15 schrieb Elijah Newren:\n> > On Tue, Jun 26, 2018 at 2:01 PM, Jeff King <peff@peff.net> wrote:\n> >> On Tue, Jun 26, 2018 at 04:46:18PM -0400, Eric Sunshine wrote:\n> >>> Some of these dangers can be de-thoothed during the linting phase by\n> >>> defining do-nothing shell functions:\n> >>>      cp () { :; }\n> >>> That, at least, makes the scariest case (\"rm\") much less so.\n> >>\n> >> Now that's an interesting idea. We can't catch every dangerous action\n> >> (notably \">\" would be hard to override), but it should be pretty cheap\n> >> to cover some obvious ones.\n> >\n> > Crazy idea: maybe we could defang it a little more thoroughly with\n> > something like the following (apologies in advance if gmail whitespace\n> > damages this):\n> >\n> > -               if test \"OK-117\" != \"$(test_eval_ \"(exit 117) &&\n> > $1${LF}${LF}echo OK-\\$?\" 3>&1)\"\n> > +               if test \"OK-117\" != \"$(test_eval_ \"cd() { return 0; }\n> > && PATH=/dev/null && export PATH && (exit 117) && $1${LF}${LF}echo\n> > OK-\\$?\" 3>&1)\"\n\nInteresting idea (coupled with Hannes's point below)...\n\n> I'd define all these functions as { return 1; } because we want to stop\n> any && chain as early as possible (and with an exit code that is not the\n> sentinel value).\n\nA very sensible suggestion.\n"},{"id":"351131","messageId":"CAGZ79kZziBqyUbFuBEiMUO+5sx6ds=RY0SXejZA7F2CVY67rqQ@mail.gmail.com","threadId":"48778","inReplyTo":"20180626073001.6555-9-sunshine@sunshineco.com","subject":"Re: [PATCH 08/29] t7400: fix broken \"submodule add/reconfigure --force\" test","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-06-27T18:04:57Z","receivedAt":"2018-06-27T18:05:12Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Tue, Jun 26, 2018 at 12:30 AM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> This test has been dysfunctional since it was added by 619acfc78c\n> (submodule add: extend force flag to add existing repos, 2016-10-06),\n> however, two problems early in the test went unnoticed due to a broken\n> &&-chain later in the test.\n>\n> First, it tries configuring the submodule with repository \"bogus-url\",\n> however, \"git submodule add\" insists that the repository be either an\n> absolute URL or a relative pathname requiring prefix \"./\" or \"../\" (this\n> is true even with --force), but \"bogus-url\" does not meet those\n> criteria, thus the command fails.\n>\n> Second, it then tries configuring a submodule with a path which is\n> .gitignore'd, which is disallowed. This restriction can be overridden\n> with --force, but the test neglects to use that option.\n>\n> Fix both problems, as well as the broken &&-chain behind which they hid.\n>\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n\nThis patch is\nReviewed-by: Stefan Beller <sbeller@google.com>\n\nThanks for this whole series (I just read the cover letter) and I think\ndetecting broken && chains is a valuable part in the test suite.\n\nThanks,\nStefan\n\n> ---\n>  t/t7400-submodule-basic.sh | 10 +++++-----\n>  1 file changed, 5 insertions(+), 5 deletions(-)\n>\n> diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh\n> index 812db137b8..401adaed32 100755\n> --- a/t/t7400-submodule-basic.sh\n> +++ b/t/t7400-submodule-basic.sh\n> @@ -171,12 +171,12 @@ test_expect_success 'submodule add to .gitignored path with --force' '\n>  test_expect_success 'submodule add to reconfigure existing submodule with --force' '\n>         (\n>                 cd addtest-ignore &&\n> -               git submodule add --force bogus-url submod &&\n> -               git submodule add -b initial \"$submodurl\" submod-branch &&\n> -               test \"bogus-url\" = \"$(git config -f .gitmodules submodule.submod.url)\" &&\n> -               test \"bogus-url\" = \"$(git config submodule.submod.url)\" &&\n> +               git submodule add --force /bogus-url submod &&\n> +               git submodule add --force -b initial \"$submodurl\" submod-branch &&\n> +               test \"/bogus-url\" = \"$(git config -f .gitmodules submodule.submod.url)\" &&\n> +               test \"/bogus-url\" = \"$(git config submodule.submod.url)\" &&\n>                 # Restore the url\n> -               git submodule add --force \"$submodurl\" submod\n> +               git submodule add --force \"$submodurl\" submod &&\n>                 test \"$submodurl\" = \"$(git config -f .gitmodules submodule.submod.url)\" &&\n>                 test \"$submodurl\" = \"$(git config submodule.submod.url)\"\n>         )\n> --\n> 2.18.0.419.gfe4b301394\n>\n"},{"id":"351145","messageId":"20180627183057.254467-1-sbeller@google.com","threadId":"48778","inReplyTo":"20180626073001.6555-14-sunshine@sunshineco.com","subject":"[PATCH] t/lib-submodule-update: fix absorbing test","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-06-27T18:30:57Z","receivedAt":"2018-06-27T18:31:04Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"From: Eric Sunshine <sunshine@sunshineco.com>\n\nThis test has been dysfunctional since it was added by 259f3ee296\n(lib-submodule-update.sh: define tests for recursing into submodules,\n2017-03-14), however, problems went unnoticed due to a broken &&-chain\ntoward the end of the test.\n\nThe test wants to verify that replacing a submodule containing a .git\ndirectory would absorb the .git directory into the .git/modules/ of the\nsuperproject, and then replace the working tree content with the liking of\nthe superproject. The check if submodule content is around is wrong as\nthe submodule should have been replaced by the content of the superproject.\n\nDelete the submodule content check, which also fixes the && chain in the\ntest.\n\nWhile at it, fix broken &&-chains in a couple neighboring tests.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n\n> The test wants to verify that replacing a submodule containing a .git\n> directory must fail. All other \"must fail\" tests in this script invoke\n> the supplied command as 'test_must_fail', however, this test neglects to\n> do so.\n\nIn an ideal world the commands would not fail, but absorb the git directory\nof the submodule. I manually tested that it is absorbed and not data from\na git directory is lost.\n\nI would propose to replace that patch with the patch below; I hope\nthe wording did not add more confusion than there is already.\n\nThanks,\nStefan\n\n\n t/lib-submodule-update.sh | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh\nindex 1f38a85371a..e90ec790877 100755\n--- a/t/lib-submodule-update.sh\n+++ b/t/lib-submodule-update.sh\n@@ -755,7 +755,7 @@ test_submodule_recursing_with_args_common() {\n \t\t\t: >sub1/untrackedfile &&\n \t\t\ttest_must_fail $command replace_sub1_with_file &&\n \t\t\ttest_superproject_content origin/add_sub1 &&\n-\t\t\ttest_submodule_content sub1 origin/add_sub1\n+\t\t\ttest_submodule_content sub1 origin/add_sub1 &&\n \t\t\ttest -f sub1/untracked_file\n \t\t)\n \t'\n@@ -842,7 +842,7 @@ test_submodule_switch_recursing_with_args () {\n \t\t\tcd submodule_update &&\n \t\t\tgit branch -t add_sub1 origin/add_sub1 &&\n \t\t\t: >sub1 &&\n-\t\t\techo sub1 >.git/info/exclude\n+\t\t\techo sub1 >.git/info/exclude &&\n \t\t\t$command add_sub1 &&\n \t\t\ttest_superproject_content origin/add_sub1 &&\n \t\t\ttest_submodule_content sub1 origin/add_sub1\n@@ -969,7 +969,6 @@ test_submodule_forced_switch_recursing_with_args () {\n \t\t\trm -rf .git/modules/sub1 &&\n \t\t\t$command replace_sub1_with_directory &&\n \t\t\ttest_superproject_content origin/replace_sub1_with_directory &&\n-\t\t\ttest_submodule_content sub1 origin/modify_sub1\n \t\t\ttest_git_directory_exists sub1\n \t\t)\n \t'\n-- \n2.18.0.399.gad0ab374a1-goog\n\n"},{"id":"351147","messageId":"CAPig+cSr0owziyxDXexAFgZSurM-Vd+p6sJ5L3zSE_GAwcSO1A@mail.gmail.com","threadId":"48778","inReplyTo":"20180627183057.254467-1-sbeller@google.com","subject":"Re: [PATCH] t/lib-submodule-update: fix absorbing test","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-06-27T18:38:16Z","receivedAt":"2018-06-27T18:38:30Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Jun 27, 2018 at 2:31 PM Stefan Beller <sbeller@google.com> wrote:\n> From: Eric Sunshine <sunshine@sunshineco.com>\n>\n> This test has been dysfunctional since it was added by 259f3ee296\n> (lib-submodule-update.sh: define tests for recursing into submodules,\n> 2017-03-14), however, problems went unnoticed due to a broken &&-chain\n> toward the end of the test.\n> [...]\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Stefan Beller <sbeller@google.com>\n> ---\n> In an ideal world the commands would not fail, but absorb the git directory\n> of the submodule. I manually tested that it is absorbed and not data from\n> a git directory is lost.\n>\n> I would propose to replace that patch with the patch below; I hope\n> the wording did not add more confusion than there is already.\n\nThanks for diagnosing the problem, Stefan. I'm not a submodule user\nand was not at all confident that I had interpreted the test breakage\ncorrectly or that my fix was appropriate, so I'm happy to have a\ndiagnosis and fix from the person who actually wrote the test.\n\nI'll also add a Helped-by: when re-posting.\n"},{"id":"351224","messageId":"20180628143513.GB16657@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CAPig+cRzZ-Nd2b-M8v0NUsLX_gkKwLqyakgcRhwGf3CzsWwxBA@mail.gmail.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-06-28T14:35:13Z","receivedAt":"2018-06-28T14:35:18Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 26, 2018 at 05:13:05PM -0400, Eric Sunshine wrote:\n\n> On Tue, Jun 26, 2018 at 5:01 PM Jeff King <peff@peff.net> wrote:\n> > On Tue, Jun 26, 2018 at 04:46:18PM -0400, Eric Sunshine wrote:\n> > > Some of these dangers can be de-thoothed during the linting phase by\n> > > defining do-nothing shell functions:\n> > >\n> > >     cp () { :; }\n> > >     mv () { :; }\n> > >     ln () { :; }\n> > >\n> > > That, at least, makes the scariest case (\"rm\") much less so.\n> >\n> > Now that's an interesting idea. We can't catch every dangerous action\n> > (notably \">\" would be hard to override), but it should be pretty cheap\n> > to cover some obvious ones.\n> \n> Taking the idea a bit further, the 'sed' script could also throw away\n> strings of \"../\" inside subshells, which would help defang the more\n> difficult cases, like \"echo x >../git.c\". There are pathological\n> cases, of course, which it wouldn't catch:\n> \n>     P=../git.c\n>     test_expect_success 'foo' '\n>         (\n>             cd dir &&\n>             echo x >$P\n>         )\n>     '\n> \n> but it does help mitigate the issue for the most typical cases.\n\nIt seems like the dangerous thing there is \">\", not necessarily \"..\".\nCould we just s/>/x/g ?\n\nThat \"breaks\" the commands in a sense, but the whole point is that these\ncommands shouldn't ever be run in the first place.\n\n-Peff\n"},{"id":"351225","messageId":"20180628143738.GC16657@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CABPp-BHgPgkgQj7i3rZtdbinVZvqH35uGraUK9doL-E8Y5QuSQ@mail.gmail.com","subject":"Re: [PATCH 29/29] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-06-28T14:37:39Z","receivedAt":"2018-06-28T14:37:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jun 26, 2018 at 07:15:45PM -0700, Elijah Newren wrote:\n\n> Crazy idea: maybe we could defang it a little more thoroughly with\n> something like the following (apologies in advance if gmail whitespace\n> damages this):\n> \n> diff --git a/t/test-lib.sh b/t/test-lib.sh\n> index 28315706be..7fda08a90a 100644\n> --- a/t/test-lib.sh\n> +++ b/t/test-lib.sh\n> @@ -675,7 +675,7 @@ test_run_ () {\n>                 trace=\n>                 # 117 is magic because it is unlikely to match the exit\n>                 # code of other programs\n> -               if test \"OK-117\" != \"$(test_eval_ \"(exit 117) &&\n> $1${LF}${LF}echo OK-\\$?\" 3>&1)\"\n> +               if test \"OK-117\" != \"$(test_eval_ \"cd() { return 0; }\n> && PATH=/dev/null && export PATH && (exit 117) && $1${LF}${LF}echo\n> OK-\\$?\" 3>&1)\"\n\nClever. We'd still run shell builtins, which is why you need the cd()\nabove. There may be others, but at least it narrows things down. Unless\nthe shell is busybox or something, and implements everything as a\nbuiltin. :)\n\nI agree on the point elsewhere of returning non-zero (and the items\nmissing from PATH should do that, which is good).\n\n-Peff\n"},{"id":"352202","messageId":"20180711064642.6933-1-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180626073001.6555-1-sunshine@sunshineco.com","subject":"[PATCH v2 00/10] detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:32Z","receivedAt":"2018-07-11T06:48:04Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is a re-roll of [1] which teaches --chain-lint to detect broken\n&&-chains in subshells since the existing implementation[2] detects\nbreakage only at the top-level. It is built atop 'es/test-fixes', which\nwas split off of [1] and submitted separately[3], and which fixes many\nbroken &&-chains.\n\nThe major difference since v1 is that broken &&-chains in subshells are\nnow detected by pure textual inspection rather than by merging subshell\ncommands into the top-level &&-chain. Thus, v2 entirely sidesteps the\nprimary objection[4] raised during v1 review of possible unintended\nside-effects of test code executing outside of the intended directory.\nThe pure textual detection implemented by v2 can have no such\nside-effects.\n\nA second important difference since v1 is that the \"linter\" itself, a\ncomplex 'sed' script, now has its own tests to ensure correct behavior.\nNot only do the tests protect against regressions, but they help to\ndocument (for humans) expected behavior, which is important since 'sed'\nscripts can seem rather inscrutable due to looking like line-noise and\ndue to the 'sed' \"language\" being stack-oriented (albeit with a very\ntiny stack) which, like other stack-oriented languages (Forth,\nPostscript, etc.) can be difficult to reason about.\n\nAlthough the 'sed' script in v1 was already well-commented, the comments\nhave been improved in v2. More importantly, a high-level overview of the\nscript's operation has been added at the top of the file to aid\ncomprehension.\n\nThanks to Elijah, Hannes, Jonathan Nieder, Jonathan Tan, Junio, Luke,\nPeff, and Stefan for comments on v1.\n\n[1]: https://public-inbox.org/git/20180626073001.6555-1-sunshine@sunshineco.com/\n[2]: https://public-inbox.org/git/20150320100429.GA17354@peff.net/\n[3]: https://public-inbox.org/git/20180702002405.3042-1-sunshine@sunshineco.com/\n[4]: https://public-inbox.org/git/xmqqwouljr5e.fsf@gitster-ct.c.googlers.com/\n\nEric Sunshine (10):\n  t/test-lib: teach --chain-lint to detect broken &&-chains in subshells\n  t/Makefile: add machinery to check correctness of chainlint.sed\n  t/chainlint: add chainlint \"basic\" test cases\n  t/chainlint: add chainlint \"whitespace\" test cases\n  t/chainlint: add chainlint \"one-liner\" test cases\n  t/chainlint: add chainlint \"nested subshell\" test cases\n  t/chainlint: add chainlint \"loop\" and \"conditional\" test cases\n  t/chainlint: add chainlint \"cuddled\" test cases\n  t/chainlint: add chainlint \"complex\" test cases\n  t/chainlint: add chainlint \"specialized\" test cases\n\n t/.gitignore                                  |   1 +\n t/Makefile                                    |  25 +-\n t/chainlint.sed                               | 346 ++++++++++++++++++\n t/chainlint/arithmetic-expansion.expect       |   9 +\n t/chainlint/arithmetic-expansion.test         |  11 +\n t/chainlint/bash-array.expect                 |  10 +\n t/chainlint/bash-array.test                   |  12 +\n t/chainlint/blank-line.expect                 |   4 +\n t/chainlint/blank-line.test                   |  10 +\n t/chainlint/block.expect                      |  12 +\n t/chainlint/block.test                        |  15 +\n t/chainlint/broken-chain.expect               |   6 +\n t/chainlint/broken-chain.test                 |   8 +\n t/chainlint/case.expect                       |  19 +\n t/chainlint/case.test                         |  23 ++\n .../close-nested-and-parent-together.expect   |   4 +\n .../close-nested-and-parent-together.test     |   3 +\n t/chainlint/close-subshell.expect             |  25 ++\n t/chainlint/close-subshell.test               |  27 ++\n t/chainlint/command-substitution.expect       |   9 +\n t/chainlint/command-substitution.test         |  11 +\n t/chainlint/comment.expect                    |   4 +\n t/chainlint/comment.test                      |  11 +\n t/chainlint/complex-if-in-cuddled-loop.expect |  10 +\n t/chainlint/complex-if-in-cuddled-loop.test   |  11 +\n t/chainlint/cuddled-if-then-else.expect       |   7 +\n t/chainlint/cuddled-if-then-else.test         |   7 +\n t/chainlint/cuddled-loop.expect               |   5 +\n t/chainlint/cuddled-loop.test                 |   7 +\n t/chainlint/cuddled.expect                    |  21 ++\n t/chainlint/cuddled.test                      |  23 ++\n t/chainlint/exit-loop.expect                  |  24 ++\n t/chainlint/exit-loop.test                    |  27 ++\n t/chainlint/exit-subshell.expect              |   5 +\n t/chainlint/exit-subshell.test                |   6 +\n t/chainlint/for-loop.expect                   |  11 +\n t/chainlint/for-loop.test                     |  19 +\n t/chainlint/here-doc.expect                   |   3 +\n t/chainlint/here-doc.test                     |  16 +\n t/chainlint/if-in-loop.expect                 |  12 +\n t/chainlint/if-in-loop.test                   |  15 +\n t/chainlint/if-then-else.expect               |  19 +\n t/chainlint/if-then-else.test                 |  28 ++\n t/chainlint/incomplete-line.expect            |   4 +\n t/chainlint/incomplete-line.test              |  12 +\n t/chainlint/inline-comment.expect             |   9 +\n t/chainlint/inline-comment.test               |  12 +\n t/chainlint/loop-in-if.expect                 |  12 +\n t/chainlint/loop-in-if.test                   |  15 +\n ...ti-line-nested-command-substitution.expect |   9 +\n ...ulti-line-nested-command-substitution.test |   9 +\n t/chainlint/multi-line-string.expect          |   9 +\n t/chainlint/multi-line-string.test            |  15 +\n t/chainlint/negated-one-liner.expect          |   5 +\n t/chainlint/negated-one-liner.test            |   7 +\n t/chainlint/nested-cuddled-subshell.expect    |  19 +\n t/chainlint/nested-cuddled-subshell.test      |  31 ++\n t/chainlint/nested-here-doc.expect            |   5 +\n t/chainlint/nested-here-doc.test              |  23 ++\n t/chainlint/nested-subshell-comment.expect    |  11 +\n t/chainlint/nested-subshell-comment.test      |  13 +\n t/chainlint/nested-subshell.expect            |  12 +\n t/chainlint/nested-subshell.test              |  14 +\n t/chainlint/one-liner.expect                  |   9 +\n t/chainlint/one-liner.test                    |  12 +\n t/chainlint/p4-filespec.expect                |   4 +\n t/chainlint/p4-filespec.test                  |   5 +\n t/chainlint/pipe.expect                       |   8 +\n t/chainlint/pipe.test                         |  12 +\n t/chainlint/semicolon.expect                  |  20 +\n t/chainlint/semicolon.test                    |  25 ++\n t/chainlint/subshell-here-doc.expect          |   5 +\n t/chainlint/subshell-here-doc.test            |  23 ++\n t/chainlint/subshell-one-liner.expect         |  14 +\n t/chainlint/subshell-one-liner.test           |  24 ++\n t/chainlint/while-loop.expect                 |  11 +\n t/chainlint/while-loop.test                   |  19 +\n t/test-lib.sh                                 |   3 +-\n 78 files changed, 1316 insertions(+), 5 deletions(-)\n create mode 100644 t/chainlint.sed\n create mode 100644 t/chainlint/arithmetic-expansion.expect\n create mode 100644 t/chainlint/arithmetic-expansion.test\n create mode 100644 t/chainlint/bash-array.expect\n create mode 100644 t/chainlint/bash-array.test\n create mode 100644 t/chainlint/blank-line.expect\n create mode 100644 t/chainlint/blank-line.test\n create mode 100644 t/chainlint/block.expect\n create mode 100644 t/chainlint/block.test\n create mode 100644 t/chainlint/broken-chain.expect\n create mode 100644 t/chainlint/broken-chain.test\n create mode 100644 t/chainlint/case.expect\n create mode 100644 t/chainlint/case.test\n create mode 100644 t/chainlint/close-nested-and-parent-together.expect\n create mode 100644 t/chainlint/close-nested-and-parent-together.test\n create mode 100644 t/chainlint/close-subshell.expect\n create mode 100644 t/chainlint/close-subshell.test\n create mode 100644 t/chainlint/command-substitution.expect\n create mode 100644 t/chainlint/command-substitution.test\n create mode 100644 t/chainlint/comment.expect\n create mode 100644 t/chainlint/comment.test\n create mode 100644 t/chainlint/complex-if-in-cuddled-loop.expect\n create mode 100644 t/chainlint/complex-if-in-cuddled-loop.test\n create mode 100644 t/chainlint/cuddled-if-then-else.expect\n create mode 100644 t/chainlint/cuddled-if-then-else.test\n create mode 100644 t/chainlint/cuddled-loop.expect\n create mode 100644 t/chainlint/cuddled-loop.test\n create mode 100644 t/chainlint/cuddled.expect\n create mode 100644 t/chainlint/cuddled.test\n create mode 100644 t/chainlint/exit-loop.expect\n create mode 100644 t/chainlint/exit-loop.test\n create mode 100644 t/chainlint/exit-subshell.expect\n create mode 100644 t/chainlint/exit-subshell.test\n create mode 100644 t/chainlint/for-loop.expect\n create mode 100644 t/chainlint/for-loop.test\n create mode 100644 t/chainlint/here-doc.expect\n create mode 100644 t/chainlint/here-doc.test\n create mode 100644 t/chainlint/if-in-loop.expect\n create mode 100644 t/chainlint/if-in-loop.test\n create mode 100644 t/chainlint/if-then-else.expect\n create mode 100644 t/chainlint/if-then-else.test\n create mode 100644 t/chainlint/incomplete-line.expect\n create mode 100644 t/chainlint/incomplete-line.test\n create mode 100644 t/chainlint/inline-comment.expect\n create mode 100644 t/chainlint/inline-comment.test\n create mode 100644 t/chainlint/loop-in-if.expect\n create mode 100644 t/chainlint/loop-in-if.test\n create mode 100644 t/chainlint/multi-line-nested-command-substitution.expect\n create mode 100644 t/chainlint/multi-line-nested-command-substitution.test\n create mode 100644 t/chainlint/multi-line-string.expect\n create mode 100644 t/chainlint/multi-line-string.test\n create mode 100644 t/chainlint/negated-one-liner.expect\n create mode 100644 t/chainlint/negated-one-liner.test\n create mode 100644 t/chainlint/nested-cuddled-subshell.expect\n create mode 100644 t/chainlint/nested-cuddled-subshell.test\n create mode 100644 t/chainlint/nested-here-doc.expect\n create mode 100644 t/chainlint/nested-here-doc.test\n create mode 100644 t/chainlint/nested-subshell-comment.expect\n create mode 100644 t/chainlint/nested-subshell-comment.test\n create mode 100644 t/chainlint/nested-subshell.expect\n create mode 100644 t/chainlint/nested-subshell.test\n create mode 100644 t/chainlint/one-liner.expect\n create mode 100644 t/chainlint/one-liner.test\n create mode 100644 t/chainlint/p4-filespec.expect\n create mode 100644 t/chainlint/p4-filespec.test\n create mode 100644 t/chainlint/pipe.expect\n create mode 100644 t/chainlint/pipe.test\n create mode 100644 t/chainlint/semicolon.expect\n create mode 100644 t/chainlint/semicolon.test\n create mode 100644 t/chainlint/subshell-here-doc.expect\n create mode 100644 t/chainlint/subshell-here-doc.test\n create mode 100644 t/chainlint/subshell-one-liner.expect\n create mode 100644 t/chainlint/subshell-one-liner.test\n create mode 100644 t/chainlint/while-loop.expect\n create mode 100644 t/chainlint/while-loop.test\n\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352203","messageId":"20180711064642.6933-4-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 03/10] t/chainlint: add chainlint \"basic\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:35Z","receivedAt":"2018-07-11T06:48:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/arithmetic-expansion.expect |  9 +++++++++\n t/chainlint/arithmetic-expansion.test   | 11 ++++++++++\n t/chainlint/broken-chain.expect         |  6 ++++++\n t/chainlint/broken-chain.test           |  8 ++++++++\n t/chainlint/close-subshell.expect       | 25 +++++++++++++++++++++++\n t/chainlint/close-subshell.test         | 27 +++++++++++++++++++++++++\n t/chainlint/command-substitution.expect |  9 +++++++++\n t/chainlint/command-substitution.test   | 11 ++++++++++\n t/chainlint/exit-subshell.expect        |  5 +++++\n t/chainlint/exit-subshell.test          |  6 ++++++\n t/chainlint/multi-line-string.expect    |  9 +++++++++\n t/chainlint/multi-line-string.test      | 15 ++++++++++++++\n t/chainlint/pipe.expect                 |  8 ++++++++\n t/chainlint/pipe.test                   | 12 +++++++++++\n t/chainlint/semicolon.expect            | 20 ++++++++++++++++++\n t/chainlint/semicolon.test              | 25 +++++++++++++++++++++++\n 16 files changed, 206 insertions(+)\n create mode 100644 t/chainlint/arithmetic-expansion.expect\n create mode 100644 t/chainlint/arithmetic-expansion.test\n create mode 100644 t/chainlint/broken-chain.expect\n create mode 100644 t/chainlint/broken-chain.test\n create mode 100644 t/chainlint/close-subshell.expect\n create mode 100644 t/chainlint/close-subshell.test\n create mode 100644 t/chainlint/command-substitution.expect\n create mode 100644 t/chainlint/command-substitution.test\n create mode 100644 t/chainlint/exit-subshell.expect\n create mode 100644 t/chainlint/exit-subshell.test\n create mode 100644 t/chainlint/multi-line-string.expect\n create mode 100644 t/chainlint/multi-line-string.test\n create mode 100644 t/chainlint/pipe.expect\n create mode 100644 t/chainlint/pipe.test\n create mode 100644 t/chainlint/semicolon.expect\n create mode 100644 t/chainlint/semicolon.test\n\ndiff --git a/t/chainlint/arithmetic-expansion.expect b/t/chainlint/arithmetic-expansion.expect\nnew file mode 100644\nindex 0000000000..09457d3196\n--- /dev/null\n+++ b/t/chainlint/arithmetic-expansion.expect\n@@ -0,0 +1,9 @@\n+(\n+\tfoo &&\n+\tbar=$((42 + 1)) &&\n+\tbaz\n+>) &&\n+(\n+?!AMP?!\tbar=$((42 + 1))\n+\tbaz\n+>)\ndiff --git a/t/chainlint/arithmetic-expansion.test b/t/chainlint/arithmetic-expansion.test\nnew file mode 100644\nindex 0000000000..16206960d8\n--- /dev/null\n+++ b/t/chainlint/arithmetic-expansion.test\n@@ -0,0 +1,11 @@\n+(\n+\tfoo &&\n+# LINT: closing \")\" of $((...)) not misinterpreted as subshell-closing \")\"\n+\tbar=$((42 + 1)) &&\n+\tbaz\n+) &&\n+(\n+# LINT: missing \"&&\" on $((...))\n+\tbar=$((42 + 1))\n+\tbaz\n+)\ndiff --git a/t/chainlint/broken-chain.expect b/t/chainlint/broken-chain.expect\nnew file mode 100644\nindex 0000000000..55b0f42a53\n--- /dev/null\n+++ b/t/chainlint/broken-chain.expect\n@@ -0,0 +1,6 @@\n+(\n+\tfoo &&\n+?!AMP?!\tbar\n+\tbaz &&\n+\twop\n+>)\ndiff --git a/t/chainlint/broken-chain.test b/t/chainlint/broken-chain.test\nnew file mode 100644\nindex 0000000000..3cc67b65d0\n--- /dev/null\n+++ b/t/chainlint/broken-chain.test\n@@ -0,0 +1,8 @@\n+(\n+\tfoo &&\n+# LINT: missing \"&&\" from 'bar'\n+\tbar\n+\tbaz &&\n+# LINT: final statement before closing \")\" legitimately lacks \"&&\"\n+\twop\n+)\ndiff --git a/t/chainlint/close-subshell.expect b/t/chainlint/close-subshell.expect\nnew file mode 100644\nindex 0000000000..184688718a\n--- /dev/null\n+++ b/t/chainlint/close-subshell.expect\n@@ -0,0 +1,25 @@\n+(\n+\tfoo\n+>) &&\n+(\n+\tbar\n+>) >out &&\n+(\n+\tbaz\n+>) 2>err &&\n+(\n+\tboo\n+>) <input &&\n+(\n+\tbip\n+>) | wuzzle &&\n+(\n+\tbop\n+>) | fazz \tfozz &&\n+(\n+\tbup\n+>) |\n+fuzzle &&\n+(\n+\tyop\n+>)\ndiff --git a/t/chainlint/close-subshell.test b/t/chainlint/close-subshell.test\nnew file mode 100644\nindex 0000000000..508ca447fd\n--- /dev/null\n+++ b/t/chainlint/close-subshell.test\n@@ -0,0 +1,27 @@\n+# LINT: closing \")\" with various decorations (\"&&\", \">\", \"|\", etc.)\n+(\n+\tfoo\n+) &&\n+(\n+\tbar\n+) >out &&\n+(\n+\tbaz\n+) 2>err &&\n+(\n+\tboo\n+) <input &&\n+(\n+\tbip\n+) | wuzzle &&\n+(\n+\tbop\n+) | fazz \\\n+\tfozz &&\n+(\n+\tbup\n+) |\n+fuzzle &&\n+(\n+\tyop\n+)\ndiff --git a/t/chainlint/command-substitution.expect b/t/chainlint/command-substitution.expect\nnew file mode 100644\nindex 0000000000..ad4118e537\n--- /dev/null\n+++ b/t/chainlint/command-substitution.expect\n@@ -0,0 +1,9 @@\n+(\n+\tfoo &&\n+\tbar=$(gobble) &&\n+\tbaz\n+>) &&\n+(\n+?!AMP?!\tbar=$(gobble blocks)\n+\tbaz\n+>)\ndiff --git a/t/chainlint/command-substitution.test b/t/chainlint/command-substitution.test\nnew file mode 100644\nindex 0000000000..3bbb002a4c\n--- /dev/null\n+++ b/t/chainlint/command-substitution.test\n@@ -0,0 +1,11 @@\n+(\n+\tfoo &&\n+# LINT: closing \")\" of $(...) not misinterpreted as subshell-closing \")\"\n+\tbar=$(gobble) &&\n+\tbaz\n+) &&\n+(\n+# LINT: missing \"&&\" on $(...)\n+\tbar=$(gobble blocks)\n+\tbaz\n+)\ndiff --git a/t/chainlint/exit-subshell.expect b/t/chainlint/exit-subshell.expect\nnew file mode 100644\nindex 0000000000..bf78454f74\n--- /dev/null\n+++ b/t/chainlint/exit-subshell.expect\n@@ -0,0 +1,5 @@\n+(\n+\tfoo || exit 1\n+\tbar &&\n+\tbaz\n+>)\ndiff --git a/t/chainlint/exit-subshell.test b/t/chainlint/exit-subshell.test\nnew file mode 100644\nindex 0000000000..4e6ab69b88\n--- /dev/null\n+++ b/t/chainlint/exit-subshell.test\n@@ -0,0 +1,6 @@\n+(\n+# LINT: \"|| exit {n}\" valid subshell escape without hurting &&-chain\n+\tfoo || exit 1\n+\tbar &&\n+\tbaz\n+)\ndiff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect\nnew file mode 100644\nindex 0000000000..8334c4cc8e\n--- /dev/null\n+++ b/t/chainlint/multi-line-string.expect\n@@ -0,0 +1,9 @@\n+(\n+\tx=line 1\t\tline 2\t\tline 3\" &&\n+?!AMP?!\ty=line 1\t\tline2'\n+\tfoobar\n+>) &&\n+(\n+\techo \"there's nothing to see here\" &&\n+\texit\n+>)\ndiff --git a/t/chainlint/multi-line-string.test b/t/chainlint/multi-line-string.test\nnew file mode 100644\nindex 0000000000..14cb44d51c\n--- /dev/null\n+++ b/t/chainlint/multi-line-string.test\n@@ -0,0 +1,15 @@\n+(\n+\tx=\"line 1\n+\t\tline 2\n+\t\tline 3\" &&\n+# LINT: missing \"&&\" on assignment\n+\ty='line 1\n+\t\tline2'\n+\tfoobar\n+) &&\n+(\n+# LINT: apostrophe (in a contraction) within string not misinterpreted as\n+# LINT: starting multi-line single-quoted string\n+\techo \"there's nothing to see here\" &&\n+\texit\n+)\ndiff --git a/t/chainlint/pipe.expect b/t/chainlint/pipe.expect\nnew file mode 100644\nindex 0000000000..211b901dbc\n--- /dev/null\n+++ b/t/chainlint/pipe.expect\n@@ -0,0 +1,8 @@\n+(\n+\tfoo |\n+\tbar |\n+\tbaz &&\n+\tfish |\n+?!AMP?!\tcow\n+\tsunder\n+>)\ndiff --git a/t/chainlint/pipe.test b/t/chainlint/pipe.test\nnew file mode 100644\nindex 0000000000..e6af4de916\n--- /dev/null\n+++ b/t/chainlint/pipe.test\n@@ -0,0 +1,12 @@\n+(\n+# LINT: no \"&&\" needed on line ending with \"|\"\n+\tfoo |\n+\tbar |\n+\tbaz &&\n+\n+# LINT: final line of pipe sequence ('cow') lacking \"&&\"\n+\tfish |\n+\tcow\n+\n+\tsunder\n+)\ndiff --git a/t/chainlint/semicolon.expect b/t/chainlint/semicolon.expect\nnew file mode 100644\nindex 0000000000..1d79384606\n--- /dev/null\n+++ b/t/chainlint/semicolon.expect\n@@ -0,0 +1,20 @@\n+(\n+?!AMP?!?!SEMI?!\tcat foo ; echo bar\n+?!SEMI?!\tcat foo ; echo bar\n+>) &&\n+(\n+?!SEMI?!\tcat foo ; echo bar &&\n+?!SEMI?!\tcat foo ; echo bar\n+>) &&\n+(\n+\techo \"foo; bar\" &&\n+?!SEMI?!\tcat foo; echo bar\n+>) &&\n+(\n+?!SEMI?!\tfoo;\n+>) &&\n+(\n+cd foo &&\n+\tfor i in a b c; do\n+?!SEMI?!\t\techo;\n+>\tdone)\ndiff --git a/t/chainlint/semicolon.test b/t/chainlint/semicolon.test\nnew file mode 100644\nindex 0000000000..d82c8ebbc0\n--- /dev/null\n+++ b/t/chainlint/semicolon.test\n@@ -0,0 +1,25 @@\n+(\n+# LINT: missing internal \"&&\" and ending \"&&\"\n+\tcat foo ; echo bar\n+# LINT: final statement before \")\" only missing internal \"&&\"\n+\tcat foo ; echo bar\n+) &&\n+(\n+# LINT: missing internal \"&&\"\n+\tcat foo ; echo bar &&\n+\tcat foo ; echo bar\n+) &&\n+(\n+# LINT: not fooled by semicolon in string\n+\techo \"foo; bar\" &&\n+\tcat foo; echo bar\n+) &&\n+(\n+# LINT: unnecessary terminating semicolon\n+\tfoo;\n+) &&\n+(cd foo &&\n+\tfor i in a b c; do\n+# LINT: unnecessary terminating semicolon\n+\t\techo;\n+\tdone)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352204","messageId":"20180711064642.6933-5-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 04/10] t/chainlint: add chainlint \"whitespace\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:36Z","receivedAt":"2018-07-11T06:48:08Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/blank-line.expect        |  4 ++++\n t/chainlint/blank-line.test          | 10 ++++++++++\n t/chainlint/comment.expect           |  4 ++++\n t/chainlint/comment.test             | 11 +++++++++++\n t/chainlint/here-doc.expect          |  3 +++\n t/chainlint/here-doc.test            | 16 ++++++++++++++++\n t/chainlint/incomplete-line.expect   |  4 ++++\n t/chainlint/incomplete-line.test     | 12 ++++++++++++\n t/chainlint/inline-comment.expect    |  9 +++++++++\n t/chainlint/inline-comment.test      | 12 ++++++++++++\n t/chainlint/subshell-here-doc.expect |  5 +++++\n t/chainlint/subshell-here-doc.test   | 23 +++++++++++++++++++++++\n 12 files changed, 113 insertions(+)\n create mode 100644 t/chainlint/blank-line.expect\n create mode 100644 t/chainlint/blank-line.test\n create mode 100644 t/chainlint/comment.expect\n create mode 100644 t/chainlint/comment.test\n create mode 100644 t/chainlint/here-doc.expect\n create mode 100644 t/chainlint/here-doc.test\n create mode 100644 t/chainlint/incomplete-line.expect\n create mode 100644 t/chainlint/incomplete-line.test\n create mode 100644 t/chainlint/inline-comment.expect\n create mode 100644 t/chainlint/inline-comment.test\n create mode 100644 t/chainlint/subshell-here-doc.expect\n create mode 100644 t/chainlint/subshell-here-doc.test\n\ndiff --git a/t/chainlint/blank-line.expect b/t/chainlint/blank-line.expect\nnew file mode 100644\nindex 0000000000..3be939ed38\n--- /dev/null\n+++ b/t/chainlint/blank-line.expect\n@@ -0,0 +1,4 @@\n+(\n+\tnothing &&\n+\tsomething\n+>)\ndiff --git a/t/chainlint/blank-line.test b/t/chainlint/blank-line.test\nnew file mode 100644\nindex 0000000000..f6dd14302b\n--- /dev/null\n+++ b/t/chainlint/blank-line.test\n@@ -0,0 +1,10 @@\n+(\n+\n+\tnothing &&\n+\n+\tsomething\n+# LINT: swallow blank lines since final _statement_ before subshell end is\n+# LINT: significant to \"&&\"-check, not final _line_ (which might be blank)\n+\n+\n+)\ndiff --git a/t/chainlint/comment.expect b/t/chainlint/comment.expect\nnew file mode 100644\nindex 0000000000..3be939ed38\n--- /dev/null\n+++ b/t/chainlint/comment.expect\n@@ -0,0 +1,4 @@\n+(\n+\tnothing &&\n+\tsomething\n+>)\ndiff --git a/t/chainlint/comment.test b/t/chainlint/comment.test\nnew file mode 100644\nindex 0000000000..113c0c466f\n--- /dev/null\n+++ b/t/chainlint/comment.test\n@@ -0,0 +1,11 @@\n+(\n+# LINT: swallow comment lines\n+\t# comment 1\n+\tnothing &&\n+\t# comment 2\n+\tsomething\n+# LINT: swallow comment lines since final _statement_ before subshell end is\n+# LINT: significant to \"&&\"-check, not final _line_ (which might be comment)\n+\t# comment 3\n+\t# comment 4\n+)\ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nnew file mode 100644\nindex 0000000000..2328fe7753\n--- /dev/null\n+++ b/t/chainlint/here-doc.expect\n@@ -0,0 +1,3 @@\n+boodle wobba        gorgo snoot        wafta snurb &&\n+\n+horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nnew file mode 100644\nindex 0000000000..bd36f6e1d3\n--- /dev/null\n+++ b/t/chainlint/here-doc.test\n@@ -0,0 +1,16 @@\n+# LINT: stitch together incomplete \\-ending lines\n+# LINT: swallow here-doc to avoid false positives in content\n+boodle wobba \\\n+       gorgo snoot \\\n+       wafta snurb <<EOF &&\n+quoth the raven,\n+nevermore...\n+EOF\n+\n+# LINT: swallow here-doc (EOF is last line of test)\n+horticulture <<\\EOF\n+gomez\n+morticia\n+wednesday\n+pugsly\n+EOF\ndiff --git a/t/chainlint/incomplete-line.expect b/t/chainlint/incomplete-line.expect\nnew file mode 100644\nindex 0000000000..2f3ebabdc2\n--- /dev/null\n+++ b/t/chainlint/incomplete-line.expect\n@@ -0,0 +1,4 @@\n+line 1 line 2 line 3 line 4 &&\n+(\n+\tline 5 \tline 6 \tline 7 \tline 8\n+>)\ndiff --git a/t/chainlint/incomplete-line.test b/t/chainlint/incomplete-line.test\nnew file mode 100644\nindex 0000000000..d856658083\n--- /dev/null\n+++ b/t/chainlint/incomplete-line.test\n@@ -0,0 +1,12 @@\n+# LINT: stitch together all incomplete \\-ending lines\n+line 1 \\\n+line 2 \\\n+line 3 \\\n+line 4 &&\n+(\n+# LINT: stitch together all incomplete \\-ending lines (subshell)\n+\tline 5 \\\n+\tline 6 \\\n+\tline 7 \\\n+\tline 8\n+)\ndiff --git a/t/chainlint/inline-comment.expect b/t/chainlint/inline-comment.expect\nnew file mode 100644\nindex 0000000000..fc9f250ac4\n--- /dev/null\n+++ b/t/chainlint/inline-comment.expect\n@@ -0,0 +1,9 @@\n+(\n+\tfoobar &&\n+?!AMP?!\tbarfoo\n+\tflibble \"not a # comment\"\n+>) &&\n+\n+(\n+cd foo &&\n+>\tflibble \"not a # comment\")\ndiff --git a/t/chainlint/inline-comment.test b/t/chainlint/inline-comment.test\nnew file mode 100644\nindex 0000000000..8f26856e77\n--- /dev/null\n+++ b/t/chainlint/inline-comment.test\n@@ -0,0 +1,12 @@\n+(\n+# LINT: swallow inline comment (leaving command intact)\n+\tfoobar && # comment 1\n+# LINT: mispositioned \"&&\" (correctly) swallowed with comment\n+\tbarfoo # wrong position for &&\n+# LINT: \"#\" in string not misinterpreted as comment\n+\tflibble \"not a # comment\"\n+) &&\n+\n+# LINT: \"#\" in string in cuddled subshell not misinterpreted as comment\n+(cd foo &&\n+\tflibble \"not a # comment\")\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nnew file mode 100644\nindex 0000000000..19d5aff233\n--- /dev/null\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -0,0 +1,5 @@\n+(\n+\techo wobba \t       gorgo snoot \t       wafta snurb &&\n+?!AMP?!\tcat >bip\n+\techo >bop\n+>)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nnew file mode 100644\nindex 0000000000..9c3564c247\n--- /dev/null\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -0,0 +1,23 @@\n+(\n+# LINT: stitch together incomplete \\-ending lines\n+# LINT: swallow here-doc to avoid false positives in content\n+\techo wobba \\\n+\t       gorgo snoot \\\n+\t       wafta snurb <<-EOF &&\n+\tquoth the raven,\n+\tnevermore...\n+\tEOF\n+\n+# LINT: missing \"&&\" on 'cat'\n+\tcat <<EOF >bip\n+\tfish fly high\n+\tEOF\n+\n+# LINT: swallow here-doc (EOF is last line of subshell)\n+\techo <<-\\EOF >bop\n+\tgomez\n+\tmorticia\n+\twednesday\n+\tpugsly\n+\tEOF\n+)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352205","messageId":"20180711064642.6933-6-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 05/10] t/chainlint: add chainlint \"one-liner\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:37Z","receivedAt":"2018-07-11T06:48:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/negated-one-liner.expect  |  5 +++++\n t/chainlint/negated-one-liner.test    |  7 +++++++\n t/chainlint/one-liner.expect          |  9 +++++++++\n t/chainlint/one-liner.test            | 12 ++++++++++++\n t/chainlint/subshell-one-liner.expect | 14 ++++++++++++++\n t/chainlint/subshell-one-liner.test   | 24 ++++++++++++++++++++++++\n 6 files changed, 71 insertions(+)\n create mode 100644 t/chainlint/negated-one-liner.expect\n create mode 100644 t/chainlint/negated-one-liner.test\n create mode 100644 t/chainlint/one-liner.expect\n create mode 100644 t/chainlint/one-liner.test\n create mode 100644 t/chainlint/subshell-one-liner.expect\n create mode 100644 t/chainlint/subshell-one-liner.test\n\ndiff --git a/t/chainlint/negated-one-liner.expect b/t/chainlint/negated-one-liner.expect\nnew file mode 100644\nindex 0000000000..cf18429d03\n--- /dev/null\n+++ b/t/chainlint/negated-one-liner.expect\n@@ -0,0 +1,5 @@\n+! (foo && bar) &&\n+! (foo && bar) >baz &&\n+\n+?!SEMI?!! (foo; bar) &&\n+?!SEMI?!! (foo; bar) >baz\ndiff --git a/t/chainlint/negated-one-liner.test b/t/chainlint/negated-one-liner.test\nnew file mode 100644\nindex 0000000000..c9598e9153\n--- /dev/null\n+++ b/t/chainlint/negated-one-liner.test\n@@ -0,0 +1,7 @@\n+# LINT: top-level one-liner subshell\n+! (foo && bar) &&\n+! (foo && bar) >baz &&\n+\n+# LINT: top-level one-liner subshell missing internal \"&&\"\n+! (foo; bar) &&\n+! (foo; bar) >baz\ndiff --git a/t/chainlint/one-liner.expect b/t/chainlint/one-liner.expect\nnew file mode 100644\nindex 0000000000..237f227349\n--- /dev/null\n+++ b/t/chainlint/one-liner.expect\n@@ -0,0 +1,9 @@\n+(foo && bar) &&\n+(foo && bar) |\n+(foo && bar) >baz &&\n+\n+?!SEMI?!(foo; bar) &&\n+?!SEMI?!(foo; bar) |\n+?!SEMI?!(foo; bar) >baz\n+\n+(foo \"bar; baz\")\ndiff --git a/t/chainlint/one-liner.test b/t/chainlint/one-liner.test\nnew file mode 100644\nindex 0000000000..ec9acb9825\n--- /dev/null\n+++ b/t/chainlint/one-liner.test\n@@ -0,0 +1,12 @@\n+# LINT: top-level one-liner subshell\n+(foo && bar) &&\n+(foo && bar) |\n+(foo && bar) >baz &&\n+\n+# LINT: top-level one-liner subshell missing internal \"&&\"\n+(foo; bar) &&\n+(foo; bar) |\n+(foo; bar) >baz\n+\n+# LINT: \";\" in string not misinterpreted as broken &&-chain\n+(foo \"bar; baz\")\ndiff --git a/t/chainlint/subshell-one-liner.expect b/t/chainlint/subshell-one-liner.expect\nnew file mode 100644\nindex 0000000000..51162821d7\n--- /dev/null\n+++ b/t/chainlint/subshell-one-liner.expect\n@@ -0,0 +1,14 @@\n+(\n+\t(foo && bar) &&\n+\t(foo && bar) |\n+\t(foo && bar) >baz &&\n+?!SEMI?!\t(foo; bar) &&\n+?!SEMI?!\t(foo; bar) |\n+?!SEMI?!\t(foo; bar) >baz &&\n+\t(foo || exit 1) &&\n+\t(foo || exit 1) |\n+\t(foo || exit 1) >baz &&\n+?!AMP?!\t(foo && bar)\n+?!AMP?!?!SEMI?!\t(foo && bar; baz)\n+\tfoobar\n+>)\ndiff --git a/t/chainlint/subshell-one-liner.test b/t/chainlint/subshell-one-liner.test\nnew file mode 100644\nindex 0000000000..37fa643c20\n--- /dev/null\n+++ b/t/chainlint/subshell-one-liner.test\n@@ -0,0 +1,24 @@\n+(\n+# LINT: nested one-liner subshell\n+\t(foo && bar) &&\n+\t(foo && bar) |\n+\t(foo && bar) >baz &&\n+\n+# LINT: nested one-liner subshell missing internal \"&&\"\n+\t(foo; bar) &&\n+\t(foo; bar) |\n+\t(foo; bar) >baz &&\n+\n+# LINT: nested one-liner subshell with \"|| exit\"\n+\t(foo || exit 1) &&\n+\t(foo || exit 1) |\n+\t(foo || exit 1) >baz &&\n+\n+# LINT: nested one-liner subshell lacking ending \"&&\"\n+\t(foo && bar)\n+\n+# LINT: nested one-liner subshell missing internal \"&&\" and lacking ending \"&&\"\n+\t(foo && bar; baz)\n+\n+\tfoobar\n+)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352206","messageId":"20180711064642.6933-7-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 06/10] t/chainlint: add chainlint \"nested subshell\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:38Z","receivedAt":"2018-07-11T06:48:10Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/block.expect                      | 12 +++++++\n t/chainlint/block.test                        | 15 +++++++++\n ...ti-line-nested-command-substitution.expect |  9 ++++++\n ...ulti-line-nested-command-substitution.test |  9 ++++++\n t/chainlint/nested-cuddled-subshell.expect    | 19 ++++++++++++\n t/chainlint/nested-cuddled-subshell.test      | 31 +++++++++++++++++++\n t/chainlint/nested-here-doc.expect            |  5 +++\n t/chainlint/nested-here-doc.test              | 23 ++++++++++++++\n t/chainlint/nested-subshell-comment.expect    | 11 +++++++\n t/chainlint/nested-subshell-comment.test      | 13 ++++++++\n t/chainlint/nested-subshell.expect            | 12 +++++++\n t/chainlint/nested-subshell.test              | 14 +++++++++\n 12 files changed, 173 insertions(+)\n create mode 100644 t/chainlint/block.expect\n create mode 100644 t/chainlint/block.test\n create mode 100644 t/chainlint/multi-line-nested-command-substitution.expect\n create mode 100644 t/chainlint/multi-line-nested-command-substitution.test\n create mode 100644 t/chainlint/nested-cuddled-subshell.expect\n create mode 100644 t/chainlint/nested-cuddled-subshell.test\n create mode 100644 t/chainlint/nested-here-doc.expect\n create mode 100644 t/chainlint/nested-here-doc.test\n create mode 100644 t/chainlint/nested-subshell-comment.expect\n create mode 100644 t/chainlint/nested-subshell-comment.test\n create mode 100644 t/chainlint/nested-subshell.expect\n create mode 100644 t/chainlint/nested-subshell.test\n\ndiff --git a/t/chainlint/block.expect b/t/chainlint/block.expect\nnew file mode 100644\nindex 0000000000..fed7e89ae8\n--- /dev/null\n+++ b/t/chainlint/block.expect\n@@ -0,0 +1,12 @@\n+(\n+\tfoo &&\n+\t{\n+\t\techo a\n+\t\techo b\n+\t} &&\n+\tbar &&\n+\t{\n+\t\techo c\n+?!AMP?!\t}\n+\tbaz\n+>)\ndiff --git a/t/chainlint/block.test b/t/chainlint/block.test\nnew file mode 100644\nindex 0000000000..d859151af1\n--- /dev/null\n+++ b/t/chainlint/block.test\n@@ -0,0 +1,15 @@\n+(\n+# LINT: missing \"&&\" in block not currently detected (for consistency with\n+# LINT: --chain-lint at top level and to provide escape hatch if needed)\n+\tfoo &&\n+\t{\n+\t\techo a\n+\t\techo b\n+\t} &&\n+\tbar &&\n+# LINT: missing \"&&\" at closing \"}\"\n+\t{\n+\t\techo c\n+\t}\n+\tbaz\n+)\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.expect b/t/chainlint/multi-line-nested-command-substitution.expect\nnew file mode 100644\nindex 0000000000..19c023b1c8\n--- /dev/null\n+++ b/t/chainlint/multi-line-nested-command-substitution.expect\n@@ -0,0 +1,9 @@\n+(\n+\tfoo &&\n+\tx=$(\n+\t\techo bar |\n+\t\tcat\n+>>\t) &&\n+\techo ok\n+>) |\n+sort\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.test b/t/chainlint/multi-line-nested-command-substitution.test\nnew file mode 100644\nindex 0000000000..ca0620ab6b\n--- /dev/null\n+++ b/t/chainlint/multi-line-nested-command-substitution.test\n@@ -0,0 +1,9 @@\n+(\n+\tfoo &&\n+\tx=$(\n+\t\techo bar |\n+\t\tcat\n+\t) &&\n+\techo ok\n+) |\n+sort\ndiff --git a/t/chainlint/nested-cuddled-subshell.expect b/t/chainlint/nested-cuddled-subshell.expect\nnew file mode 100644\nindex 0000000000..c2a59ffc33\n--- /dev/null\n+++ b/t/chainlint/nested-cuddled-subshell.expect\n@@ -0,0 +1,19 @@\n+(\n+\t(cd foo &&\n+\t\tbar\n+>>\t) &&\n+\t(cd foo &&\n+\t\tbar\n+?!AMP?!>>\t)\n+\t(\n+\t\tcd foo &&\n+>>\t\tbar) &&\n+\t(\n+\t\tcd foo &&\n+?!AMP?!>>\t\tbar)\n+\t(cd foo &&\n+>>\t\tbar) &&\n+\t(cd foo &&\n+?!AMP?!>>\t\tbar)\n+\tfoobar\n+>)\ndiff --git a/t/chainlint/nested-cuddled-subshell.test b/t/chainlint/nested-cuddled-subshell.test\nnew file mode 100644\nindex 0000000000..8fd656c7b5\n--- /dev/null\n+++ b/t/chainlint/nested-cuddled-subshell.test\n@@ -0,0 +1,31 @@\n+(\n+# LINT: opening \"(\" cuddled with first nested subshell statement\n+\t(cd foo &&\n+\t\tbar\n+\t) &&\n+\n+# LINT: same but \"&&\" missing\n+\t(cd foo &&\n+\t\tbar\n+\t)\n+\n+# LINT: closing \")\" cuddled with final nested subshell statement\n+\t(\n+\t\tcd foo &&\n+\t\tbar) &&\n+\n+# LINT: same but \"&&\" missing\n+\t(\n+\t\tcd foo &&\n+\t\tbar)\n+\n+# LINT: \"(\" and \")\" cuddled with first and final subshell statements\n+\t(cd foo &&\n+\t\tbar) &&\n+\n+# LINT: same but \"&&\" missing\n+\t(cd foo &&\n+\t\tbar)\n+\n+\tfoobar\n+)\ndiff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect\nnew file mode 100644\nindex 0000000000..559301e005\n--- /dev/null\n+++ b/t/chainlint/nested-here-doc.expect\n@@ -0,0 +1,5 @@\n+(\n+\tcat &&\n+?!AMP?!\tcat\n+\tfoobar\n+>)\ndiff --git a/t/chainlint/nested-here-doc.test b/t/chainlint/nested-here-doc.test\nnew file mode 100644\nindex 0000000000..027e0bb3ff\n--- /dev/null\n+++ b/t/chainlint/nested-here-doc.test\n@@ -0,0 +1,23 @@\n+(\n+# LINT: inner \"EOF\" not misintrepreted as closing INPUT_END here-doc\n+\tcat <<-\\INPUT_END &&\n+\tfish are mice\n+\tbut geese go slow\n+\tdata <<EOF\n+\t\tperl is lerp\n+\t\tand nothing else\n+\tEOF\n+\ttoink\n+\tINPUT_END\n+\n+# LINT: same but missing \"&&\"\n+\tcat <<-\\EOT\n+\ttext goes here\n+\tdata <<EOF\n+\t\tdata goes here\n+\tEOF\n+\tmore test here\n+\tEOT\n+\n+\tfoobar\n+)\ndiff --git a/t/chainlint/nested-subshell-comment.expect b/t/chainlint/nested-subshell-comment.expect\nnew file mode 100644\nindex 0000000000..15b68d4373\n--- /dev/null\n+++ b/t/chainlint/nested-subshell-comment.expect\n@@ -0,0 +1,11 @@\n+(\n+\tfoo &&\n+\t(\n+\t\tbar &&\n+\t\t# bottles wobble while fiddles gobble\n+\t\t# minor numbers of cows (or do they?)\n+\t\tbaz &&\n+\t\tsnaff\n+?!AMP?!>>\t)\n+\tfuzzy\n+>)\ndiff --git a/t/chainlint/nested-subshell-comment.test b/t/chainlint/nested-subshell-comment.test\nnew file mode 100644\nindex 0000000000..0ff136ab3c\n--- /dev/null\n+++ b/t/chainlint/nested-subshell-comment.test\n@@ -0,0 +1,13 @@\n+(\n+\tfoo &&\n+\t(\n+\t\tbar &&\n+# LINT: \")\" in comment in nested subshell not misinterpreted as closing \")\"\n+\t\t# bottles wobble while fiddles gobble\n+\t\t# minor numbers of cows (or do they?)\n+\t\tbaz &&\n+\t\tsnaff\n+# LINT: missing \"&&\" on ')'\n+\t)\n+\tfuzzy\n+)\ndiff --git a/t/chainlint/nested-subshell.expect b/t/chainlint/nested-subshell.expect\nnew file mode 100644\nindex 0000000000..c8165ad19e\n--- /dev/null\n+++ b/t/chainlint/nested-subshell.expect\n@@ -0,0 +1,12 @@\n+(\n+\tcd foo &&\n+\t(\n+\t\techo a &&\n+\t\techo b\n+>>\t) >file &&\n+\tcd foo &&\n+\t(\n+\t\techo a\n+\t\techo b\n+>>\t) >file\n+>)\ndiff --git a/t/chainlint/nested-subshell.test b/t/chainlint/nested-subshell.test\nnew file mode 100644\nindex 0000000000..998b05a47d\n--- /dev/null\n+++ b/t/chainlint/nested-subshell.test\n@@ -0,0 +1,14 @@\n+(\n+\tcd foo &&\n+\t(\n+\t\techo a &&\n+\t\techo b\n+\t) >file &&\n+\n+\tcd foo &&\n+\t(\n+# LINT: nested multi-line subshell not presently checked for missing \"&&\"\n+\t\techo a\n+\t\techo b\n+\t) >file\n+)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352207","messageId":"20180711064642.6933-2-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:33Z","receivedAt":"2018-07-11T06:48:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option detects broken &&-chains by forcing the test to\nexit early (as the very first step) with a sentinel value. If that\nsentinel is the test's overall exit code, then the &&-chain is intact;\nif not, then the chain is broken. Unfortunately, this detection does not\nextend to &&-chains within subshells even when the subshell itself is\nproperly linked into the outer &&-chain.\n\nAddress this shortcoming by feeding the body of the test to a\nlightweight \"linter\" which can peer inside subshells and identify broken\n&&-chains by pure textual inspection. Although the linter does not\nactually parse shell scripts, it has enough knowledge of shell syntax to\nreliably deal with formatting style variations (as evolved over the\nyears) and to avoid being fooled by non-shell content (such as inside\nhere-docs and multi-line strings). It recognizes modern subshell\nformatting:\n\n    statement1 &&\n    (\n        statement2 &&\n        statement3\n    ) &&\n    statement4\n\nas well as old-style:\n\n    statement1 &&\n    (statement2 &&\n     statement3) &&\n    statement4\n\nHeuristics are employed to properly identify the extent of a subshell\nformatted in the old-style since a number of legitimate constructs may\nsuperficially appear to close the subshell even though they don't. For\nexample, it understands that neither \"x=$(command)\" nor \"case $x in *)\"\nend a subshell, despite the \")\" at the end of line.\n\nDue to limitations of the tool used ('sed') and its inherent\nline-by-line processing, only subshells one level deep are handled, as\nwell as one-liner subshells one level below that. Subshells deeper than\nthat or multi-line subshells at level two are passed through as-is, thus\n&&-chains in their bodies are not checked.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed | 346 ++++++++++++++++++++++++++++++++++++++++++++++++\n t/test-lib.sh   |   3 +-\n 2 files changed, 348 insertions(+), 1 deletion(-)\n create mode 100644 t/chainlint.sed\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nnew file mode 100644\nindex 0000000000..a0de8a3882\n--- /dev/null\n+++ b/t/chainlint.sed\n@@ -0,0 +1,346 @@\n+#------------------------------------------------------------------------------\n+# Detect broken &&-chains in tests.\n+#\n+# At present, only &&-chains in subshells are examined by this linter;\n+# top-level &&-chains are instead checked directly by the test framework. Like\n+# the top-level &&-chain linter, the subshell linter (intentionally) does not\n+# check &&-chains within {...} blocks.\n+#\n+# Checking for &&-chain breakage is done line-by-line by pure textual\n+# inspection.\n+#\n+# Incomplete lines (those ending with \"\\\") are stitched together with following\n+# lines to simplify processing, particularly of \"one-liner\" statements.\n+# Top-level here-docs are swallowed to avoid false positives within the\n+# here-doc body, although the statement to which the here-doc is attached is\n+# retained.\n+#\n+# Heuristics are used to detect end-of-subshell when the closing \")\" is cuddled\n+# with the final subshell statement on the same line:\n+#\n+#    (cd foo &&\n+#        bar)\n+#\n+# in order to avoid misinterpreting the \")\" in constructs such as \"x=$(...)\"\n+# and \"case $x in *)\" as ending the subshell.\n+#\n+# Lines missing a final \"&&\" are flagged with \"?!AMP?!\", and lines which chain\n+# commands with \";\" internally rather than \"&&\" are flagged \"?!SEMI?!\". A line\n+# may be flagged for both violations.\n+#\n+# Detection of a missing &&-link in a multi-line subshell is complicated by the\n+# fact that the last statement before the closing \")\" must not end with \"&&\".\n+# Since processing is line-by-line, it is not known whether a missing \"&&\" is\n+# legitimate or not until the _next_ line is seen. To accommodate this, within\n+# multi-line subshells, each line is stored in sed's \"hold\" area until after\n+# the next line is seen and processed. If the next line is a stand-alone \")\",\n+# then a missing \"&&\" on the previous line is legitimate; otherwise a missing\n+# \"&&\" is a break in the &&-chain.\n+#\n+#    (\n+#         cd foo &&\n+#         bar\n+#    )\n+#\n+# In practical terms, when \"bar\" is encountered, it is flagged with \"?!AMP?!\",\n+# but when the stand-alone \")\" line is seen which closes the subshell, the\n+# \"?!AMP?!\" violation is removed from the \"bar\" line (retrieved from the \"hold\"\n+# area) since the final statement of a subshell must not end with \"&&\". The\n+# final line of a subshell may still break the &&-chain by using \";\" internally\n+# to chain commands together rather than \"&&\", so \"?!SEMI?!\" is never removed\n+# from a line (even though \"?!AMP?!\" might be).\n+#\n+# Care is taken to recognize the last _statement_ of a multi-line subshell, not\n+# necessarily the last textual _line_ within the subshell, since &&-chaining\n+# applies to statements, not to lines. Consequently, blank lines, comment\n+# lines, and here-docs are swallowed (but not the command to which the here-doc\n+# is attached), leaving the last statement in the \"hold\" area, not the last\n+# line, thus simplifying &&-link checking.\n+#\n+# The final statement before \"done\" in for- and while-loops, and before \"elif\",\n+# \"else\", and \"fi\" in if-then-else likewise must not end with \"&&\", thus\n+# receives similar treatment.\n+#\n+# To facilitate regression testing (and manual debugging), a \">\" annotation is\n+# applied to the line containing \")\" which closes a subshell, \">>\" to a line\n+# closing a nested subshell, and \">>>\" to a line closing both at once. This\n+# makes it easy to detect whether the heuristics correctly identify\n+# end-of-subshell.\n+#------------------------------------------------------------------------------\n+\n+# incomplete line -- slurp up next line\n+:squash\n+/\\\\$/ {\n+      N\n+      s/\\\\\\n//\n+      bsquash\n+}\n+\n+# here-doc -- swallow it to avoid false hits within its body (but keep the\n+# command to which it was attached)\n+/<<[ \t]*[-\\\\]*EOF[ \t]*/ {\n+\ts/[ \t]*<<[ \t]*[-\\\\]*EOF//\n+\th\n+\t:hereslurp\n+\tN\n+\ts/.*\\n//\n+\t/^[ \t]*EOF[ \t]*$/!bhereslurp\n+\tx\n+}\n+\n+# one-liner \"(...) &&\"\n+/^[ \t]*!*[ \t]*(..*)[ \t]*&&[ \t]*$/boneline\n+\n+# same as above but without trailing \"&&\"\n+/^[ \t]*!*[ \t]*(..*)[ \t]*$/boneline\n+\n+# one-liner \"(...) >x\" (or \"2>x\" or \"<x\" or \"|x\" or \"&\"\n+/^[ \t]*!*[ \t]*(..*)[ \t]*[0-9]*[<>|&]/boneline\n+\n+# multi-line \"(...\\n...)\"\n+/^[ \t]*(/bsubshell\n+\n+# innocuous line -- print it and advance to next line\n+b\n+\n+# found one-liner \"(...)\" -- mark suspect if it uses \";\" internally rather than\n+# \"&&\" (but not \";\" in a string)\n+:oneline\n+/;/{\n+\t/\"[^\"]*;[^\"]*\"/!s/^/?!SEMI?!/\n+}\n+b\n+\n+:subshell\n+# bare \"(\" line?\n+/^[ \t]*([\t]*$/ {\n+\t# stash for later printing\n+\th\n+\tbnextline\n+}\n+# \"(...\" line -- split off and stash \"(\", then process \"...\" as its own line\n+x\n+s/.*/(/\n+x\n+s/(//\n+bslurp\n+\n+:nextline\n+N\n+s/.*\\n//\n+\n+:slurp\n+# incomplete line \"...\\\"\n+/\\\\$/bincomplete\n+# multi-line quoted string \"...\\n...\"\n+/^[^\"]*\"[^\"]*$/bdqstring\n+# multi-line quoted string '...\\n...' (but not contraction in string \"it's so\")\n+/^[^']*'[^']*$/{\n+\t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n+}\n+# here-doc -- swallow it\n+/<<[ \t]*[-\\\\]*EOF/bheredoc\n+/<<[ \t]*[-\\\\]*EOT/bheredoc\n+/<<[ \t]*[-\\\\]*INPUT_END/bheredoc\n+# comment or empty line -- discard since final non-comment, non-empty line\n+# before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n+# re-visited to drop \"suspect\" marking since final line of those constructs\n+# legitimately lacks \"&&\", so \"suspect\" mark must be removed\n+/^[ \t]*#/bnextline\n+/^[ \t]*$/bnextline\n+# in-line comment -- strip it (but not \"#\" in a string, Bash ${#...} array\n+# length, or Perforce \"//depot/path#42\" revision in filespec)\n+/[ \t]#/{\n+\t/\"[^\"]*#[^\"]*\"/!s/[ \t]#.*$//\n+}\n+# one-liner \"case ... esac\"\n+/^[ \t]*case[ \t]*..*esac/bcheckchain\n+# multi-line \"case ... esac\"\n+/^[ \t]*case[ \t]..*[ \t]in/bcase\n+# multi-line \"for ... done\" or \"while ... done\"\n+/^[ \t]*for[ \t]..*[ \t]in/bcontinue\n+/^[ \t]*while[ \t]/bcontinue\n+/^[ \t]*do[ \t]/bcontinue\n+/^[ \t]*do[ \t]*$/bcontinue\n+/;[ \t]*do/bcontinue\n+/^[ \t]*done[ \t]*&&[ \t]*$/bdone\n+/^[ \t]*done[ \t]*$/bdone\n+/^[ \t]*done[ \t]*[<>|]/bdone\n+/^[ \t]*done[ \t]*)/bdone\n+/||[ \t]*exit[ \t]/bcontinue\n+/||[ \t]*exit[ \t]*$/bcontinue\n+# multi-line \"if...elsif...else...fi\"\n+/^[ \t]*if[ \t]/bcontinue\n+/^[ \t]*then[ \t]/bcontinue\n+/^[ \t]*then[ \t]*$/bcontinue\n+/;[ \t]*then/bcontinue\n+/^[ \t]*elif[ \t]/belse\n+/^[ \t]*elif[ \t]*$/belse\n+/^[ \t]*else[ \t]/belse\n+/^[ \t]*else[ \t]*$/belse\n+/^[ \t]*fi[ \t]*&&[ \t]*$/bdone\n+/^[ \t]*fi[ \t]*$/bdone\n+/^[ \t]*fi[ \t]*[<>|]/bdone\n+/^[ \t]*fi[ \t]*)/bdone\n+# nested one-liner \"(...) &&\"\n+/^[ \t]*(.*)[ \t]*&&[ \t]*$/bcheckchain\n+# nested one-liner \"(...)\"\n+/^[ \t]*(.*)[ \t]*$/bcheckchain\n+# nested one-liner \"(...) >x\" (or \"2>x\" or \"<x\" or \"|x\")\n+/^[ \t]*(.*)[ \t]*[0-9]*[<>|]/bcheckchain\n+# nested multi-line \"(...\\n...)\"\n+/^[ \t]*(/bnest\n+# multi-line \"{...\\n...}\"\n+/^[ \t]*{/bblock\n+# closing \")\" on own line -- exit subshell\n+/^[ \t]*)/bclosesolo\n+# \"$((...))\" -- arithmetic expansion; not closing \")\"\n+/\\$(([^)][^)]*))[^)]*$/bcheckchain\n+# \"$(...)\" -- command substitution; not closing \")\"\n+/\\$([^)][^)]*)[^)]*$/bcheckchain\n+# multi-line \"$(...\\n...)\" -- command substitution; treat as nested subshell\n+/\\$([ \t     ]*$/bnest\n+# \"=(...)\" -- Bash array assignment; not closing \")\"\n+/=(/bcheckchain\n+# closing \"...) &&\"\n+/)[ \t]*&&[ \t]*$/bclose\n+# closing \"...)\"\n+/)[ \t]*$/bclose\n+# closing \"...) >x\" (or \"2>x\" or \"<x\" or \"|x\")\n+/)[ \t]*[<>|]/bclose\n+:checkchain\n+# mark suspect if line uses \";\" internally rather than \"&&\" (but not \";\" in a\n+# string and not \";;\" in one-liner \"case...esac\")\n+/;/{\n+\t/;;/!{\n+\t\t/\"[^\"]*;[^\"]*\"/!s/^/?!SEMI?!/\n+\t}\n+}\n+# line ends with pipe \"...|\" -- valid; not missing \"&&\"\n+/|[ \t]*$/bcontinue\n+# missing end-of-line \"&&\" -- mark suspect\n+/&&[ \t]*$/!s/^/?!AMP?!/\n+:continue\n+# retrieve and print previous line\n+x\n+n\n+bslurp\n+\n+# found incomplete line \"...\\\" -- slurp up next line\n+:incomplete\n+N\n+s/\\\\\\n//\n+bslurp\n+\n+# found multi-line double-quoted string \"...\\n...\" -- slurp until end of string\n+:dqstring\n+s/\"//g\n+N\n+s/\\n//\n+/\"/!bdqstring\n+bcheckchain\n+\n+# found multi-line single-quoted string '...\\n...' -- slurp until end of string\n+:sqstring\n+s/'//g\n+N\n+s/\\n//\n+/'/!bsqstring\n+bcheckchain\n+\n+# found here-doc -- swallow it to avoid false hits within its body (but keep\n+# the command to which it was attached); take care to handle here-docs nested\n+# within here-docs by only recognizing closing tag matching outer here-doc\n+# opening tag\n+:heredoc\n+/EOF/{ s/[ \t]*<<[ \t]*[-\\\\]*EOF//; s/^/EOF/; }\n+/EOT/{ s/[ \t]*<<[ \t]*[-\\\\]*EOT//; s/^/EOT/; }\n+/INPUT_END/{ s/[ \t]*<<[ \t]*[-\\\\]*INPUT_END//; s/^/INPUT_END/; }\n+:hereslurpsub\n+N\n+/^EOF.*\\n[ \t]*EOF[ \t]*$/bhereclose\n+/^EOT.*\\n[ \t]*EOT[ \t]*$/bhereclose\n+/^INPUT_END.*\\n[ \t]*INPUT_END[ \t]*$/bhereclose\n+bhereslurpsub\n+:hereclose\n+s/^EOF//\n+s/^EOT//\n+s/^INPUT_END//\n+s/\\n.*$//\n+bcheckchain\n+\n+# found \"case ... in\" -- pass through untouched\n+:case\n+x\n+n\n+/^[ \t]*esac/bslurp\n+bcase\n+\n+# found \"else\" or \"elif\" -- drop \"suspect\" from final line before \"else\" since\n+# that line legitimately lacks \"&&\"\n+:else\n+x\n+s/?!AMP?!//\n+x\n+bcontinue\n+\n+# found \"done\" closing for-loop or while-loop, or \"fi\" closing if-then -- drop\n+# \"suspect\" from final contained line since that line legitimately lacks \"&&\"\n+:done\n+x\n+s/?!AMP?!//\n+x\n+# is 'done' or 'fi' cuddled with \")\" to close subshell?\n+/done.*)/bclose\n+/fi.*)/bclose\n+bcheckchain\n+\n+# found nested multi-line \"(...\\n...)\" -- pass through untouched\n+:nest\n+x\n+:nestslurp\n+n\n+# closing \")\" on own line -- stop nested slurp\n+/^[ \t]*)/bnestclose\n+# comment -- not closing \")\" if in comment\n+/^[ \t]*#/bnestcontinue\n+# \"$((...))\" -- arithmetic expansion; not closing \")\"\n+/\\$(([^)][^)]*))[^)]*$/bnestcontinue\n+# \"$(...)\" -- command substitution; not closing \")\"\n+/\\$([^)][^)]*)[^)]*$/bnestcontinue\n+# closing \"...)\" -- stop nested slurp\n+/)/bnestclose\n+:nestcontinue\n+x\n+bnestslurp\n+:nestclose\n+s/^/>>/\n+# is it \"))\" which closes nested and parent subshells?\n+/)[ \t]*)/bslurp\n+bcheckchain\n+\n+# found multi-line \"{...\\n...}\" block -- pass through untouched\n+:block\n+x\n+n\n+# closing \"}\" -- stop block slurp\n+/}/bcheckchain\n+bblock\n+\n+# found closing \")\" on own line -- drop \"suspect\" from final line of subshell\n+# since that line legitimately lacks \"&&\" and exit subshell loop\n+:closesolo\n+x\n+s/?!AMP?!//\n+p\n+x\n+s/^/>/\n+b\n+\n+# found closing \"...)\" -- exit subshell loop\n+:close\n+x\n+p\n+x\n+s/^/>/\n+b\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 28315706be..78f7097746 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -675,7 +675,8 @@ test_run_ () {\n \t\ttrace=\n \t\t# 117 is magic because it is unlikely to match the exit\n \t\t# code of other programs\n-\t\tif test \"OK-117\" != \"$(test_eval_ \"(exit 117) && $1${LF}${LF}echo OK-\\$?\" 3>&1)\"\n+\t\tif $(printf '%s\\n' \"$1\" | sed -f \"$GIT_BUILD_DIR/t/chainlint.sed\" | grep -q '?![A-Z][A-Z]*?!') ||\n+\t\t\ttest \"OK-117\" != \"$(test_eval_ \"(exit 117) && $1${LF}${LF}echo OK-\\$?\" 3>&1)\"\n \t\tthen\n \t\t\terror \"bug in the test script: broken &&-chain or run-away HERE-DOC: $1\"\n \t\tfi\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352209","messageId":"20180711064642.6933-10-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 09/10] t/chainlint: add chainlint \"complex\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:41Z","receivedAt":"2018-07-11T06:48:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n .../close-nested-and-parent-together.expect       |  4 ++++\n t/chainlint/close-nested-and-parent-together.test |  3 +++\n t/chainlint/complex-if-in-cuddled-loop.expect     | 10 ++++++++++\n t/chainlint/complex-if-in-cuddled-loop.test       | 11 +++++++++++\n t/chainlint/if-in-loop.expect                     | 12 ++++++++++++\n t/chainlint/if-in-loop.test                       | 15 +++++++++++++++\n t/chainlint/loop-in-if.expect                     | 12 ++++++++++++\n t/chainlint/loop-in-if.test                       | 15 +++++++++++++++\n 8 files changed, 82 insertions(+)\n create mode 100644 t/chainlint/close-nested-and-parent-together.expect\n create mode 100644 t/chainlint/close-nested-and-parent-together.test\n create mode 100644 t/chainlint/complex-if-in-cuddled-loop.expect\n create mode 100644 t/chainlint/complex-if-in-cuddled-loop.test\n create mode 100644 t/chainlint/if-in-loop.expect\n create mode 100644 t/chainlint/if-in-loop.test\n create mode 100644 t/chainlint/loop-in-if.expect\n create mode 100644 t/chainlint/loop-in-if.test\n\ndiff --git a/t/chainlint/close-nested-and-parent-together.expect b/t/chainlint/close-nested-and-parent-together.expect\nnew file mode 100644\nindex 0000000000..2a910f9d66\n--- /dev/null\n+++ b/t/chainlint/close-nested-and-parent-together.expect\n@@ -0,0 +1,4 @@\n+(\n+cd foo &&\n+\t(bar &&\n+>>>\t\tbaz))\ndiff --git a/t/chainlint/close-nested-and-parent-together.test b/t/chainlint/close-nested-and-parent-together.test\nnew file mode 100644\nindex 0000000000..72d482f76d\n--- /dev/null\n+++ b/t/chainlint/close-nested-and-parent-together.test\n@@ -0,0 +1,3 @@\n+(cd foo &&\n+\t(bar &&\n+\t\tbaz))\ndiff --git a/t/chainlint/complex-if-in-cuddled-loop.expect b/t/chainlint/complex-if-in-cuddled-loop.expect\nnew file mode 100644\nindex 0000000000..9674b88cf2\n--- /dev/null\n+++ b/t/chainlint/complex-if-in-cuddled-loop.expect\n@@ -0,0 +1,10 @@\n+(\n+for i in a b c; do\n+   if test \"$(echo $(waffle bat))\" = \"eleventeen\" &&\n+     test \"$x\" = \"$y\"; then\n+     :\n+   else\n+     echo >file\n+   fi\n+> done) &&\n+test ! -f file\ndiff --git a/t/chainlint/complex-if-in-cuddled-loop.test b/t/chainlint/complex-if-in-cuddled-loop.test\nnew file mode 100644\nindex 0000000000..571bbd85cd\n--- /dev/null\n+++ b/t/chainlint/complex-if-in-cuddled-loop.test\n@@ -0,0 +1,11 @@\n+# LINT: 'for' loop cuddled with \"(\" and \")\" and nested 'if' with complex\n+# LINT: multi-line condition; indented with spaces, not tabs\n+(for i in a b c; do\n+   if test \"$(echo $(waffle bat))\" = \"eleventeen\" &&\n+     test \"$x\" = \"$y\"; then\n+     :\n+   else\n+     echo >file\n+   fi\n+ done) &&\n+test ! -f file\ndiff --git a/t/chainlint/if-in-loop.expect b/t/chainlint/if-in-loop.expect\nnew file mode 100644\nindex 0000000000..03d3ceb22d\n--- /dev/null\n+++ b/t/chainlint/if-in-loop.expect\n@@ -0,0 +1,12 @@\n+(\n+\tfor i in a b c\n+\tdo\n+\t\tif false\n+\t\tthen\n+?!AMP?!\t\t\techo \"err\"\n+\t\t\texit 1\n+?!AMP?!\t\tfi\n+\t\tfoo\n+?!AMP?!\tdone\n+\tbar\n+>)\ndiff --git a/t/chainlint/if-in-loop.test b/t/chainlint/if-in-loop.test\nnew file mode 100644\nindex 0000000000..daf22da164\n--- /dev/null\n+++ b/t/chainlint/if-in-loop.test\n@@ -0,0 +1,15 @@\n+(\n+\tfor i in a b c\n+\tdo\n+\t\tif false\n+\t\tthen\n+# LINT: missing \"&&\" on 'echo'\n+\t\t\techo \"err\"\n+\t\t\texit 1\n+# LINT: missing \"&&\" on 'fi'\n+\t\tfi\n+\t\tfoo\n+# LINT: missing \"&&\" on 'done'\n+\tdone\n+\tbar\n+)\ndiff --git a/t/chainlint/loop-in-if.expect b/t/chainlint/loop-in-if.expect\nnew file mode 100644\nindex 0000000000..088e622c31\n--- /dev/null\n+++ b/t/chainlint/loop-in-if.expect\n@@ -0,0 +1,12 @@\n+(\n+\tif true\n+\tthen\n+\t\twhile true\n+\t\tdo\n+?!AMP?!\t\t\techo \"pop\"\n+\t\t\techo \"glup\"\n+?!AMP?!\t\tdone\n+\t\tfoo\n+?!AMP?!\tfi\n+\tbar\n+>)\ndiff --git a/t/chainlint/loop-in-if.test b/t/chainlint/loop-in-if.test\nnew file mode 100644\nindex 0000000000..93e8ba8e4d\n--- /dev/null\n+++ b/t/chainlint/loop-in-if.test\n@@ -0,0 +1,15 @@\n+(\n+\tif true\n+\tthen\n+\t\twhile true\n+\t\tdo\n+# LINT: missing \"&&\" on 'echo'\n+\t\t\techo \"pop\"\n+\t\t\techo \"glup\"\n+# LINT: missing \"&&\" on 'done'\n+\t\tdone\n+\t\tfoo\n+# LINT: missing \"&&\" on 'fi'\n+\tfi\n+\tbar\n+)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352208","messageId":"20180711064642.6933-8-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 07/10] t/chainlint: add chainlint \"loop\" and \"conditional\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:39Z","receivedAt":"2018-07-11T06:48:13Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/case.expect         | 19 +++++++++++++++++++\n t/chainlint/case.test           | 23 +++++++++++++++++++++++\n t/chainlint/exit-loop.expect    | 24 ++++++++++++++++++++++++\n t/chainlint/exit-loop.test      | 27 +++++++++++++++++++++++++++\n t/chainlint/for-loop.expect     | 11 +++++++++++\n t/chainlint/for-loop.test       | 19 +++++++++++++++++++\n t/chainlint/if-then-else.expect | 19 +++++++++++++++++++\n t/chainlint/if-then-else.test   | 28 ++++++++++++++++++++++++++++\n t/chainlint/while-loop.expect   | 11 +++++++++++\n t/chainlint/while-loop.test     | 19 +++++++++++++++++++\n 10 files changed, 200 insertions(+)\n create mode 100644 t/chainlint/case.expect\n create mode 100644 t/chainlint/case.test\n create mode 100644 t/chainlint/exit-loop.expect\n create mode 100644 t/chainlint/exit-loop.test\n create mode 100644 t/chainlint/for-loop.expect\n create mode 100644 t/chainlint/for-loop.test\n create mode 100644 t/chainlint/if-then-else.expect\n create mode 100644 t/chainlint/if-then-else.test\n create mode 100644 t/chainlint/while-loop.expect\n create mode 100644 t/chainlint/while-loop.test\n\ndiff --git a/t/chainlint/case.expect b/t/chainlint/case.expect\nnew file mode 100644\nindex 0000000000..41f121fbbf\n--- /dev/null\n+++ b/t/chainlint/case.expect\n@@ -0,0 +1,19 @@\n+(\n+\tcase \"$x\" in\n+\tx) foo ;;\n+\t*) bar ;;\n+\tesac &&\n+\tfoobar\n+>) &&\n+(\n+\tcase \"$x\" in\n+\tx) foo ;;\n+\t*) bar ;;\n+?!AMP?!\tesac\n+\tfoobar\n+>) &&\n+(\n+\tcase \"$x\" in 1) true;; esac &&\n+?!AMP?!\tcase \"$y\" in 2) false;; esac\n+\tfoobar\n+>)\ndiff --git a/t/chainlint/case.test b/t/chainlint/case.test\nnew file mode 100644\nindex 0000000000..5ef6ff7db5\n--- /dev/null\n+++ b/t/chainlint/case.test\n@@ -0,0 +1,23 @@\n+(\n+# LINT: \"...)\" arms in 'case' not misinterpreted as subshell-closing \")\"\n+\tcase \"$x\" in\n+\tx) foo ;;\n+\t*) bar ;;\n+\tesac &&\n+\tfoobar\n+) &&\n+(\n+# LINT: missing \"&&\" on 'esac'\n+\tcase \"$x\" in\n+\tx) foo ;;\n+\t*) bar ;;\n+\tesac\n+\tfoobar\n+) &&\n+(\n+# LINT: \"...)\" arm in one-liner 'case' not misinterpreted as closing \")\"\n+\tcase \"$x\" in 1) true;; esac &&\n+# LINT: same but missing \"&&\"\n+\tcase \"$y\" in 2) false;; esac\n+\tfoobar\n+)\ndiff --git a/t/chainlint/exit-loop.expect b/t/chainlint/exit-loop.expect\nnew file mode 100644\nindex 0000000000..84d8bdebc0\n--- /dev/null\n+++ b/t/chainlint/exit-loop.expect\n@@ -0,0 +1,24 @@\n+(\n+\tfor i in a b c\n+\tdo\n+\t\tfoo || exit 1\n+\t\tbar &&\n+\t\tbaz\n+\tdone\n+>) &&\n+(\n+\twhile true\n+\tdo\n+\t\tfoo || exit 1\n+\t\tbar &&\n+\t\tbaz\n+\tdone\n+>) &&\n+(\n+\ti=0 &&\n+\twhile test $i -lt 10\n+\tdo\n+\t\techo $i || exit\n+\t\ti=$(($i + 1))\n+\tdone\n+>)\ndiff --git a/t/chainlint/exit-loop.test b/t/chainlint/exit-loop.test\nnew file mode 100644\nindex 0000000000..2f038207e1\n--- /dev/null\n+++ b/t/chainlint/exit-loop.test\n@@ -0,0 +1,27 @@\n+(\n+\tfor i in a b c\n+\tdo\n+# LINT: \"|| exit {n}\" valid for-loop escape in subshell; no \"&&\" needed\n+\t\tfoo || exit 1\n+\t\tbar &&\n+\t\tbaz\n+\tdone\n+) &&\n+(\n+\twhile true\n+\tdo\n+# LINT: \"|| exit {n}\" valid while-loop escape in subshell; no \"&&\" needed\n+\t\tfoo || exit 1\n+\t\tbar &&\n+\t\tbaz\n+\tdone\n+) &&\n+(\n+\ti=0 &&\n+\twhile test $i -lt 10\n+\tdo\n+# LINT: \"|| exit\" (sans exit code) valid escape in subshell; no \"&&\" needed\n+\t\techo $i || exit\n+\t\ti=$(($i + 1))\n+\tdone\n+)\ndiff --git a/t/chainlint/for-loop.expect b/t/chainlint/for-loop.expect\nnew file mode 100644\nindex 0000000000..c33cf56ee7\n--- /dev/null\n+++ b/t/chainlint/for-loop.expect\n@@ -0,0 +1,11 @@\n+(\n+\tfor i in a b c\n+\tdo\n+?!AMP?!\t\techo $i\n+\t\tcat\n+?!AMP?!\tdone\n+\tfor i in a b c; do\n+\t\techo $i &&\n+\t\tcat $i\n+\tdone\n+>)\ndiff --git a/t/chainlint/for-loop.test b/t/chainlint/for-loop.test\nnew file mode 100644\nindex 0000000000..7db76262bc\n--- /dev/null\n+++ b/t/chainlint/for-loop.test\n@@ -0,0 +1,19 @@\n+(\n+# LINT: 'for', 'do', 'done' do not need \"&&\"\n+\tfor i in a b c\n+\tdo\n+# LINT: missing \"&&\" on 'echo'\n+\t\techo $i\n+# LINT: last statement of while does not need \"&&\"\n+\t\tcat <<-\\EOF\n+\t\tbar\n+\t\tEOF\n+# LINT: missing \"&&\" on 'done'\n+\tdone\n+\n+# LINT: 'do' on same line as 'for'\n+\tfor i in a b c; do\n+\t\techo $i &&\n+\t\tcat $i\n+\tdone\n+)\ndiff --git a/t/chainlint/if-then-else.expect b/t/chainlint/if-then-else.expect\nnew file mode 100644\nindex 0000000000..5953c7bfbc\n--- /dev/null\n+++ b/t/chainlint/if-then-else.expect\n@@ -0,0 +1,19 @@\n+(\n+\tif test -n \"\"\n+\tthen\n+?!AMP?!\t\techo very\n+\t\techo empty\n+\telif test -z \"\"\n+\t\techo foo\n+\telse\n+\t\techo foo &&\n+\t\tcat\n+?!AMP?!\tfi\n+\techo poodle\n+>) &&\n+(\n+\tif test -n \"\"; then\n+\t\techo very &&\n+?!AMP?!\t\techo empty\n+\tif\n+>)\ndiff --git a/t/chainlint/if-then-else.test b/t/chainlint/if-then-else.test\nnew file mode 100644\nindex 0000000000..9bd8e9a4c6\n--- /dev/null\n+++ b/t/chainlint/if-then-else.test\n@@ -0,0 +1,28 @@\n+(\n+# LINT: 'if', 'then', 'elif', 'else', 'fi' do not need \"&&\"\n+\tif test -n \"\"\n+\tthen\n+# LINT: missing \"&&\" on 'echo'\n+\t\techo very\n+# LINT: last statement before 'elif' does not need \"&&\"\n+\t\techo empty\n+\telif test -z \"\"\n+# LINT: last statement before 'else' does not need \"&&\"\n+\t\techo foo\n+\telse\n+\t\techo foo &&\n+# LINT: last statement before 'fi' does not need \"&&\"\n+\t\tcat <<-\\EOF\n+\t\tbar\n+\t\tEOF\n+# LINT: missing \"&&\" on 'fi'\n+\tfi\n+\techo poodle\n+) &&\n+(\n+# LINT: 'then' on same line as 'if'\n+\tif test -n \"\"; then\n+\t\techo very &&\n+\t\techo empty\n+\tif\n+)\ndiff --git a/t/chainlint/while-loop.expect b/t/chainlint/while-loop.expect\nnew file mode 100644\nindex 0000000000..13cff2c0a5\n--- /dev/null\n+++ b/t/chainlint/while-loop.expect\n@@ -0,0 +1,11 @@\n+(\n+\twhile true\n+\tdo\n+?!AMP?!\t\techo foo\n+\t\tcat\n+?!AMP?!\tdone\n+\twhile true; do\n+\t\techo foo &&\n+\t\tcat bar\n+\tdone\n+>)\ndiff --git a/t/chainlint/while-loop.test b/t/chainlint/while-loop.test\nnew file mode 100644\nindex 0000000000..f1df085bf0\n--- /dev/null\n+++ b/t/chainlint/while-loop.test\n@@ -0,0 +1,19 @@\n+(\n+# LINT: 'while, 'do', 'done' do not need \"&&\"\n+\twhile true\n+\tdo\n+# LINT: missing \"&&\" on 'echo'\n+\t\techo foo\n+# LINT: last statement of while does not need \"&&\"\n+\t\tcat <<-\\EOF\n+\t\tbar\n+\t\tEOF\n+# LINT: missing \"&&\" on 'done'\n+\tdone\n+\n+# LINT: 'do' on same line as 'while'\n+\twhile true; do\n+\t\techo foo &&\n+\t\tcat bar\n+\tdone\n+)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352210","messageId":"20180711064642.6933-9-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 08/10] t/chainlint: add chainlint \"cuddled\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:40Z","receivedAt":"2018-07-11T06:48:14Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/cuddled-if-then-else.expect |  7 +++++++\n t/chainlint/cuddled-if-then-else.test   |  7 +++++++\n t/chainlint/cuddled-loop.expect         |  5 +++++\n t/chainlint/cuddled-loop.test           |  7 +++++++\n t/chainlint/cuddled.expect              | 21 +++++++++++++++++++++\n t/chainlint/cuddled.test                | 23 +++++++++++++++++++++++\n 6 files changed, 70 insertions(+)\n create mode 100644 t/chainlint/cuddled-if-then-else.expect\n create mode 100644 t/chainlint/cuddled-if-then-else.test\n create mode 100644 t/chainlint/cuddled-loop.expect\n create mode 100644 t/chainlint/cuddled-loop.test\n create mode 100644 t/chainlint/cuddled.expect\n create mode 100644 t/chainlint/cuddled.test\n\ndiff --git a/t/chainlint/cuddled-if-then-else.expect b/t/chainlint/cuddled-if-then-else.expect\nnew file mode 100644\nindex 0000000000..ab2a026fbc\n--- /dev/null\n+++ b/t/chainlint/cuddled-if-then-else.expect\n@@ -0,0 +1,7 @@\n+(\n+if test -z \"\"; then\n+    echo empty\n+ else\n+    echo bizzy\n+> fi) &&\n+echo foobar\ndiff --git a/t/chainlint/cuddled-if-then-else.test b/t/chainlint/cuddled-if-then-else.test\nnew file mode 100644\nindex 0000000000..eed774a9d6\n--- /dev/null\n+++ b/t/chainlint/cuddled-if-then-else.test\n@@ -0,0 +1,7 @@\n+# LINT: 'if' cuddled with \"(\" and \")\"; indented with spaces, not tabs\n+(if test -z \"\"; then\n+    echo empty\n+ else\n+    echo bizzy\n+ fi) &&\n+echo foobar\ndiff --git a/t/chainlint/cuddled-loop.expect b/t/chainlint/cuddled-loop.expect\nnew file mode 100644\nindex 0000000000..8c0260d7f1\n--- /dev/null\n+++ b/t/chainlint/cuddled-loop.expect\n@@ -0,0 +1,5 @@\n+(\n+ while read x\n+  do foobar bop || exit 1\n+>  done <file ) &&\n+outside subshell\ndiff --git a/t/chainlint/cuddled-loop.test b/t/chainlint/cuddled-loop.test\nnew file mode 100644\nindex 0000000000..a841d781f0\n--- /dev/null\n+++ b/t/chainlint/cuddled-loop.test\n@@ -0,0 +1,7 @@\n+# LINT: 'while' loop cuddled with \"(\" and \")\", with embedded (allowed)\n+# LINT: \"|| exit {n}\" to exit loop early, and using redirection \"<\" to feed\n+# LINT: loop; indented with spaces, not tabs\n+( while read x\n+  do foobar bop || exit 1\n+  done <file ) &&\n+outside subshell\ndiff --git a/t/chainlint/cuddled.expect b/t/chainlint/cuddled.expect\nnew file mode 100644\nindex 0000000000..b506d46221\n--- /dev/null\n+++ b/t/chainlint/cuddled.expect\n@@ -0,0 +1,21 @@\n+(\n+cd foo &&\n+\tbar\n+>) &&\n+\n+(\n+?!AMP?!cd foo\n+\tbar\n+>) &&\n+\n+(\n+\tcd foo &&\n+>\tbar) &&\n+\n+(\n+cd foo &&\n+>\tbar) &&\n+\n+(\n+?!AMP?!cd foo\n+>\tbar)\ndiff --git a/t/chainlint/cuddled.test b/t/chainlint/cuddled.test\nnew file mode 100644\nindex 0000000000..0499fa4180\n--- /dev/null\n+++ b/t/chainlint/cuddled.test\n@@ -0,0 +1,23 @@\n+# LINT: first subshell statement cuddled with opening \"(\"; for implementation\n+# LINT: simplicity, \"(...\" is split into two lines, \"(\" and \"...\"\n+(cd foo &&\n+\tbar\n+) &&\n+\n+# LINT: same with missing \"&&\"\n+(cd foo\n+\tbar\n+) &&\n+\n+# LINT: closing \")\" cuddled with final subshell statement\n+(\n+\tcd foo &&\n+\tbar) &&\n+\n+# LINT: \"(\" and \")\" cuddled with first and final subshell statements\n+(cd foo &&\n+\tbar) &&\n+\n+# LINT: same with missing \"&&\"\n+(cd foo\n+\tbar)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352211","messageId":"20180711064642.6933-11-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 10/10] t/chainlint: add chainlint \"specialized\" test cases","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:42Z","receivedAt":"2018-07-11T06:48:16Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection. The\nheuristics handle a range of stylistic variations in existing tests\n(evolved over the years), however, they are still best-guesses. As such,\nit is possible for future changes to accidentally break assumptions upon\nwhich the heuristics are based. Protect against this possibility by\nadding tests which check the linter itself for correctness.\n\nIn addition to protecting against regressions, these tests help document\n(for humans) expected behavior, which is important since the linter's\nimplementation language ('sed') does not necessarily lend itself to easy\ncomprehension.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/bash-array.expect  | 10 ++++++++++\n t/chainlint/bash-array.test    | 12 ++++++++++++\n t/chainlint/p4-filespec.expect |  4 ++++\n t/chainlint/p4-filespec.test   |  5 +++++\n 4 files changed, 31 insertions(+)\n create mode 100644 t/chainlint/bash-array.expect\n create mode 100644 t/chainlint/bash-array.test\n create mode 100644 t/chainlint/p4-filespec.expect\n create mode 100644 t/chainlint/p4-filespec.test\n\ndiff --git a/t/chainlint/bash-array.expect b/t/chainlint/bash-array.expect\nnew file mode 100644\nindex 0000000000..c4a830d1c1\n--- /dev/null\n+++ b/t/chainlint/bash-array.expect\n@@ -0,0 +1,10 @@\n+(\n+\tfoo &&\n+\tbar=(gumbo stumbo wumbo) &&\n+\tbaz\n+>) &&\n+(\n+\tfoo &&\n+\tbar=${#bar[@]} &&\n+\tbaz\n+>)\ndiff --git a/t/chainlint/bash-array.test b/t/chainlint/bash-array.test\nnew file mode 100644\nindex 0000000000..92bbb777b8\n--- /dev/null\n+++ b/t/chainlint/bash-array.test\n@@ -0,0 +1,12 @@\n+(\n+\tfoo &&\n+# LINT: \")\" in Bash array assignment not misinterpreted as subshell-closing \")\"\n+\tbar=(gumbo stumbo wumbo) &&\n+\tbaz\n+) &&\n+(\n+\tfoo &&\n+# LINT: Bash array length operator not misinterpreted as comment\n+\tbar=${#bar[@]} &&\n+\tbaz\n+)\ndiff --git a/t/chainlint/p4-filespec.expect b/t/chainlint/p4-filespec.expect\nnew file mode 100644\nindex 0000000000..98b3d881fd\n--- /dev/null\n+++ b/t/chainlint/p4-filespec.expect\n@@ -0,0 +1,4 @@\n+(\n+\tp4 print -1 //depot/fiddle#42 >file &&\n+\tfoobar\n+>)\ndiff --git a/t/chainlint/p4-filespec.test b/t/chainlint/p4-filespec.test\nnew file mode 100644\nindex 0000000000..4fd2d6e2b8\n--- /dev/null\n+++ b/t/chainlint/p4-filespec.test\n@@ -0,0 +1,5 @@\n+(\n+# LINT: Perforce revspec in filespec not misinterpreted as in-line comment\n+\tp4 print -1 //depot/fiddle#42 >file &&\n+\tfoobar\n+)\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352212","messageId":"20180711064642.6933-3-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH v2 02/10] t/Makefile: add machinery to check correctness of chainlint.sed","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-11T06:46:34Z","receivedAt":"2018-07-11T06:48:18Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The --chain-lint option uses heuristics and knowledge of shell syntax to\ndetect broken &&-chains in subshells by pure textual inspection.\nAlthough the heuristics work well, they are still best-guesses and\nfuture changes could accidentally break assumptions upon which they are\nbased. To protect against this possibility, tests checking correctness\nof the linter itself will be added. As preparation, add a new makefile\n\"check-chainlint\" target and associated machinery.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/.gitignore |  1 +\n t/Makefile   | 25 +++++++++++++++++++++----\n 2 files changed, 22 insertions(+), 4 deletions(-)\n\ndiff --git a/t/.gitignore b/t/.gitignore\nindex 4e731dc1e3..348715f0e4 100644\n--- a/t/.gitignore\n+++ b/t/.gitignore\n@@ -1,3 +1,4 @@\n /trash directory*\n /test-results\n /.prove\n+/chainlinttmp\ndiff --git a/t/Makefile b/t/Makefile\nindex 96317a35f4..c83fd18861 100644\n--- a/t/Makefile\n+++ b/t/Makefile\n@@ -18,8 +18,10 @@ TEST_LINT ?= test-lint\n \n ifdef TEST_OUTPUT_DIRECTORY\n TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results\n+CHAINLINTTMP = $(TEST_OUTPUT_DIRECTORY)/chainlinttmp\n else\n TEST_RESULTS_DIRECTORY = test-results\n+CHAINLINTTMP = chainlinttmp\n endif\n \n # Shell quote;\n@@ -27,14 +29,17 @@ SHELL_PATH_SQ = $(subst ','\\'',$(SHELL_PATH))\n TEST_SHELL_PATH_SQ = $(subst ','\\'',$(TEST_SHELL_PATH))\n PERL_PATH_SQ = $(subst ','\\'',$(PERL_PATH))\n TEST_RESULTS_DIRECTORY_SQ = $(subst ','\\'',$(TEST_RESULTS_DIRECTORY))\n+CHAINLINTTMP_SQ = $(subst ','\\'',$(CHAINLINTTMP))\n \n T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))\n TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh))\n THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh)))\n+CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.test)))\n+CHAINLINT = sed -f chainlint.sed\n \n all: $(DEFAULT_TEST_TARGET)\n \n-test: pre-clean $(TEST_LINT)\n+test: pre-clean check-chainlint $(TEST_LINT)\n \t$(MAKE) aggregate-results-and-cleanup\n \n failed:\n@@ -43,7 +48,7 @@ failed:\n \t\tsed -n 's/\\.counts$$/.sh/p') && \\\n \ttest -z \"$$failed\" || $(MAKE) $$failed\n \n-prove: pre-clean $(TEST_LINT)\n+prove: pre-clean check-chainlint $(TEST_LINT)\n \t@echo \"*** prove ***\"; $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)\n \t$(MAKE) clean-except-prove-cache\n \n@@ -53,13 +58,25 @@ $(T):\n pre-clean:\n \t$(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'\n \n-clean-except-prove-cache:\n+clean-except-prove-cache: clean-chainlint\n \t$(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'\n \t$(RM) -r valgrind/bin\n \n clean: clean-except-prove-cache\n \t$(RM) .prove\n \n+clean-chainlint:\n+\t$(RM) -r '$(CHAINLINTTMP_SQ)'\n+\n+check-chainlint:\n+\t@mkdir -p '$(CHAINLINTTMP_SQ)' && \\\n+\terr=0 && \\\n+\tfor i in $(CHAINLINTTESTS); do \\\n+\t\t$(CHAINLINT) <chainlint/$$i.test | \\\n+\t\tsed -e '/^# LINT: /d' >'$(CHAINLINTTMP_SQ)'/$$i.actual && \\\n+\t\tdiff -u chainlint/$$i.expect '$(CHAINLINTTMP_SQ)'/$$i.actual || err=1; \\\n+\tdone && exit $$err\n+\n test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \\\n \ttest-lint-filenames\n \n@@ -102,4 +119,4 @@ valgrind:\n perf:\n \t$(MAKE) -C perf/ all\n \n-.PHONY: pre-clean $(T) aggregate-results clean valgrind perf\n+.PHONY: pre-clean $(T) aggregate-results clean valgrind perf check-chainlint clean-chainlint\n-- \n2.18.0.203.gfac676dfb9\n\n"},{"id":"352304","messageId":"xmqqk1q11mkj.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180711064642.6933-2-sunshine@sunshineco.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-11T21:37:00Z","receivedAt":"2018-07-11T21:37:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> The --chain-lint option detects broken &&-chains by forcing the test to\n> exit early (as the very first step) with a sentinel value. If that\n> sentinel is the test's overall exit code, then the &&-chain is intact;\n> if not, then the chain is broken. Unfortunately, this detection does not\n> extend to &&-chains within subshells even when the subshell itself is\n> properly linked into the outer &&-chain.\n>\n> Address this shortcoming by feeding the body of the test to a\n> lightweight \"linter\" which can peer inside subshells and identify broken\n> &&-chains by pure textual inspection. Although the linter does not\n> ...\n> Heuristics are employed to properly identify the extent of a subshell\n> formatted in the old-style since a number of legitimate constructs may\n> superficially appear to close the subshell even though they don't. For\n> example, it understands that neither \"x=$(command)\" nor \"case $x in *)\"\n> end a subshell, despite the \")\" at the end of line.\n>\n> Due to limitations of the tool used ('sed') and its inherent\n> line-by-line processing, only subshells one level deep are handled, as\n> well as one-liner subshells one level below that. Subshells deeper than\n> that or multi-line subshells at level two are passed through as-is, thus\n> &&-chains in their bodies are not checked.\n>\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n\nAs with the previous \"transform the script and feed the result to\nshell\" approach, this risks to force us into writing our tests in a\nsubset of valid shell language, which is the primary reason why I\nwas not enthused when I saw the previous round.  The worst part of\nit is that the subset is not strictly defined based on the shell\nlanguage syntax or features (e.g. we allow this and that feature but\nnot that other feature) but \"whatever that does not cause the linter\nscript to trigger false positives\".\n\nSo I dunno.  I haven't spent enough time to carefully look at the\nactual scripts to access how serious the \"problem\" I perceive\nactually is with this series to form a firm opinion yet.  Let me\ncome back to the topic after doing so.\n"},{"id":"352323","messageId":"CAPig+cS+fFZNZMxc0ARu=cZ2RVjHSP0iiLTsAJ_L7iOarwftgg@mail.gmail.com","threadId":"48778","inReplyTo":"xmqqk1q11mkj.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-12T10:50:20Z","receivedAt":"2018-07-12T10:50:34Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Jul 11, 2018 at 5:37 PM Junio C Hamano <gitster@pobox.com> wrote:\n> As with the previous \"transform the script and feed the result to\n> shell\" approach, this risks to force us into writing our tests in a\n> subset of valid shell language, which is the primary reason why I\n> was not enthused when I saw the previous round.  The worst part of\n> it is that the subset is not strictly defined based on the shell\n> language syntax or features (e.g. we allow this and that feature but\n> not that other feature) but \"whatever that does not cause the linter\n> script to trigger false positives\".\n\nSome observations perhaps worth considering:\n\nThe linter is happy (no false positives) with the 13000+ existing\ntests (though, of course, not all of them use subshells). Those tests,\nwritten over many years, vary quite wildly in style and implementation\napproach, so the \"subset\" of shell language accepted by the linter is\nquite broad.\n\nThe original --chain-lint series (jk/test-chain-lint) had to make some\nchanges, such as wrapping code in a {...} block[1], merely to pacify\nthe linter. v2 of the subshell linter required no such changes.\n\nThe subshell linter was crafted to be on par with the existing\n--chain-lint in terms of strictness (and looseness), so the subshell\nlinter is not more strict than the existing implementation. (For\ninstance, one can escape the strict &&-chain requirement in the\nexisting --chain-lint by wrapping code in a {...} block. The subshell\nlinter intentionally allows that escape, as well.)\n\nAnd, perhaps most important: We're not tied indefinitely to the\n\"subset\" implemented by the current linter. If it is indeed found to\nbe too strict or limiting, it can always be loosened or retired\naltogether.\n\nThanks for the feedback.\n\n[1]: bfe998fc9b (t0050: appease --chain-lint, 2015-03-20)\n"},{"id":"352366","messageId":"20180712165608.GA10515@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CAPig+cS+fFZNZMxc0ARu=cZ2RVjHSP0iiLTsAJ_L7iOarwftgg@mail.gmail.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-12T16:56:08Z","receivedAt":"2018-07-12T16:56:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Jul 12, 2018 at 06:50:20AM -0400, Eric Sunshine wrote:\n\n> And, perhaps most important: We're not tied indefinitely to the\n> \"subset\" implemented by the current linter. If it is indeed found to\n> be too strict or limiting, it can always be loosened or retired\n> altogether.\n\nYeah, I agree this is the key point.\n\nLike Junio, I'm a little nervous that this is going to end up being a\nmaintenance burden. People may hit false positives and then be\nconfronted with this horrible mass of sed to try to figure out what went\nwrong (which isn't to bust on your sed in particular; I think you made a\nheroic effort in commenting).\n\nBut I came around to thinking:\n\n  - this found and fixed real problems in the test suite, with minimal\n    false positives across the existing code\n\n  - it's being done by a long-time contributor, not somebody who is\n    going to dump sed on us and leave\n\n  - worst case is that relief is only a \"git revert\" away\n\nSo I'm OK with merging it, and even running it by default.\n\n-Peff\n"},{"id":"352391","messageId":"CAPig+cRmAkiYqFXwRAkQALDoOo-79r2iAumdEJEZhBnETvL-fw@mail.gmail.com","threadId":"48778","inReplyTo":"20180712165608.GA10515@sigill.intra.peff.net","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-12T19:32:50Z","receivedAt":"2018-07-12T19:33:03Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Jul 12, 2018 at 12:56 PM Jeff King <peff@peff.net> wrote:\n> Like Junio, I'm a little nervous that this is going to end up being a\n> maintenance burden. People may hit false positives and then be\n> confronted with this horrible mass of sed to try to figure out what went\n> wrong [...]\n\nA very valid concern.\n\n> But I came around to thinking:\n>   - this found and fixed real problems in the test suite, with minimal\n>     false positives across the existing code\n\nThe counterargument (and arguing against my own case) is that, while\nit found 3 or 4 genuine test bugs hidden by &&-breakage, they were\njust that: bugs in the tests; they weren't hiding any bugs in Git\nitself, which is pretty measly return for the effort invested in the\nlinter.\n\nHowever, existing tests aside, the more important goal is detecting\nproblems in new or updated tests hiding genuine bugs in changes to Git\nitself, so it may have some value.\n\n>   - worst case is that relief is only a \"git revert\" away\n\nRight. It's just a developer aid, not a user-facing feature which has\nto be maintained in perpetuity, so retiring it is easy.\n"},{"id":"352424","messageId":"xmqqin5kw7q3.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"CAPig+cRmAkiYqFXwRAkQALDoOo-79r2iAumdEJEZhBnETvL-fw@mail.gmail.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-07-12T19:54:12Z","receivedAt":"2018-07-12T19:54:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> However, existing tests aside, the more important goal is detecting\n> problems in new or updated tests hiding genuine bugs in changes to Git\n> itself, so it may have some value.\n\nYes, indeed.\n"},{"id":"353926","messageId":"20180730181356.GA156463@aiede.svl.corp.google.com","threadId":"48778","inReplyTo":"20180711064642.6933-2-sunshine@sunshineco.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-07-30T18:13:56Z","receivedAt":"2018-07-30T18:14:06Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nEric Sunshine wrote:\n\n> The --chain-lint option detects broken &&-chains by forcing the test to\n> exit early (as the very first step) with a sentinel value. If that\n> sentinel is the test's overall exit code, then the &&-chain is intact;\n> if not, then the chain is broken. Unfortunately, this detection does not\n> extend to &&-chains within subshells even when the subshell itself is\n> properly linked into the outer &&-chain.\n>\n> Address this shortcoming by feeding the body of the test to a\n> lightweight \"linter\" which can peer inside subshells and identify broken\n> &&-chains by pure textual inspection.\n\nInteresting.\n\n>                                        Although the linter does not\n> actually parse shell scripts, it has enough knowledge of shell syntax to\n> reliably deal with formatting style variations (as evolved over the\n> years) and to avoid being fooled by non-shell content (such as inside\n> here-docs and multi-line strings).\n\nThis is causing contrib/subtree tests to fail for me: running \"make -C\ncontrib/subtree test\" produces\n\n[...]\n\t*** t7900-subtree.sh ***\n\tok 1 - no merge from non-existent subtree\n\tok 2 - no pull from non-existent subtree\n\tok 3 - add subproj as subtree into sub dir/ with --prefix\n\tok 4 - add subproj as subtree into sub dir/ with --prefix and --message\n\tok 5 - add subproj as subtree into sub dir/ with --prefix as -P and --message as -m\n\tok 6 - add subproj as subtree into sub dir/ with --squash and --prefix and --message\n\tok 7 - merge new subproj history into sub dir/ with --prefix\n\tok 8 - merge new subproj history into sub dir/ with --prefix and --message\n\tok 9 - merge new subproj history into sub dir/ with --squash and --prefix and --message\n\tok 10 - merge the added subproj again, should do nothing\n\tok 11 - merge new subproj history into subdir/ with a slash appended to the argument of --prefix\n\tok 12 - split requires option --prefix\n\tok 13 - split requires path given by option --prefix must exist\n\tok 14 - split sub dir/ with --rejoin\n\tok 15 - split sub dir/ with --rejoin from scratch\n\tok 16 - split sub dir/ with --rejoin and --message\n\tok 17 - split \"sub dir\"/ with --branch\n\tok 18 - check hash of split\n\tok 19 - split \"sub dir\"/ with --branch for an existing branch\n\tok 20 - split \"sub dir\"/ with --branch for an incompatible branch\n\terror: bug in the test script: broken &&-chain or run-away HERE-DOC: \n\t\tsubtree_test_create_repo \"$subtree_test_count\" &&\n[...]\n\t\t)\n\n\tMakefile:44: recipe for target 't7900-subtree.sh' failed\n\nThe problematic test code looks like this:\n\n\t(\n\t\tcd \"$subtree_test_count/sub proj\" &&\n\t\tgit fetch .. subproj-br &&\n\t\tgit merge FETCH_HEAD &&\n\n\t\tchks=\"sub1\nsub2\nsub3\nsub4\" &&\n\t\tchks_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n$chks\nTXT\n) &&\n\t\tchkms=\"main-sub1\nmain-sub2\nmain-sub3\nmain-sub4\" &&\n\t\tchkms_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n$chkms\nTXT\n) &&\n\n\t\tsubfiles=$(git ls-files) &&\n\t\tcheck_equal \"$subfiles\" \"$chkms\n$chks\"\n\t)\n\nUgly quoting, useless use of \"cat\", etc, aside, I don't think it's\nmissing any &&.  Hints?\n\nThanks,\nJonathan\n"},{"id":"353934","messageId":"20180730190612.GB156463@aiede.svl.corp.google.com","threadId":"48778","inReplyTo":"20180730181356.GA156463@aiede.svl.corp.google.com","subject":"[PATCH 0/2] subtree: fix &&-chain and simplify tests (Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells)","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-07-30T19:06:12Z","receivedAt":"2018-07-30T19:06:16Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"(resetting cc list)\nJonathan Nieder wrote:\n\n> This is causing contrib/subtree tests to fail for me: running \"make -C\n> contrib/subtree test\" produces\n[...]\n> \terror: bug in the test script: broken &&-chain or run-away HERE-DOC:\n[...]\n> Ugly quoting, useless use of \"cat\", etc, aside, I don't think it's\n> missing any &&.  Hints?\n\nTurns out it was missing a && too. :)\n\nThese patches are against \"master\".  Ideally this would have come\nbefore es/chain-lint-in-subshell.  Since this is contrib/, I'm okay\nwith losing bisectability and having it come after, though.\n\nThoughts of all kinds welcome.\n\nJonathan Nieder (2):\n  subtree: add missing && to &&-chain\n  subtree: simplify preparation of expected results\n\n contrib/subtree/t/t7900-subtree.sh | 121 ++++++++---------------------\n 1 file changed, 31 insertions(+), 90 deletions(-)\n"},{"id":"353935","messageId":"20180730190702.GC156463@aiede.svl.corp.google.com","threadId":"48778","inReplyTo":"20180730190612.GB156463@aiede.svl.corp.google.com","subject":"[PATCH 1/2] subtree test: add missing && to &&-chain","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-07-30T19:07:02Z","receivedAt":"2018-07-30T19:07:05Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Detected using t/chainlint.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\n contrib/subtree/t/t7900-subtree.sh | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh\nindex d05c613c97..e6a28f2c3e 100755\n--- a/contrib/subtree/t/t7900-subtree.sh\n+++ b/contrib/subtree/t/t7900-subtree.sh\n@@ -708,7 +708,7 @@ test_expect_success 'make sure each filename changed exactly once in the entire\n \ttest_create_commit \"$subtree_test_count/sub proj\" sub1 &&\n \t(\n \t\tcd \"$subtree_test_count\" &&\n-\t\tgit config log.date relative\n+\t\tgit config log.date relative &&\n \t\tgit fetch ./\"sub proj\" master &&\n \t\tgit subtree add --prefix=\"sub dir\" FETCH_HEAD\n \t) &&\n-- \n2.18.0.345.g5c9ce644c3-goog\n\n"},{"id":"353936","messageId":"20180730190738.GD156463@aiede.svl.corp.google.com","threadId":"48778","inReplyTo":"20180730190612.GB156463@aiede.svl.corp.google.com","subject":"[PATCH 2/2] subtree test: simplify preparation of expected results","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-07-30T19:07:38Z","receivedAt":"2018-07-30T19:07:43Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"This mixture of quoting, pipes, and here-docs to produce expected\nresults in shell variables is difficult to follow.  Simplify by using\nsimpler constructs that write output to files instead.\n\nNoticed because without this patch, t/chainlint is not able to\nunderstand the script in order to validate that its subshells use an\nunbroken &&-chain, causing \"make -C contrib/subtree test\" to fail with\n\n\terror: bug in the test script: broken &&-chain or run-away HERE-DOC:\n\nin t7900.21.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nThat's the end of the series.  Thanks for reading.\n\nThanks,\nJonathan\n\n contrib/subtree/t/t7900-subtree.sh | 119 ++++++++---------------------\n 1 file changed, 30 insertions(+), 89 deletions(-)\n\ndiff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh\nindex e6a28f2c3e..57ff4b25c1 100755\n--- a/contrib/subtree/t/t7900-subtree.sh\n+++ b/contrib/subtree/t/t7900-subtree.sh\n@@ -540,26 +540,10 @@ test_expect_success 'make sure exactly the right set of files ends up in the sub\n \t\tgit fetch .. subproj-br &&\n \t\tgit merge FETCH_HEAD &&\n \n-\t\tchks=\"sub1\n-sub2\n-sub3\n-sub4\" &&\n-\t\tchks_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chks\n-TXT\n-) &&\n-\t\tchkms=\"main-sub1\n-main-sub2\n-main-sub3\n-main-sub4\" &&\n-\t\tchkms_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chkms\n-TXT\n-) &&\n-\n-\t\tsubfiles=$(git ls-files) &&\n-\t\tcheck_equal \"$subfiles\" \"$chkms\n-$chks\"\n+\t\ttest_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \\\n+\t\t\tsub1 sub2 sub3 sub4 >expect &&\n+\t\tgit ls-files >actual &&\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -606,25 +590,11 @@ test_expect_success 'make sure the subproj *only* contains commits that affect t\n \t\tgit fetch .. subproj-br &&\n \t\tgit merge FETCH_HEAD &&\n \n-\t\tchks=\"sub1\n-sub2\n-sub3\n-sub4\" &&\n-\t\tchks_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chks\n-TXT\n-) &&\n-\t\tchkms=\"main-sub1\n-main-sub2\n-main-sub3\n-main-sub4\" &&\n-\t\tchkms_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chkms\n-TXT\n-) &&\n-\t\tallchanges=$(git log --name-only --pretty=format:\"\" | sort | sed \"/^$/d\") &&\n-\t\tcheck_equal \"$allchanges\" \"$chkms\n-$chks\"\n+\t\ttest_write_lines main-sub1 main-sub2 main-sub3 main-sub4 \\\n+\t\t\tsub1 sub2 sub3 sub4 >expect &&\n+\t\tgit log --name-only --pretty=format:\"\" >log &&\n+\t\tsort <log | sed \"/^\\$/ d\" >actual &&\n+\t\ttest_cmp expect actual\n \t)\n '\n \n@@ -675,29 +645,16 @@ test_expect_success 'make sure exactly the right set of files ends up in the mai\n \t\tcd \"$subtree_test_count\" &&\n \t\tgit subtree pull --prefix=\"sub dir\" ./\"sub proj\" master &&\n \n-\t\tchkm=\"main1\n-main2\" &&\n-\t\tchks=\"sub1\n-sub2\n-sub3\n-sub4\" &&\n-\t\tchks_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chks\n-TXT\n-) &&\n-\t\tchkms=\"main-sub1\n-main-sub2\n-main-sub3\n-main-sub4\" &&\n-\t\tchkms_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chkms\n-TXT\n-) &&\n-\t\tmainfiles=$(git ls-files) &&\n-\t\tcheck_equal \"$mainfiles\" \"$chkm\n-$chkms_sub\n-$chks_sub\"\n-)\n+\t\ttest_write_lines main1 main2 >chkm &&\n+\t\ttest_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms &&\n+\t\tsed \"s,^,sub dir/,\" chkms >chkms_sub &&\n+\t\ttest_write_lines sub1 sub2 sub3 sub4 >chks &&\n+\t\tsed \"s,^,sub dir/,\" chks >chks_sub &&\n+\n+\t\tcat chkm chkms_sub chks_sub >expect &&\n+\t\tgit ls-files >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n '\n \n next_test\n@@ -748,37 +705,21 @@ test_expect_success 'make sure each filename changed exactly once in the entire\n \t\tcd \"$subtree_test_count\" &&\n \t\tgit subtree pull --prefix=\"sub dir\" ./\"sub proj\" master &&\n \n-\t\tchkm=\"main1\n-main2\" &&\n-\t\tchks=\"sub1\n-sub2\n-sub3\n-sub4\" &&\n-\t\tchks_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chks\n-TXT\n-) &&\n-\t\tchkms=\"main-sub1\n-main-sub2\n-main-sub3\n-main-sub4\" &&\n-\t\tchkms_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n-$chkms\n-TXT\n-) &&\n+\t\ttest_write_lines main1 main2 >chkm &&\n+\t\ttest_write_lines sub1 sub2 sub3 sub4 >chks &&\n+\t\ttest_write_lines main-sub1 main-sub2 main-sub3 main-sub4 >chkms &&\n+\t\tsed \"s,^,sub dir/,\" chkms >chkms_sub &&\n \n \t\t# main-sub?? and /\"sub dir\"/main-sub?? both change, because those are the\n \t\t# changes that were split into their own history.  And \"sub dir\"/sub?? never\n \t\t# change, since they were *only* changed in the subtree branch.\n-\t\tallchanges=$(git log --name-only --pretty=format:\"\" | sort | sed \"/^$/d\") &&\n-\t\texpected=''\"$(cat <<TXT | sort\n-$chkms\n-$chkm\n-$chks\n-$chkms_sub\n-TXT\n-)\"'' &&\n-\t\tcheck_equal \"$allchanges\" \"$expected\"\n+\t\tgit log --name-only --pretty=format:\"\" >log &&\n+\t\tsort <log >sorted-log &&\n+\t\tsed \"/^$/ d\" sorted-log >actual &&\n+\n+\t\tcat chkms chkm chks chkms_sub >expect-unsorted &&\n+\t\tsort expect-unsorted >expect &&\n+\t\ttest_cmp expect actual\n \t)\n '\n \n-- \n2.18.0.345.g5c9ce644c3\n\n"},{"id":"353951","messageId":"CAPig+cRFMKBQVVYjhS6-Yyy-aQCYXGiqG6XoqucJoedCvAzheQ@mail.gmail.com","threadId":"48778","inReplyTo":"20180730181356.GA156463@aiede.svl.corp.google.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-30T20:25:34Z","receivedAt":"2018-07-30T20:25:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jul 30, 2018 at 2:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Eric Sunshine wrote:\n> > Address this shortcoming by feeding the body of the test to a\n> > lightweight \"linter\" which can peer inside subshells and identify broken\n> > &&-chains by pure textual inspection.\n>\n> This is causing contrib/subtree tests to fail for me: running \"make -C\n> contrib/subtree test\" produces\n\nThanks, I forgot that some of 'contrib' had bundled tests. (In fact, I\njust checked the other 'contrib' tests and found that a MediaWiki test\nhas a broken top-level &&-chain.)\n\n> The problematic test code looks like this:\n>\n>         (\n>                 chks_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n> $chks\n> TXT\n> ) &&\n>\n> Ugly quoting, useless use of \"cat\", etc, aside, I don't think it's\n> missing any &&.  Hints?\n\nYes, it's a false positive.\n\nThe subshell linter would normally fold out the here-doc content, but\n'sed' isn't a proper programming language, so the linter can't\nrecognize arbitrary here-doc tags. Instead it has hard-coded knowledge\nof the tags commonly used in the Git tests, specifically EOF, EOT, and\nINPUT_END.\n\nThe linter also deals with multi-line $(...) expressions, however, it\ncurrently only recognizes them when the $( is on its own line.\n\nHad this test used one of the common here-doc tags _or_ had it\nformatted the $(...) as described, then it wouldn't have misfired.\n\nI could try to update the linter to not trip over this sort of input,\nhowever, this test code is indeed ugly and difficult to understand,\nand your rewrite[1] of it makes it far easier to grok, so I'm not sure\nthe effort would be worthwhile. What do you think?\n\n[1]: https://public-inbox.org/git/20180730190738.GD156463@aiede.svl.corp.google.com/\n"},{"id":"353958","messageId":"20180730205914.GE156463@aiede.svl.corp.google.com","threadId":"48778","inReplyTo":"CAPig+cRFMKBQVVYjhS6-Yyy-aQCYXGiqG6XoqucJoedCvAzheQ@mail.gmail.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2018-07-30T20:59:14Z","receivedAt":"2018-07-30T20:59:18Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Eric Sunshine wrote:\n> On Mon, Jul 30, 2018 at 2:14 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n\n>>         (\n>>                 chks_sub=$(cat <<TXT | sed '\\''s,^,sub dir/,'\\''\n>> $chks\n>> TXT\n>> ) &&\n>>\n>> Ugly quoting, useless use of \"cat\", etc, aside, I don't think it's\n>> missing any &&.  Hints?\n>\n> Yes, it's a false positive.\n>\n> The subshell linter would normally fold out the here-doc content, but\n> 'sed' isn't a proper programming language, so the linter can't\n> recognize arbitrary here-doc tags. Instead it has hard-coded knowledge\n> of the tags commonly used in the Git tests, specifically EOF, EOT, and\n> INPUT_END.\n\nOh, hmm.  I also see some others (outside subshells, though):\n\n\tEXPECT_END\n\tFRONTEND_END\n\tEND_PART1\n\tSETUP_END\n\tEOF2\n\tEXPECTED\n\tEND_OF_LOG\n\tINPUT_END\n\tEND_EXPECT\n\nI wonder if it should look for something like [A-Z][A-Z_]* to catch\nall of these.\n\n> The linter also deals with multi-line $(...) expressions, however, it\n> currently only recognizes them when the $( is on its own line.\n\nThat's reasonable, especially if \"on its own line\" means \"at end of\nline\".\n\nWhat would help most is if the error message could explain what is\ngoing on, but I understand that that can be hard to do in a sed\nscript.\n\n[...]\n> I could try to update the linter to not trip over this sort of input,\n> however, this test code is indeed ugly and difficult to understand,\n> and your rewrite[1] of it makes it far easier to grok, so I'm not sure\n> the effort would be worthwhile. What do you think?\n\nI'd be happy to look over a change that handles more here-doc\ndelimiters or produces a clearer message for tests in poor style, but\nI agree with you that it's not too important.\n\nThanks for looking it over.\n\nSincerely,\nJonathan\n\n> [1]: https://public-inbox.org/git/20180730190738.GD156463@aiede.svl.corp.google.com/\n"},{"id":"353964","messageId":"CAPig+cRTgh6DStUdmXqvhbL_7sQY6wu21h27rjq_i=kZ_d+LAw@mail.gmail.com","threadId":"48778","inReplyTo":"20180730205914.GE156463@aiede.svl.corp.google.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-30T21:38:06Z","receivedAt":"2018-07-30T21:38:21Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Jul 30, 2018 at 4:59 PM Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Eric Sunshine wrote:\n> > The subshell linter would normally fold out the here-doc content, but\n> > 'sed' isn't a proper programming language, so the linter can't\n> > recognize arbitrary here-doc tags. Instead it has hard-coded knowledge\n> > of the tags commonly used in the Git tests, specifically EOF, EOT, and\n> > INPUT_END.\n>\n> Oh, hmm.  I also see some others (outside subshells, though):\n>\n>         EXPECT_END\n>         [...]\n\nCorrect. The linter does fold-out top-level EOF here-docs to hedge\nagainst the body of a here-doc containing something that might look\nlike the start of a subshell (which would activate the more\nstrict/expensive &&-chain validation). It special-cases top-level EOF\nbecause it's such a common tag name, thus an easy way to avoid\nfalse-positives, in general. I didn't bother trying to recognize\n_every_ possible tag since those other here-docs don't trigger any\nproblems. (It's heuristic-based, after all.)\n\n> I wonder if it should look for something like [A-Z][A-Z_]* to catch\n> all of these.\n\nI considered that, but it doesn't handle nested here-docs, which we\nactually have in the test suite. For instance, from t9300-fast-import:\n\n    cat >input <<-INPUT_END &&\n    mark :2\n    data <<EOF\n    $file2_data\n    EOF\n    ...\n    INPUT_END\n\nNesting could be handled easily enough either by stashing away the\nopening tag and matching against it later _or_ by doing recursive\nhere-doc folding, however, 'sed' isn't a proper programming language\nand can't be coerced into doing either of those. (And, it was tricky\nenough just getting it to handle the nested case with a limited set of\nrecognized tag names, without having to explicitly handle every\ncombination of those names nested inside one another.)\n\n> > The linter also deals with multi-line $(...) expressions, however, it\n> > currently only recognizes them when the $( is on its own line.\n>\n> That's reasonable, especially if \"on its own line\" means \"at end of\n> line\".\n\nIt does; sorry for not being clear.\n\n> What would help most is if the error message could explain what is\n> going on, but I understand that that can be hard to do in a sed\n> script.\n\nRight, unfortunately, it can't be too helpful, but, when fixing all\nthose broken chains, I did find it useful to dump the result after\n'sed' processing in order to identify what it was actually complaining\nabout. I did so by changing the $1 in this line from test-lib.sh:\n\nerror \"bug in the test script: broken &&-chain or run-away HERE-DOC: $1\"\n\nto print the 'sed' output instead. The linter prepends \"??AMP??\" and\n\"??SEMI??\" to suspect lines. It also prepends lines with \">\" to\nindicate where it thinks a subshell ends. This information was quite\nhelpful when figuring out what was broken in the test (or, for false\npositives, where a heuristic had gone wrong).\n\nI considered enabling this output by default instead of $1 but decided\nagainst it since it is only helpful for broken &&-chains in subshells,\nthus doesn't aid in the more common case of top-level &&-breakage,\nthus might be confusing.\n\n> > I could try to update the linter to not trip over this sort of input,\n> > however, this test code is indeed ugly and difficult to understand,\n> > and your rewrite[1] of it makes it far easier to grok, so I'm not sure\n> > the effort would be worthwhile. What do you think?\n>\n> I'd be happy to look over a change that handles more here-doc\n> delimiters or produces a clearer message for tests in poor style, but\n> I agree with you that it's not too important.\n\nI am, for a couple reasons, somewhat hesitant to tweak the heuristic.\n\nFirst, each tweak has the potential of causing more false-positives or\n(perhaps worse) false-negatives. The linter's own test-suite is\nsupposed to protect against that, but test suite coverage is never\nperfect.\n\nSecond, ideally, the linter should protect against new broken\n&&-chains from entering the codebase, so poorly coded historic tests\nsuch as these aren't necessarily good motivation for tweaking, _and_\nit is (hopefully) unlikely that we would allow this sort of ugly shell\ncode to enter the codebase going forward. (The counterargument is that\nthis false-positive doesn't help someone coding up a new test who\nhasn't yet submitted the patch to the mailing list where more seasoned\neyes would suggest better coding style.)\n"},{"id":"354021","messageId":"20180731125026.GA8072@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CAPig+cRTgh6DStUdmXqvhbL_7sQY6wu21h27rjq_i=kZ_d+LAw@mail.gmail.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-31T12:50:27Z","receivedAt":"2018-07-31T12:50:30Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 30, 2018 at 05:38:06PM -0400, Eric Sunshine wrote:\n\n> > I wonder if it should look for something like [A-Z][A-Z_]* to catch\n> > all of these.\n> \n> I considered that, but it doesn't handle nested here-docs, which we\n> actually have in the test suite. For instance, from t9300-fast-import:\n> \n>     cat >input <<-INPUT_END &&\n>     mark :2\n>     data <<EOF\n>     $file2_data\n>     EOF\n>     ...\n>     INPUT_END\n> \n> Nesting could be handled easily enough either by stashing away the\n> opening tag and matching against it later _or_ by doing recursive\n> here-doc folding, however, 'sed' isn't a proper programming language\n> and can't be coerced into doing either of those. (And, it was tricky\n> enough just getting it to handle the nested case with a limited set of\n> recognized tag names, without having to explicitly handle every\n> combination of those names nested inside one another.)\n\nI hesitate to make any suggestion here, as I think we may have passed\na point of useful cost/benefit in sinking more time into this script.\nBut...is switching to awk or perl an option? Our test suite already\ndepends on having a vanilla perl, so I don't think it would be a new\ndependency. And it would give you actual data structures.\n\nBut like I said, it may not be worth it. I'd be OK just adjusting the\nfalse positive and moving on.\n\n> I am, for a couple reasons, somewhat hesitant to tweak the heuristic.\n> \n> First, each tweak has the potential of causing more false-positives or\n> (perhaps worse) false-negatives. The linter's own test-suite is\n> supposed to protect against that, but test suite coverage is never\n> perfect.\n> \n> Second, ideally, the linter should protect against new broken\n> &&-chains from entering the codebase, so poorly coded historic tests\n> such as these aren't necessarily good motivation for tweaking, _and_\n> it is (hopefully) unlikely that we would allow this sort of ugly shell\n> code to enter the codebase going forward. (The counterargument is that\n> this false-positive doesn't help someone coding up a new test who\n> hasn't yet submitted the patch to the mailing list where more seasoned\n> eyes would suggest better coding style.)\n\nRight, I think the real cost is somebody who adds \"<<CUSTOM_END_TAG\"\nlater and is confused when they see the breakage. I.e., I don't mind\nsaying \"we have a couple of style rules that you must follow to appease\nthe linter\". But if the error message is not clear, it can send somebody\ndown the wrong rabbit hole trying to figure out what is going on.\n\n-Peff\n"},{"id":"354101","messageId":"CAPig+cTqod7KY=EKPbFUFK=mpO1T2jUgpozMVN_0R51vgJLsDw@mail.gmail.com","threadId":"48778","inReplyTo":"20180731125026.GA8072@sigill.intra.peff.net","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-07-31T18:55:51Z","receivedAt":"2018-07-31T18:56:05Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Jul 31, 2018 at 8:50 AM Jeff King <peff@peff.net> wrote:\n> On Mon, Jul 30, 2018 at 05:38:06PM -0400, Eric Sunshine wrote:\n> > I considered that, but it doesn't handle nested here-docs, which we\n> > actually have in the test suite. For instance, from t9300-fast-import:\n> > [...]\n> > Nesting could be handled easily enough either by stashing away the\n> > opening tag and matching against it later _or_ by doing recursive\n> > here-doc folding, however, 'sed' isn't a proper programming language\n> > and can't be coerced into doing either of those. (And, it was tricky\n> > enough just getting it to handle the nested case with a limited set of\n> > recognized tag names, without having to explicitly handle every\n> > combination of those names nested inside one another.)\n>\n> I hesitate to make any suggestion here, as I think we may have passed\n> a point of useful cost/benefit in sinking more time into this script.\n> But...is switching to awk or perl an option? Our test suite already\n> depends on having a vanilla perl, so I don't think it would be a new\n> dependency. And it would give you actual data structures.\n\nIt would, and I did consider it, however, I was very concerned about\nstartup cost (launch time) with heavyweight perl considering that it\nwould have to be run for _every_ test. With 13000+ tests, that cost\nwas a very real concern, especially for Windows users, but even for\nMacOS users (such as myself, for which the full test suite already\ntakes probably close to 30 minutes to run, even on a ram drive). So, I\nwanted something very lightweight (and deliberately used that word in\nthe commit message), and 'sed' seemed the lightest-weight of the\nbunch.\n\n'awk' might be about as lightweight as 'sed', and it may even be\npossible to coerce it into handling the task (since the linter's job\nis primarily just a bunch of regex matching with very little\n\"manipulating\"). v1 of the linter was somewhat simpler and didn't deal\nwith these more complex cases, such as nested here-docs. v1 also did\nrather more \"manipulating\" of the script since the result was meant to\nbe run by the shell. When it came time to implement v2, which detects\nbroken &&-chains itself by textual inspection, most of the\nfunctionality (coming from v1) was already implemented in 'sed', so\n'awk' never really came up as a candidate since rewriting the script\nfrom scratch in 'awk' didn't seem like a good idea. (And, at the time\nv2 was started, I didn't know that these more complex cases would\narise.) So, 'awk' might be a viable alternative, and perhaps I'll take\na stab at it for fun at some point (or not), but I don't think there's\na pressing need right now.\n"},{"id":"354103","messageId":"20180731190853.GB3372@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CAPig+cTqod7KY=EKPbFUFK=mpO1T2jUgpozMVN_0R51vgJLsDw@mail.gmail.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-07-31T19:08:53Z","receivedAt":"2018-07-31T19:08:56Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 31, 2018 at 02:55:51PM -0400, Eric Sunshine wrote:\n\n> > I hesitate to make any suggestion here, as I think we may have passed\n> > a point of useful cost/benefit in sinking more time into this script.\n> > But...is switching to awk or perl an option? Our test suite already\n> > depends on having a vanilla perl, so I don't think it would be a new\n> > dependency. And it would give you actual data structures.\n> \n> It would, and I did consider it, however, I was very concerned about\n> startup cost (launch time) with heavyweight perl considering that it\n> would have to be run for _every_ test. With 13000+ tests, that cost\n> was a very real concern, especially for Windows users, but even for\n> MacOS users (such as myself, for which the full test suite already\n> takes probably close to 30 minutes to run, even on a ram drive). So, I\n> wanted something very lightweight (and deliberately used that word in\n> the commit message), and 'sed' seemed the lightest-weight of the\n> bunch.\n\nBoth perl and sed seem about the same on my system (sometimes one is\nfaster than the other, and sometimes vice versa). However, I expect for\nWindows the problem is not how big the child executable is, but running\na child process at all. I might be wrong, though.\n\n> 'awk' might be about as lightweight as 'sed', and it may even be\n> possible to coerce it into handling the task (since the linter's job\n> is primarily just a bunch of regex matching with very little\n> \"manipulating\"). v1 of the linter was somewhat simpler and didn't deal\n> with these more complex cases, such as nested here-docs. v1 also did\n> rather more \"manipulating\" of the script since the result was meant to\n> be run by the shell. When it came time to implement v2, which detects\n> broken &&-chains itself by textual inspection, most of the\n> functionality (coming from v1) was already implemented in 'sed', so\n> 'awk' never really came up as a candidate since rewriting the script\n> from scratch in 'awk' didn't seem like a good idea. (And, at the time\n> v2 was started, I didn't know that these more complex cases would\n> arise.) So, 'awk' might be a viable alternative, and perhaps I'll take\n> a stab at it for fun at some point (or not), but I don't think there's\n> a pressing need right now.\n\nYeah, I agree with that.\n\n-Peff\n"},{"id":"354711","messageId":"20180807082135.60913-1-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180711064642.6933-1-sunshine@sunshineco.com","subject":"[PATCH 0/5] chainlint: improve robustness against \"unusual\" shell coding","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-07T08:21:30Z","receivedAt":"2018-08-07T08:22:03Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This series improves chainlint's robustness when faced with the sort of\nunusual shell coding in contrib/subtree/t7900 which triggered a\nfalse-positive, as reported by Jonathan[1]. Jonathan has already\nrewritten[2] that code to be cleaner and more easily understood (and,\nconsequently, to avoid triggering the false-positive), thus the\nimprovements in this series are not strictly necessary.\n\nNevertheless, it seems prudent to make chainlint more robust against\nsuch unusual coding as an aid to future less-experienced test writers,\nmaking it less likely for them to trigger a false-positive and waste\ntime trying to decipher a non-existent problem (in their code).\n\nIn [3], I said that 'sed' couldn't \"be coerced\" into dealing with nested\nhere-docs with arbitrary tag names (explaining why it recognized only a\n\"blessed\" set of hard-coded names). However, I put a bit of thought into\nit and figured out how to do it. Patch 1/5 is the result.\n\nThis applies atop 'master'.\n\n[1]: https://public-inbox.org/git/20180730181356.GA156463@aiede.svl.corp.google.com/\n[2]: https://public-inbox.org/git/20180730190738.GD156463@aiede.svl.corp.google.com/\n[3]: https://public-inbox.org/git/CAPig+cRTgh6DStUdmXqvhbL_7sQY6wu21h27rjq_i=kZ_d+LAw@mail.gmail.com/\n\nEric Sunshine (5):\n  chainlint: match arbitrary here-docs tags rather than hard-coded names\n  chainlint: recognize multi-line $(...) when command cuddled with \"$(\"\n  chainlint: let here-doc and multi-line string commence on same line\n  chainlint: recognize multi-line quoted strings more robustly\n  chainlint: add test of pathological case which triggered false\n    positive\n\n t/chainlint.sed                               | 98 ++++++++++++-------\n t/chainlint/here-doc-close-subshell.expect    |  2 +\n t/chainlint/here-doc-close-subshell.test      |  5 +\n .../here-doc-multi-line-command-subst.expect  |  5 +\n .../here-doc-multi-line-command-subst.test    |  9 ++\n t/chainlint/here-doc-multi-line-string.expect |  4 +\n t/chainlint/here-doc-multi-line-string.test   |  8 ++\n t/chainlint/here-doc.expect                   |  2 +\n t/chainlint/here-doc.test                     |  7 ++\n ...ti-line-nested-command-substitution.expect | 11 ++-\n ...ulti-line-nested-command-substitution.test | 11 ++-\n t/chainlint/multi-line-string.expect          | 10 +-\n t/chainlint/multi-line-string.test            | 12 +++\n t/chainlint/nested-here-doc.expect            |  2 +\n t/chainlint/nested-here-doc.test              | 10 ++\n t/chainlint/subshell-here-doc.expect          |  4 +\n t/chainlint/subshell-here-doc.test            |  8 ++\n t/chainlint/t7900-subtree.expect              | 10 ++\n t/chainlint/t7900-subtree.test                | 22 +++++\n 19 files changed, 199 insertions(+), 41 deletions(-)\n create mode 100644 t/chainlint/here-doc-close-subshell.expect\n create mode 100644 t/chainlint/here-doc-close-subshell.test\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.expect\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.test\n create mode 100644 t/chainlint/here-doc-multi-line-string.expect\n create mode 100644 t/chainlint/here-doc-multi-line-string.test\n create mode 100644 t/chainlint/t7900-subtree.expect\n create mode 100644 t/chainlint/t7900-subtree.test\n\n-- \n2.18.0.758.g1932418f46\n\n"},{"id":"354712","messageId":"20180807082135.60913-2-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180807082135.60913-1-sunshine@sunshineco.com","subject":"[PATCH 1/5] chainlint: match arbitrary here-docs tags rather than hard-coded names","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-07T08:21:31Z","receivedAt":"2018-08-07T08:22:04Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"chainlint.sed swallows top-level here-docs to avoid being fooled by\ncontent which might look like start-of-subshell. It likewise swallows\nhere-docs in subshells to avoid marking content lines as breaking the\n&&-chain, and to avoid being fooled by content which might look like\nend-of-subshell, start-of-nested-subshell, or other specially-recognized\nconstructs.\n\nAt the time of implementation, it was believed that it was not possible\nto support arbitrary here-doc tag names since 'sed' provides no way to\nstash the opening tag name in a variable for later comparison against a\nline signaling end-of-here-doc. Consequently, tag names are hard-coded,\nwith \"EOF\" being the only tag recognized at the top-level, and only\n\"EOF\", \"EOT\", and \"INPUT_END\" being recognized within subshells. Also,\nspecial care was taken to avoid being confused by here-docs nested\nwithin other here-docs.\n\nIn practice, this limited number of hard-coded tag names has been \"good\nenough\" for the 13000+ existing Git test, despite many of those tests\nusing tags other than the recognized ones, since the bodies of those\nhere-docs do not contain content which would fool the linter.\nNevertheless, the situation is not ideal since someone writing new\ntests, and choosing a name not in the \"blessed\" set could potentially\ntrigger a false-positive.\n\nTo address this shortcoming, upgrade chainlint.sed to handle arbitrary\nhere-doc tag names, both at the top-level and within subshells.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                      | 57 +++++++++++++++++-----------\n t/chainlint/here-doc.expect          |  2 +\n t/chainlint/here-doc.test            |  7 ++++\n t/chainlint/nested-here-doc.expect   |  2 +\n t/chainlint/nested-here-doc.test     | 10 +++++\n t/chainlint/subshell-here-doc.expect |  4 ++\n t/chainlint/subshell-here-doc.test   |  8 ++++\n 7 files changed, 67 insertions(+), 23 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 5f0882cb38..bd76c5d181 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -61,6 +61,22 @@\n # \"else\", and \"fi\" in if-then-else likewise must not end with \"&&\", thus\n # receives similar treatment.\n #\n+# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a\n+# line such as \"cat <<EOF >out\" is seen, the here-doc tag is moved to the front\n+# of the line enclosed in angle brackets as a sentinel, giving \"<EOF>cat >out\".\n+# As each subsequent line is read, it is appended to the target line and a\n+# (whitespace-loose) back-reference match /^<(.*)>\\n\\1$/ is attempted to see if\n+# the content inside \"<...>\" matches the entirety of the newly-read line. For\n+# instance, if the next line read is \"some data\", when concatenated with the\n+# target line, it becomes \"<EOF>cat >out\\nsome data\", and a match is attempted\n+# to see if \"EOF\" matches \"some data\". Since it doesn't, the next line is\n+# attempted. When a line consisting of only \"EOF\" (and possible whitespace) is\n+# encountered, it is appended to the target line giving \"<EOF>cat >out\\nEOF\",\n+# in which case the \"EOF\" inside \"<...>\" does match the text following the\n+# newline, thus the closing here-doc tag has been found. The closing tag line\n+# and the \"<...>\" prefix on the target line are then discarded, leaving just\n+# the target line \"cat >out\".\n+#\n # To facilitate regression testing (and manual debugging), a \">\" annotation is\n # applied to the line containing \")\" which closes a subshell, \">>\" to a line\n # closing a nested subshell, and \">>>\" to a line closing both at once. This\n@@ -78,14 +94,17 @@\n \n # here-doc -- swallow it to avoid false hits within its body (but keep the\n # command to which it was attached)\n-/<<[ \t]*[-\\\\]*EOF[ \t]*/ {\n-\ts/[ \t]*<<[ \t]*[-\\\\]*EOF//\n-\th\n+/<<[ \t]*[-\\\\]*[A-Z0-9_][A-Z0-9_]*/ {\n+\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Z0-9_][A-Z0-9_]*\\)/<\\2>\\1<</\n+\ts/[ \t]*<<//\n \t:hereslurp\n \tN\n-\ts/.*\\n//\n-\t/^[ \t]*EOF[ \t]*$/!bhereslurp\n-\tx\n+\t/^<\\([^>]*\\)>.*\\n[ \t]*\\1[ \t]*$/!{\n+\t\ts/\\n.*$//\n+\t\tbhereslurp\n+\t}\n+\ts/^<[^>]*>//\n+\ts/\\n.*$//\n }\n \n # one-liner \"(...) &&\"\n@@ -139,9 +158,7 @@ s/.*\\n//\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n # here-doc -- swallow it\n-/<<[ \t]*[-\\\\]*EOF/bheredoc\n-/<<[ \t]*[-\\\\]*EOT/bheredoc\n-/<<[ \t]*[-\\\\]*INPUT_END/bheredoc\n+/<<[ \t]*[-\\\\]*[A-Z0-9_][A-Z0-9_]*/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n # re-visited to drop \"suspect\" marking since final line of those constructs\n@@ -249,23 +266,17 @@ s/\\n//\n bcheckchain\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\n-# the command to which it was attached); take care to handle here-docs nested\n-# within here-docs by only recognizing closing tag matching outer here-doc\n-# opening tag\n+# the command to which it was attached)\n :heredoc\n-/EOF/{ s/[ \t]*<<[ \t]*[-\\\\]*EOF//; s/^/EOF/; }\n-/EOT/{ s/[ \t]*<<[ \t]*[-\\\\]*EOT//; s/^/EOT/; }\n-/INPUT_END/{ s/[ \t]*<<[ \t]*[-\\\\]*INPUT_END//; s/^/INPUT_END/; }\n+s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Z0-9_][A-Z0-9_]*\\)/<\\2>\\1<</\n+s/[ \t]*<<//\n :hereslurpsub\n N\n-/^EOF.*\\n[ \t]*EOF[ \t]*$/bhereclose\n-/^EOT.*\\n[ \t]*EOT[ \t]*$/bhereclose\n-/^INPUT_END.*\\n[ \t]*INPUT_END[ \t]*$/bhereclose\n-bhereslurpsub\n-:hereclose\n-s/^EOF//\n-s/^EOT//\n-s/^INPUT_END//\n+/^<\\([^>]*\\)>.*\\n[ \t]*\\1[ \t]*$/!{\n+\ts/\\n.*$//\n+\tbhereslurpsub\n+}\n+s/^<[^>]*>//\n s/\\n.*$//\n bcheckchain\n \ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nindex 2328fe7753..33bc3cc0b4 100644\n--- a/t/chainlint/here-doc.expect\n+++ b/t/chainlint/here-doc.expect\n@@ -1,3 +1,5 @@\n boodle wobba        gorgo snoot        wafta snurb &&\n \n+cat >foo &&\n+\n horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nindex bd36f6e1d3..3736fd2cb4 100644\n--- a/t/chainlint/here-doc.test\n+++ b/t/chainlint/here-doc.test\n@@ -7,6 +7,13 @@ quoth the raven,\n nevermore...\n EOF\n \n+# LINT: swallow here-doc with arbitrary tag\n+cat <<-ARBITRARY >foo &&\n+snoz\n+boz\n+woz\n+ARBITRARY\n+\n # LINT: swallow here-doc (EOF is last line of test)\n horticulture <<\\EOF\n gomez\ndiff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect\nindex 559301e005..0c9ef1cfc6 100644\n--- a/t/chainlint/nested-here-doc.expect\n+++ b/t/chainlint/nested-here-doc.expect\n@@ -1,3 +1,5 @@\n+cat >foop &&\n+\n (\n \tcat &&\n ?!AMP?!\tcat\ndiff --git a/t/chainlint/nested-here-doc.test b/t/chainlint/nested-here-doc.test\nindex 027e0bb3ff..f35404bf0f 100644\n--- a/t/chainlint/nested-here-doc.test\n+++ b/t/chainlint/nested-here-doc.test\n@@ -1,3 +1,13 @@\n+# LINT: inner \"EOF\" not misintrepreted as closing ARBITRARY here-doc\n+cat <<ARBITRARY >foop &&\n+naddle\n+fub <<EOF\n+\tnozzle\n+\tnoodle\n+EOF\n+formp\n+ARBITRARY\n+\n (\n # LINT: inner \"EOF\" not misintrepreted as closing INPUT_END here-doc\n \tcat <<-\\INPUT_END &&\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nindex 19d5aff233..7c2da63bc7 100644\n--- a/t/chainlint/subshell-here-doc.expect\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -2,4 +2,8 @@\n \techo wobba \t       gorgo snoot \t       wafta snurb &&\n ?!AMP?!\tcat >bip\n \techo >bop\n+>) &&\n+(\n+\tcat >bup &&\n+\tmeep\n >)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nindex 9c3564c247..05139af0b5 100644\n--- a/t/chainlint/subshell-here-doc.test\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -20,4 +20,12 @@\n \twednesday\n \tpugsly\n \tEOF\n+) &&\n+(\n+# LINT: swallow here-doc with arbitrary tag\n+\tcat <<-\\ARBITRARY >bup &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY\n+\tmeep\n )\n-- \n2.18.0.758.g1932418f46\n\n"},{"id":"354713","messageId":"20180807082135.60913-3-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180807082135.60913-1-sunshine@sunshineco.com","subject":"[PATCH 2/5] chainlint: recognize multi-line $(...) when command cuddled with \"$(\"","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-07T08:21:32Z","receivedAt":"2018-08-07T08:22:05Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"For multi-line $(...) expressions nested within subshells, chainlint.sed\nonly recognizes:\n\n    x=$(\n        echo foo &&\n        ...\n\nbut it is not unlikely that test authors may also cuddle the command\nwith the opening \"$(\", so support that style, as well:\n\n    x=$(echo foo &&\n        ...\n\nThe closing \")\" is already correctly recognized when cuddled or not.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                                       |  2 +-\n .../multi-line-nested-command-substitution.expect     | 11 ++++++++++-\n .../multi-line-nested-command-substitution.test       | 11 ++++++++++-\n 3 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex bd76c5d181..a0726d3e7d 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -216,7 +216,7 @@ s/.*\\n//\n # \"$(...)\" -- command substitution; not closing \")\"\n /\\$([^)][^)]*)[^)]*$/bcheckchain\n # multi-line \"$(...\\n...)\" -- command substitution; treat as nested subshell\n-/\\$([ \t]*$/bnest\n+/\\$([^)]*$/bnest\n # \"=(...)\" -- Bash array assignment; not closing \")\"\n /=(/bcheckchain\n # closing \"...) &&\"\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.expect b/t/chainlint/multi-line-nested-command-substitution.expect\nindex 19c023b1c8..59b6c8b850 100644\n--- a/t/chainlint/multi-line-nested-command-substitution.expect\n+++ b/t/chainlint/multi-line-nested-command-substitution.expect\n@@ -6,4 +6,13 @@\n >>\t) &&\n \techo ok\n >) |\n-sort\n+sort &&\n+(\n+\tbar &&\n+\tx=$(echo bar |\n+\t\tcat\n+>>\t) &&\n+\ty=$(echo baz |\n+>>\t\tfip) &&\n+\techo fail\n+>)\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.test b/t/chainlint/multi-line-nested-command-substitution.test\nindex ca0620ab6b..300058341b 100644\n--- a/t/chainlint/multi-line-nested-command-substitution.test\n+++ b/t/chainlint/multi-line-nested-command-substitution.test\n@@ -6,4 +6,13 @@\n \t) &&\n \techo ok\n ) |\n-sort\n+sort &&\n+(\n+\tbar &&\n+\tx=$(echo bar |\n+\t\tcat\n+\t) &&\n+\ty=$(echo baz |\n+\t\tfip) &&\n+\techo fail\n+)\n-- \n2.18.0.758.g1932418f46\n\n"},{"id":"354714","messageId":"20180807082135.60913-4-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180807082135.60913-1-sunshine@sunshineco.com","subject":"[PATCH 3/5] chainlint: let here-doc and multi-line string commence on same line","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-07T08:21:33Z","receivedAt":"2018-08-07T08:22:06Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"After swallowing a here-doc, chainlint.sed assumes that no other\nprocessing needs to be done on the line aside from checking for &&-chain\nbreakage; likewise, after folding a multi-line quoted string. However,\nit's conceivable (even if unlikely in practice) that both a here-doc and\na multi-line quoted string might commence on the same line:\n\n    cat <<\\EOF && echo \"foo\n    bar\"\n    data\n    EOF\n\nSupport this case by sending the line (after swallowing and folding)\nthrough the normal processing sequence rather than jumping directly to\nthe check for broken &&-chain.\n\nThis change also allows other somewhat pathological cases to be handled,\nsuch as closing a subshell on the same line starting a here-doc:\n\n    (\n        cat <<-\\INPUT)\n        data\n        INPUT\n\nor, for instance, opening a multi-line $(...) expression on the same\nline starting a here-doc:\n\n    x=$(cat <<-\\END &&\n        data\n        END\n        echo \"x\")\n\namong others.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                                      | 7 ++++---\n t/chainlint/here-doc-close-subshell.expect           | 2 ++\n t/chainlint/here-doc-close-subshell.test             | 5 +++++\n t/chainlint/here-doc-multi-line-command-subst.expect | 5 +++++\n t/chainlint/here-doc-multi-line-command-subst.test   | 9 +++++++++\n t/chainlint/here-doc-multi-line-string.expect        | 4 ++++\n t/chainlint/here-doc-multi-line-string.test          | 8 ++++++++\n 7 files changed, 37 insertions(+), 3 deletions(-)\n create mode 100644 t/chainlint/here-doc-close-subshell.expect\n create mode 100644 t/chainlint/here-doc-close-subshell.test\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.expect\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.test\n create mode 100644 t/chainlint/here-doc-multi-line-string.expect\n create mode 100644 t/chainlint/here-doc-multi-line-string.test\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex a0726d3e7d..6c891bf383 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -157,6 +157,7 @@ s/.*\\n//\n /^[^']*'[^']*$/{\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n+:folded\n # here-doc -- swallow it\n /<<[ \t]*[-\\\\]*[A-Z0-9_][A-Z0-9_]*/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n@@ -255,7 +256,7 @@ s/\"//g\n N\n s/\\n//\n /\"/!bdqstring\n-bcheckchain\n+bfolded\n \n # found multi-line single-quoted string '...\\n...' -- slurp until end of string\n :sqstring\n@@ -263,7 +264,7 @@ s/'//g\n N\n s/\\n//\n /'/!bsqstring\n-bcheckchain\n+bfolded\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\n # the command to which it was attached)\n@@ -278,7 +279,7 @@ N\n }\n s/^<[^>]*>//\n s/\\n.*$//\n-bcheckchain\n+bfolded\n \n # found \"case ... in\" -- pass through untouched\n :case\ndiff --git a/t/chainlint/here-doc-close-subshell.expect b/t/chainlint/here-doc-close-subshell.expect\nnew file mode 100644\nindex 0000000000..f011e335e5\n--- /dev/null\n+++ b/t/chainlint/here-doc-close-subshell.expect\n@@ -0,0 +1,2 @@\n+(\n+>\tcat)\ndiff --git a/t/chainlint/here-doc-close-subshell.test b/t/chainlint/here-doc-close-subshell.test\nnew file mode 100644\nindex 0000000000..b857ff5467\n--- /dev/null\n+++ b/t/chainlint/here-doc-close-subshell.test\n@@ -0,0 +1,5 @@\n+(\n+# LINT: line contains here-doc and closes nested subshell\n+\tcat <<-\\INPUT)\n+\tfizz\n+\tINPUT\ndiff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect\nnew file mode 100644\nindex 0000000000..e5fb752d2f\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-command-subst.expect\n@@ -0,0 +1,5 @@\n+(\n+\tx=$(bobble &&\n+?!AMP?!>>\t\twiffle)\n+\techo $x\n+>)\ndiff --git a/t/chainlint/here-doc-multi-line-command-subst.test b/t/chainlint/here-doc-multi-line-command-subst.test\nnew file mode 100644\nindex 0000000000..899bc5de8b\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-command-subst.test\n@@ -0,0 +1,9 @@\n+(\n+# LINT: line contains here-doc and opens multi-line $(...)\n+\tx=$(bobble <<-\\END &&\n+\t\tfossil\n+\t\tvegetable\n+\t\tEND\n+\t\twiffle)\n+\techo $x\n+)\ndiff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect\nnew file mode 100644\nindex 0000000000..1e5b724b9d\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-string.expect\n@@ -0,0 +1,4 @@\n+(\n+?!AMP?!\tcat && echo multi-line\tstring\"\n+\tbap\n+>)\ndiff --git a/t/chainlint/here-doc-multi-line-string.test b/t/chainlint/here-doc-multi-line-string.test\nnew file mode 100644\nindex 0000000000..a53edbcc8d\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-string.test\n@@ -0,0 +1,8 @@\n+(\n+# LINT: line contains here-doc and opens multi-line string\n+\tcat <<-\\TXT && echo \"multi-line\n+\tstring\"\n+\tfizzle\n+\tTXT\n+\tbap\n+)\n-- \n2.18.0.758.g1932418f46\n\n"},{"id":"354715","messageId":"20180807082135.60913-5-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180807082135.60913-1-sunshine@sunshineco.com","subject":"[PATCH 4/5] chainlint: recognize multi-line quoted strings more robustly","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-07T08:21:34Z","receivedAt":"2018-08-07T08:22:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"chainlint.sed recognizes multi-line quoted strings within subshells:\n\n    echo \"abc\n        def\" >out &&\n\nso it can avoid incorrectly classifying lines internal to the string as\nbreaking the &&-chain. To identify the first line of a multi-line\nstring, it checks if the line contains a single quote. However, this is\nfragile and can be easily fooled by a line containing multiple strings:\n\n    echo \"xyz\" \"abc\n        def\" >out &&\n\nMake detection more robust by checking for an odd number of quotes\nrather than only a single one.\n\n(Escaped quotes are not handled, but support may be added later.)\n\nThe original multi-line string recognizer rather cavalierly threw away\nall but the final quote, whereas the new one is careful to retain all\nquotes, so the \"expected\" output of a couple existing chainlint tests is\nupdated to account for this new behavior.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                               | 32 +++++++++++++------\n t/chainlint/here-doc-multi-line-string.expect |  2 +-\n t/chainlint/multi-line-string.expect          | 10 ++++--\n t/chainlint/multi-line-string.test            | 12 +++++++\n 4 files changed, 43 insertions(+), 13 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 6c891bf383..338163c2f5 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -151,10 +151,10 @@ s/.*\\n//\n :slurp\n # incomplete line \"...\\\"\n /\\\\$/bincomplete\n-# multi-line quoted string \"...\\n...\"\n-/^[^\"]*\"[^\"]*$/bdqstring\n-# multi-line quoted string '...\\n...' (but not contraction in string \"it's so\")\n-/^[^']*'[^']*$/{\n+# multi-line quoted string \"...\\n...\"?\n+/\"/bdqstring\n+# multi-line quoted string '...\\n...'? (but not contraction in string \"it's\")\n+/'/{\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n :folded\n@@ -250,20 +250,32 @@ N\n s/\\\\\\n//\n bslurp\n \n-# found multi-line double-quoted string \"...\\n...\" -- slurp until end of string\n+# check for multi-line double-quoted string \"...\\n...\" -- fold to one line\n :dqstring\n-s/\"//g\n+# remove all quote pairs\n+s/\"\\([^\"]*\\)\"/@!\\1@!/g\n+# done if no dangling quote\n+/\"/!bdqdone\n+# otherwise, slurp next line and try again\n N\n s/\\n//\n-/\"/!bdqstring\n+bdqstring\n+:dqdone\n+s/@!/\"/g\n bfolded\n \n-# found multi-line single-quoted string '...\\n...' -- slurp until end of string\n+# check for multi-line single-quoted string '...\\n...' -- fold to one line\n :sqstring\n-s/'//g\n+# remove all quote pairs\n+s/'\\([^']*\\)'/@!\\1@!/g\n+# done if no dangling quote\n+/'/!bsqdone\n+# otherwise, slurp next line and try again\n N\n s/\\n//\n-/'/!bsqstring\n+bsqstring\n+:sqdone\n+s/@!/'/g\n bfolded\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\ndiff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect\nindex 1e5b724b9d..32038a070c 100644\n--- a/t/chainlint/here-doc-multi-line-string.expect\n+++ b/t/chainlint/here-doc-multi-line-string.expect\n@@ -1,4 +1,4 @@\n (\n-?!AMP?!\tcat && echo multi-line\tstring\"\n+?!AMP?!\tcat && echo \"multi-line\tstring\"\n \tbap\n >)\ndiff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect\nindex 8334c4cc8e..170cb59993 100644\n--- a/t/chainlint/multi-line-string.expect\n+++ b/t/chainlint/multi-line-string.expect\n@@ -1,9 +1,15 @@\n (\n-\tx=line 1\t\tline 2\t\tline 3\" &&\n-?!AMP?!\ty=line 1\t\tline2'\n+\tx=\"line 1\t\tline 2\t\tline 3\" &&\n+?!AMP?!\ty='line 1\t\tline2'\n \tfoobar\n >) &&\n (\n \techo \"there's nothing to see here\" &&\n \texit\n+>) &&\n+(\n+\techo \"xyz\" \"abc\t\tdef\t\tghi\" &&\n+\techo 'xyz' 'abc\t\tdef\t\tghi' &&\n+\techo 'xyz' \"abc\t\tdef\t\tghi\" &&\n+\tbarfoo\n >)\ndiff --git a/t/chainlint/multi-line-string.test b/t/chainlint/multi-line-string.test\nindex 14cb44d51c..287ab89705 100644\n--- a/t/chainlint/multi-line-string.test\n+++ b/t/chainlint/multi-line-string.test\n@@ -12,4 +12,16 @@\n # LINT: starting multi-line single-quoted string\n \techo \"there's nothing to see here\" &&\n \texit\n+) &&\n+(\n+\techo \"xyz\" \"abc\n+\t\tdef\n+\t\tghi\" &&\n+\techo 'xyz' 'abc\n+\t\tdef\n+\t\tghi' &&\n+\techo 'xyz' \"abc\n+\t\tdef\n+\t\tghi\" &&\n+\tbarfoo\n )\n-- \n2.18.0.758.g1932418f46\n\n"},{"id":"354716","messageId":"20180807082135.60913-6-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180807082135.60913-1-sunshine@sunshineco.com","subject":"[PATCH 5/5] chainlint: add test of pathological case which triggered false positive","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-07T08:21:35Z","receivedAt":"2018-08-07T08:22:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This extract from contrib/subtree/t7900 triggered a false positive due\nto three chainlint limitations:\n\n* recognizing only a \"blessed\" set of here-doc tag names in a subshell\n  (\"EOF\", \"EOT\", \"INPUT_END\"), of which \"TXT\" is not a member\n\n* inability to recognize multi-line $(...) when the first statement of\n  the body is cuddled with the opening \"$(\"\n\n* inability to recognize multiple constructs on a single line, such as\n  opening a multi-line $(...) and starting a here-doc\n\nNow that all of these shortcomings have been addressed, turn this rather\npathological bit of shell coding into a chainlint test case.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/t7900-subtree.expect | 10 ++++++++++\n t/chainlint/t7900-subtree.test   | 22 ++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n create mode 100644 t/chainlint/t7900-subtree.expect\n create mode 100644 t/chainlint/t7900-subtree.test\n\ndiff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect\nnew file mode 100644\nindex 0000000000..c9913429e6\n--- /dev/null\n+++ b/t/chainlint/t7900-subtree.expect\n@@ -0,0 +1,10 @@\n+(\n+\tchks=\"sub1sub2sub3sub4\" &&\n+\tchks_sub=$(cat | sed 's,^,sub dir/,'\n+>>) &&\n+\tchkms=\"main-sub1main-sub2main-sub3main-sub4\" &&\n+\tchkms_sub=$(cat | sed 's,^,sub dir/,'\n+>>) &&\n+\tsubfiles=$(git ls-files) &&\n+\tcheck_equal \"$subfiles\" \"$chkms$chks\"\n+>)\ndiff --git a/t/chainlint/t7900-subtree.test b/t/chainlint/t7900-subtree.test\nnew file mode 100644\nindex 0000000000..277d8358df\n--- /dev/null\n+++ b/t/chainlint/t7900-subtree.test\n@@ -0,0 +1,22 @@\n+(\n+\tchks=\"sub1\n+sub2\n+sub3\n+sub4\" &&\n+\tchks_sub=$(cat <<TXT | sed 's,^,sub dir/,'\n+$chks\n+TXT\n+) &&\n+\tchkms=\"main-sub1\n+main-sub2\n+main-sub3\n+main-sub4\" &&\n+\tchkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'\n+$chkms\n+TXT\n+) &&\n+\n+\tsubfiles=$(git ls-files) &&\n+\tcheck_equal \"$subfiles\" \"$chkms\n+$chks\"\n+)\n-- \n2.18.0.758.g1932418f46\n\n"},{"id":"354953","messageId":"20180808225011.GC3902@sigill.intra.peff.net","threadId":"48778","inReplyTo":"20180807082135.60913-2-sunshine@sunshineco.com","subject":"Re: [PATCH 1/5] chainlint: match arbitrary here-docs tags rather than hard-coded names","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-08-08T22:50:12Z","receivedAt":"2018-08-08T22:50:15Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Aug 07, 2018 at 04:21:31AM -0400, Eric Sunshine wrote:\n\n> diff --git a/t/chainlint.sed b/t/chainlint.sed\n> index 5f0882cb38..bd76c5d181 100644\n> --- a/t/chainlint.sed\n> +++ b/t/chainlint.sed\n> @@ -61,6 +61,22 @@\n>  # \"else\", and \"fi\" in if-then-else likewise must not end with \"&&\", thus\n>  # receives similar treatment.\n>  #\n> +# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a\n> +# line such as \"cat <<EOF >out\" is seen, the here-doc tag is moved to the front\n> +# of the line enclosed in angle brackets as a sentinel, giving \"<EOF>cat >out\".\n> +# As each subsequent line is read, it is appended to the target line and a\n> +# (whitespace-loose) back-reference match /^<(.*)>\\n\\1$/ is attempted to see if\n> +# the content inside \"<...>\" matches the entirety of the newly-read line. For\n> +# instance, if the next line read is \"some data\", when concatenated with the\n> +# target line, it becomes \"<EOF>cat >out\\nsome data\", and a match is attempted\n> +# to see if \"EOF\" matches \"some data\". Since it doesn't, the next line is\n> +# attempted. When a line consisting of only \"EOF\" (and possible whitespace) is\n> +# encountered, it is appended to the target line giving \"<EOF>cat >out\\nEOF\",\n> +# in which case the \"EOF\" inside \"<...>\" does match the text following the\n> +# newline, thus the closing here-doc tag has been found. The closing tag line\n> +# and the \"<...>\" prefix on the target line are then discarded, leaving just\n> +# the target line \"cat >out\".\n\nGross, but OK, as long as we would not get confused by a line that\nactually started with <EOF> at the start.\n\n> +/<<[ \t]*[-\\\\]*[A-Z0-9_][A-Z0-9_]*/ {\n> +\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Z0-9_][A-Z0-9_]*\\)/<\\2>\\1<</\n> +\ts/[ \t]*<<//\n\nHere-docs can use lowercase, too, though I'd personally frown on that\nfrom a style perspective.\n\nIt looks like this doesn't catch:\n\n  cat <<'EOF'\n  EOF\n\neither. I think we prefer the backslash style, but there are quite a few\n<<-'EOF' hits. Is it covered somewhere else?\n\n-Peff\n"},{"id":"354954","messageId":"20180808225327.GD3902@sigill.intra.peff.net","threadId":"48778","inReplyTo":"20180807082135.60913-1-sunshine@sunshineco.com","subject":"Re: [PATCH 0/5] chainlint: improve robustness against \"unusual\" shell coding","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-08-08T22:53:28Z","receivedAt":"2018-08-08T22:53:31Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Aug 07, 2018 at 04:21:30AM -0400, Eric Sunshine wrote:\n\n> This series improves chainlint's robustness when faced with the sort of\n> unusual shell coding in contrib/subtree/t7900 which triggered a\n> false-positive, as reported by Jonathan[1]. Jonathan has already\n> rewritten[2] that code to be cleaner and more easily understood (and,\n> consequently, to avoid triggering the false-positive), thus the\n> improvements in this series are not strictly necessary.\n> \n> Nevertheless, it seems prudent to make chainlint more robust against\n> such unusual coding as an aid to future less-experienced test writers,\n> making it less likely for them to trigger a false-positive and waste\n> time trying to decipher a non-existent problem (in their code).\n> \n> In [3], I said that 'sed' couldn't \"be coerced\" into dealing with nested\n> here-docs with arbitrary tag names (explaining why it recognized only a\n> \"blessed\" set of hard-coded names). However, I put a bit of thought into\n> it and figured out how to do it. Patch 1/5 is the result.\n> \n> This applies atop 'master'.\n\nI had two minor comments on the first patch. I'll admit my eyes glazed\nover looking at the rest of them, and to make any kind of intelligent\nreview I'd need to spend an hour understanding how the sed script works.\nWhich frankly, I'm not sure is worth it. Given the empirical results\n(both on the real code base and the new tests you add) and the low-risk\nnature (it's linting our tests, after all, not code users run), I'd be\ninclined to say it's not making anything worse, and probably making\nthings better. We can find out about any further short-comings in the\nwild.\n\n-Peff\n"},{"id":"354962","messageId":"xmqq4lg4e5d4.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180808225327.GD3902@sigill.intra.peff.net","subject":"Re: [PATCH 0/5] chainlint: improve robustness against \"unusual\" shell coding","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-08-09T00:44:07Z","receivedAt":"2018-08-09T00:45:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I had two minor comments on the first patch. I'll admit my eyes glazed\n> over looking at the rest of them, and to make any kind of intelligent\n> review I'd need to spend an hour understanding how the sed script works.\n> Which frankly, I'm not sure is worth it.\n\nDidn't I make this prediction when we started the \"text inspection\"\napproach that it quickly go downhill resulting in unmaintainable\nmess rather quickly ;-)?\n\n> Given the empirical results\n> (both on the real code base and the new tests you add) and the low-risk\n> nature (it's linting our tests, after all, not code users run), I'd be\n> inclined to say it's not making anything worse, and probably making\n> things better. We can find out about any further short-comings in the\n> wild.\n\nAmen to that.\n"},{"id":"354967","messageId":"CAPig+cR6=HjSo4HW8jPCDBQ0SsVqCw8o=cekVEPnSZZzsh2Tkw@mail.gmail.com","threadId":"48778","inReplyTo":"20180808225011.GC3902@sigill.intra.peff.net","subject":"Re: [PATCH 1/5] chainlint: match arbitrary here-docs tags rather than hard-coded names","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-09T05:58:05Z","receivedAt":"2018-08-09T05:58:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Wed, Aug 8, 2018 at 6:50 PM Jeff King <peff@peff.net> wrote:\n> On Tue, Aug 07, 2018 at 04:21:31AM -0400, Eric Sunshine wrote:\n> > +# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a\n> > +# line such as \"cat <<EOF >out\" is seen, the here-doc tag is moved to the front\n> > +# of the line enclosed in angle brackets as a sentinel, giving \"<EOF>cat >out\".\n>\n> Gross, but OK, as long as we would not get confused by a line that\n> actually started with <EOF> at the start.\n\nIt can't get confused by such a line. There here-doc swallower\nprepends that when it starts the swallowing process and removes it add\nthe end. Even if a line actually started with that, it would become\n\"<EOF><EOF>cmd\" while swallowing the here-doc, and be restored to\n\"<EOF>cmd\" at the end. Stripping the \"<EOF>\" is done non-greedily, so\nit wouldn't remove both of them. Likewise, non-greedy matching is used\nfor pulling the \"EOF\" out of the \"<...>\" when trying to match against\nthe terminating \"EOF\" line, so there can be no confusion.\n\n> > +/<<[         ]*[-\\\\]*[A-Z0-9_][A-Z0-9_]*/ {\n> > +     s/^\\(.*\\)<<[    ]*[-\\\\]*\\([A-Z0-9_][A-Z0-9_]*\\)/<\\2>\\1<</\n> > +     s/[     ]*<<//\n>\n> Here-docs can use lowercase, too, though I'd personally frown on that\n> from a style perspective.\n\nYeah, I was going with the tighter uppercase-only which Jonathan\nsuggested[1], but I guess it wouldn't hurt to re-roll to allow\nlowercase too.\n\n[1]: https://public-inbox.org/git/20180730205914.GE156463@aiede.svl.corp.google.com/\n\n> It looks like this doesn't catch:\n>\n>   cat <<'EOF'\n>   EOF\n>\n> either. I think we prefer the backslash style, but there are quite a few\n> <<-'EOF' hits. Is it covered somewhere else?\n\nNo. I've gotten so used to \\EOF in this codebase that it didn't occur\nto me to even think about 'EOF', but a re-roll could add that, as\nwell.\n"},{"id":"354992","messageId":"20180809142636.GC1439@sigill.intra.peff.net","threadId":"48778","inReplyTo":"CAPig+cR6=HjSo4HW8jPCDBQ0SsVqCw8o=cekVEPnSZZzsh2Tkw@mail.gmail.com","subject":"Re: [PATCH 1/5] chainlint: match arbitrary here-docs tags rather than hard-coded names","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2018-08-09T14:26:36Z","receivedAt":"2018-08-09T14:26:40Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Aug 09, 2018 at 01:58:05AM -0400, Eric Sunshine wrote:\n\n> On Wed, Aug 8, 2018 at 6:50 PM Jeff King <peff@peff.net> wrote:\n> > On Tue, Aug 07, 2018 at 04:21:31AM -0400, Eric Sunshine wrote:\n> > > +# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a\n> > > +# line such as \"cat <<EOF >out\" is seen, the here-doc tag is moved to the front\n> > > +# of the line enclosed in angle brackets as a sentinel, giving \"<EOF>cat >out\".\n> >\n> > Gross, but OK, as long as we would not get confused by a line that\n> > actually started with <EOF> at the start.\n> \n> It can't get confused by such a line. There here-doc swallower\n> prepends that when it starts the swallowing process and removes it add\n> the end. Even if a line actually started with that, it would become\n> \"<EOF><EOF>cmd\" while swallowing the here-doc, and be restored to\n> \"<EOF>cmd\" at the end. Stripping the \"<EOF>\" is done non-greedily, so\n> it wouldn't remove both of them. Likewise, non-greedy matching is used\n> for pulling the \"EOF\" out of the \"<...>\" when trying to match against\n> the terminating \"EOF\" line, so there can be no confusion.\n\nThanks. I figured you probably had thought of that, but it seemed easier\nto ask than to wade through the sed code (I do feel like a bad person to\ngive that answer, because IMHO one of the key things that makes open\nsource work is a willingness to dig in yourself rather than asking; but\nI am making an exception for this sed code).\n\n> Yeah, I was going with the tighter uppercase-only which Jonathan\n> suggested[1], but I guess it wouldn't hurt to re-roll to allow\n> lowercase too.\n>\n> [...]\n>\n> No. I've gotten so used to \\EOF in this codebase that it didn't occur\n> to me to even think about 'EOF', but a re-roll could add that, as\n> well.\n\nThanks. I could take or leave such fixes, since I think our style\ndiscourages both, so I'll leave it up to you whether you want to pursue\nthem.\n\n-Peff\n"},{"id":"355309","messageId":"20180813084739.16134-1-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180807082135.60913-1-sunshine@sunshineco.com","subject":"[PATCH v2 0/6] chainlint: improve robustness against \"unusual\" shell coding","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T08:47:33Z","receivedAt":"2018-08-13T08:48:17Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is a re-roll of [1] which improves chainlint's robustness in the\nface of unusual shell coding such as in contrib/subtree/t7900 which\ntriggered a false-positive[2].\n\nChanges since v1:\n\n* recognize lowercase in here-doc tag names (in addition to uppercase)\n* recognize 'quoted' here-doc tag names (in addition to \\escaped)\n\nPatch 2/6 is new. Range-diff below.\n\n[1]: https://public-inbox.org/git/20180807082135.60913-1-sunshine@sunshineco.com/\n[2]: https://public-inbox.org/git/20180730181356.GA156463@aiede.svl.corp.google.com/\n\nEric Sunshine (6):\n  chainlint: match arbitrary here-docs tags rather than hard-coded names\n  chainlint: match 'quoted' here-doc tags\n  chainlint: recognize multi-line $(...) when command cuddled with \"$(\"\n  chainlint: let here-doc and multi-line string commence on same line\n  chainlint: recognize multi-line quoted strings more robustly\n  chainlint: add test of pathological case which triggered false\n    positive\n\n t/chainlint.sed                               | 98 ++++++++++++-------\n t/chainlint/here-doc-close-subshell.expect    |  2 +\n t/chainlint/here-doc-close-subshell.test      |  5 +\n .../here-doc-multi-line-command-subst.expect  |  5 +\n .../here-doc-multi-line-command-subst.test    |  9 ++\n t/chainlint/here-doc-multi-line-string.expect |  4 +\n t/chainlint/here-doc-multi-line-string.test   |  8 ++\n t/chainlint/here-doc.expect                   |  4 +\n t/chainlint/here-doc.test                     | 14 +++\n ...ti-line-nested-command-substitution.expect | 11 ++-\n ...ulti-line-nested-command-substitution.test | 11 ++-\n t/chainlint/multi-line-string.expect          | 10 +-\n t/chainlint/multi-line-string.test            | 12 +++\n t/chainlint/nested-here-doc.expect            |  2 +\n t/chainlint/nested-here-doc.test              | 10 ++\n t/chainlint/subshell-here-doc.expect          |  5 +\n t/chainlint/subshell-here-doc.test            | 12 +++\n t/chainlint/t7900-subtree.expect              | 10 ++\n t/chainlint/t7900-subtree.test                | 22 +++++\n 19 files changed, 213 insertions(+), 41 deletions(-)\n create mode 100644 t/chainlint/here-doc-close-subshell.expect\n create mode 100644 t/chainlint/here-doc-close-subshell.test\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.expect\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.test\n create mode 100644 t/chainlint/here-doc-multi-line-string.expect\n create mode 100644 t/chainlint/here-doc-multi-line-string.test\n create mode 100644 t/chainlint/t7900-subtree.expect\n create mode 100644 t/chainlint/t7900-subtree.test\n\nRange-diff against v1:\n1:  d9ed356b97 ! 1:  f4c606cb7a chainlint: match arbitrary here-docs tags rather than hard-coded names\n    @@ -64,8 +64,8 @@\n     -/<<[ \t]*[-\\\\]*EOF[ \t]*/ {\n     -\ts/[ \t]*<<[ \t]*[-\\\\]*EOF//\n     -\th\n    -+/<<[ \t]*[-\\\\]*[A-Z0-9_][A-Z0-9_]*/ {\n    -+\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Z0-9_][A-Z0-9_]*\\)/<\\2>\\1<</\n    ++/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/ {\n    ++\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n     +\ts/[ \t]*<<//\n      \t:hereslurp\n      \tN\n    @@ -88,7 +88,7 @@\n     -/<<[ \t]*[-\\\\]*EOF/bheredoc\n     -/<<[ \t]*[-\\\\]*EOT/bheredoc\n     -/<<[ \t]*[-\\\\]*INPUT_END/bheredoc\n    -+/<<[ \t]*[-\\\\]*[A-Z0-9_][A-Z0-9_]*/bheredoc\n    ++/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/bheredoc\n      # comment or empty line -- discard since final non-comment, non-empty line\n      # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n      # re-visited to drop \"suspect\" marking since final line of those constructs\n    @@ -104,7 +104,7 @@\n     -/EOF/{ s/[ \t]*<<[ \t]*[-\\\\]*EOF//; s/^/EOF/; }\n     -/EOT/{ s/[ \t]*<<[ \t]*[-\\\\]*EOT//; s/^/EOT/; }\n     -/INPUT_END/{ s/[ \t]*<<[ \t]*[-\\\\]*INPUT_END//; s/^/INPUT_END/; }\n    -+s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Z0-9_][A-Z0-9_]*\\)/<\\2>\\1<</\n    ++s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n     +s/[ \t]*<<//\n      :hereslurpsub\n      N\n    @@ -143,11 +143,11 @@\n      EOF\n      \n     +# LINT: swallow here-doc with arbitrary tag\n    -+cat <<-ARBITRARY >foo &&\n    ++cat <<-Arbitrary_Tag_42 >foo &&\n     +snoz\n     +boz\n     +woz\n    -+ARBITRARY\n    ++Arbitrary_Tag_42\n     +\n      # LINT: swallow here-doc (EOF is last line of test)\n      horticulture <<\\EOF\n-:  ---------- > 2:  61c0d9c979 chainlint: match 'quoted' here-doc tags\n2:  d63920cdd5 = 3:  b97a05aa9c chainlint: recognize multi-line $(...) when command cuddled with \"$(\"\n3:  a5078923ef = 4:  9d4d2b6c4d chainlint: let here-doc and multi-line string commence on same line\n4:  4841ca6ac9 = 5:  0a7f533889 chainlint: recognize multi-line quoted strings more robustly\n5:  e9eb45a2dc = 6:  d42bad0323 chainlint: add test of pathological case which triggered false positive\n-- \n2.18.0.267.gbc8be36ecb\n"},{"id":"355310","messageId":"20180813084739.16134-2-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180813084739.16134-1-sunshine@sunshineco.com","subject":"[PATCH v2 1/6] chainlint: match arbitrary here-docs tags rather than hard-coded names","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T08:47:34Z","receivedAt":"2018-08-13T08:48:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"chainlint.sed swallows top-level here-docs to avoid being fooled by\ncontent which might look like start-of-subshell. It likewise swallows\nhere-docs in subshells to avoid marking content lines as breaking the\n&&-chain, and to avoid being fooled by content which might look like\nend-of-subshell, start-of-nested-subshell, or other specially-recognized\nconstructs.\n\nAt the time of implementation, it was believed that it was not possible\nto support arbitrary here-doc tag names since 'sed' provides no way to\nstash the opening tag name in a variable for later comparison against a\nline signaling end-of-here-doc. Consequently, tag names are hard-coded,\nwith \"EOF\" being the only tag recognized at the top-level, and only\n\"EOF\", \"EOT\", and \"INPUT_END\" being recognized within subshells. Also,\nspecial care was taken to avoid being confused by here-docs nested\nwithin other here-docs.\n\nIn practice, this limited number of hard-coded tag names has been \"good\nenough\" for the 13000+ existing Git test, despite many of those tests\nusing tags other than the recognized ones, since the bodies of those\nhere-docs do not contain content which would fool the linter.\nNevertheless, the situation is not ideal since someone writing new\ntests, and choosing a name not in the \"blessed\" set could potentially\ntrigger a false-positive.\n\nTo address this shortcoming, upgrade chainlint.sed to handle arbitrary\nhere-doc tag names, both at the top-level and within subshells.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                      | 57 +++++++++++++++++-----------\n t/chainlint/here-doc.expect          |  2 +\n t/chainlint/here-doc.test            |  7 ++++\n t/chainlint/nested-here-doc.expect   |  2 +\n t/chainlint/nested-here-doc.test     | 10 +++++\n t/chainlint/subshell-here-doc.expect |  4 ++\n t/chainlint/subshell-here-doc.test   |  8 ++++\n 7 files changed, 67 insertions(+), 23 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 5f0882cb38..2af1a687f8 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -61,6 +61,22 @@\n # \"else\", and \"fi\" in if-then-else likewise must not end with \"&&\", thus\n # receives similar treatment.\n #\n+# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a\n+# line such as \"cat <<EOF >out\" is seen, the here-doc tag is moved to the front\n+# of the line enclosed in angle brackets as a sentinel, giving \"<EOF>cat >out\".\n+# As each subsequent line is read, it is appended to the target line and a\n+# (whitespace-loose) back-reference match /^<(.*)>\\n\\1$/ is attempted to see if\n+# the content inside \"<...>\" matches the entirety of the newly-read line. For\n+# instance, if the next line read is \"some data\", when concatenated with the\n+# target line, it becomes \"<EOF>cat >out\\nsome data\", and a match is attempted\n+# to see if \"EOF\" matches \"some data\". Since it doesn't, the next line is\n+# attempted. When a line consisting of only \"EOF\" (and possible whitespace) is\n+# encountered, it is appended to the target line giving \"<EOF>cat >out\\nEOF\",\n+# in which case the \"EOF\" inside \"<...>\" does match the text following the\n+# newline, thus the closing here-doc tag has been found. The closing tag line\n+# and the \"<...>\" prefix on the target line are then discarded, leaving just\n+# the target line \"cat >out\".\n+#\n # To facilitate regression testing (and manual debugging), a \">\" annotation is\n # applied to the line containing \")\" which closes a subshell, \">>\" to a line\n # closing a nested subshell, and \">>>\" to a line closing both at once. This\n@@ -78,14 +94,17 @@\n \n # here-doc -- swallow it to avoid false hits within its body (but keep the\n # command to which it was attached)\n-/<<[ \t]*[-\\\\]*EOF[ \t]*/ {\n-\ts/[ \t]*<<[ \t]*[-\\\\]*EOF//\n-\th\n+/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/ {\n+\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+\ts/[ \t]*<<//\n \t:hereslurp\n \tN\n-\ts/.*\\n//\n-\t/^[ \t]*EOF[ \t]*$/!bhereslurp\n-\tx\n+\t/^<\\([^>]*\\)>.*\\n[ \t]*\\1[ \t]*$/!{\n+\t\ts/\\n.*$//\n+\t\tbhereslurp\n+\t}\n+\ts/^<[^>]*>//\n+\ts/\\n.*$//\n }\n \n # one-liner \"(...) &&\"\n@@ -139,9 +158,7 @@ s/.*\\n//\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n # here-doc -- swallow it\n-/<<[ \t]*[-\\\\]*EOF/bheredoc\n-/<<[ \t]*[-\\\\]*EOT/bheredoc\n-/<<[ \t]*[-\\\\]*INPUT_END/bheredoc\n+/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n # re-visited to drop \"suspect\" marking since final line of those constructs\n@@ -249,23 +266,17 @@ s/\\n//\n bcheckchain\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\n-# the command to which it was attached); take care to handle here-docs nested\n-# within here-docs by only recognizing closing tag matching outer here-doc\n-# opening tag\n+# the command to which it was attached)\n :heredoc\n-/EOF/{ s/[ \t]*<<[ \t]*[-\\\\]*EOF//; s/^/EOF/; }\n-/EOT/{ s/[ \t]*<<[ \t]*[-\\\\]*EOT//; s/^/EOT/; }\n-/INPUT_END/{ s/[ \t]*<<[ \t]*[-\\\\]*INPUT_END//; s/^/INPUT_END/; }\n+s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+s/[ \t]*<<//\n :hereslurpsub\n N\n-/^EOF.*\\n[ \t]*EOF[ \t]*$/bhereclose\n-/^EOT.*\\n[ \t]*EOT[ \t]*$/bhereclose\n-/^INPUT_END.*\\n[ \t]*INPUT_END[ \t]*$/bhereclose\n-bhereslurpsub\n-:hereclose\n-s/^EOF//\n-s/^EOT//\n-s/^INPUT_END//\n+/^<\\([^>]*\\)>.*\\n[ \t]*\\1[ \t]*$/!{\n+\ts/\\n.*$//\n+\tbhereslurpsub\n+}\n+s/^<[^>]*>//\n s/\\n.*$//\n bcheckchain\n \ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nindex 2328fe7753..33bc3cc0b4 100644\n--- a/t/chainlint/here-doc.expect\n+++ b/t/chainlint/here-doc.expect\n@@ -1,3 +1,5 @@\n boodle wobba        gorgo snoot        wafta snurb &&\n \n+cat >foo &&\n+\n horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nindex bd36f6e1d3..8986eefe74 100644\n--- a/t/chainlint/here-doc.test\n+++ b/t/chainlint/here-doc.test\n@@ -7,6 +7,13 @@ quoth the raven,\n nevermore...\n EOF\n \n+# LINT: swallow here-doc with arbitrary tag\n+cat <<-Arbitrary_Tag_42 >foo &&\n+snoz\n+boz\n+woz\n+Arbitrary_Tag_42\n+\n # LINT: swallow here-doc (EOF is last line of test)\n horticulture <<\\EOF\n gomez\ndiff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect\nindex 559301e005..0c9ef1cfc6 100644\n--- a/t/chainlint/nested-here-doc.expect\n+++ b/t/chainlint/nested-here-doc.expect\n@@ -1,3 +1,5 @@\n+cat >foop &&\n+\n (\n \tcat &&\n ?!AMP?!\tcat\ndiff --git a/t/chainlint/nested-here-doc.test b/t/chainlint/nested-here-doc.test\nindex 027e0bb3ff..f35404bf0f 100644\n--- a/t/chainlint/nested-here-doc.test\n+++ b/t/chainlint/nested-here-doc.test\n@@ -1,3 +1,13 @@\n+# LINT: inner \"EOF\" not misintrepreted as closing ARBITRARY here-doc\n+cat <<ARBITRARY >foop &&\n+naddle\n+fub <<EOF\n+\tnozzle\n+\tnoodle\n+EOF\n+formp\n+ARBITRARY\n+\n (\n # LINT: inner \"EOF\" not misintrepreted as closing INPUT_END here-doc\n \tcat <<-\\INPUT_END &&\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nindex 19d5aff233..7c2da63bc7 100644\n--- a/t/chainlint/subshell-here-doc.expect\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -2,4 +2,8 @@\n \techo wobba \t       gorgo snoot \t       wafta snurb &&\n ?!AMP?!\tcat >bip\n \techo >bop\n+>) &&\n+(\n+\tcat >bup &&\n+\tmeep\n >)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nindex 9c3564c247..05139af0b5 100644\n--- a/t/chainlint/subshell-here-doc.test\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -20,4 +20,12 @@\n \twednesday\n \tpugsly\n \tEOF\n+) &&\n+(\n+# LINT: swallow here-doc with arbitrary tag\n+\tcat <<-\\ARBITRARY >bup &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY\n+\tmeep\n )\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355311","messageId":"20180813084739.16134-3-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180813084739.16134-1-sunshine@sunshineco.com","subject":"[PATCH v2 2/6] chainlint: match 'quoted' here-doc tags","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T08:47:35Z","receivedAt":"2018-08-13T08:48:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"A here-doc tag can be quoted ('EOF') or escaped (\\EOF) to suppress\ninterpolation within the body. Although, chainlint recognizes escaped\ntags, it does not know about quoted tags. For completeness, teach it to\nrecognize quoted tags, as well.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                      | 8 ++++----\n t/chainlint/here-doc.expect          | 2 ++\n t/chainlint/here-doc.test            | 7 +++++++\n t/chainlint/subshell-here-doc.expect | 1 +\n t/chainlint/subshell-here-doc.test   | 4 ++++\n 5 files changed, 18 insertions(+), 4 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 2af1a687f8..2901494e8a 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -94,8 +94,8 @@\n \n # here-doc -- swallow it to avoid false hits within its body (but keep the\n # command to which it was attached)\n-/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/ {\n-\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/ {\n+\ts/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n \ts/[ \t]*<<//\n \t:hereslurp\n \tN\n@@ -158,7 +158,7 @@ s/.*\\n//\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n # here-doc -- swallow it\n-/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/bheredoc\n+/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n # re-visited to drop \"suspect\" marking since final line of those constructs\n@@ -268,7 +268,7 @@ bcheckchain\n # found here-doc -- swallow it to avoid false hits within its body (but keep\n # the command to which it was attached)\n :heredoc\n-s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+s/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n s/[ \t]*<<//\n :hereslurpsub\n N\ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nindex 33bc3cc0b4..aff6568716 100644\n--- a/t/chainlint/here-doc.expect\n+++ b/t/chainlint/here-doc.expect\n@@ -2,4 +2,6 @@ boodle wobba        gorgo snoot        wafta snurb &&\n \n cat >foo &&\n \n+cat >bar &&\n+\n horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nindex 8986eefe74..f2bb14b693 100644\n--- a/t/chainlint/here-doc.test\n+++ b/t/chainlint/here-doc.test\n@@ -14,6 +14,13 @@ boz\n woz\n Arbitrary_Tag_42\n \n+# LINT: swallow 'quoted' here-doc\n+cat <<'FUMP' >bar &&\n+snoz\n+boz\n+woz\n+FUMP\n+\n # LINT: swallow here-doc (EOF is last line of test)\n horticulture <<\\EOF\n gomez\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nindex 7c2da63bc7..7663ea7fc4 100644\n--- a/t/chainlint/subshell-here-doc.expect\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -5,5 +5,6 @@\n >) &&\n (\n \tcat >bup &&\n+\tcat >bup2 &&\n \tmeep\n >)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nindex 05139af0b5..b6b5a9b33a 100644\n--- a/t/chainlint/subshell-here-doc.test\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -27,5 +27,9 @@\n \tglink\n \tFIZZ\n \tARBITRARY\n+\tcat <<-'ARBITRARY2' >bup2 &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY2\n \tmeep\n )\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355312","messageId":"20180813084739.16134-4-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180813084739.16134-1-sunshine@sunshineco.com","subject":"[PATCH v2 3/6] chainlint: recognize multi-line $(...) when command cuddled with \"$(\"","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T08:47:36Z","receivedAt":"2018-08-13T08:48:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"For multi-line $(...) expressions nested within subshells, chainlint.sed\nonly recognizes:\n\n    x=$(\n        echo foo &&\n        ...\n\nbut it is not unlikely that test authors may also cuddle the command\nwith the opening \"$(\", so support that style, as well:\n\n    x=$(echo foo &&\n        ...\n\nThe closing \")\" is already correctly recognized when cuddled or not.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                                       |  2 +-\n .../multi-line-nested-command-substitution.expect     | 11 ++++++++++-\n .../multi-line-nested-command-substitution.test       | 11 ++++++++++-\n 3 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 2901494e8a..afb2443a9c 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -216,7 +216,7 @@ s/.*\\n//\n # \"$(...)\" -- command substitution; not closing \")\"\n /\\$([^)][^)]*)[^)]*$/bcheckchain\n # multi-line \"$(...\\n...)\" -- command substitution; treat as nested subshell\n-/\\$([ \t]*$/bnest\n+/\\$([^)]*$/bnest\n # \"=(...)\" -- Bash array assignment; not closing \")\"\n /=(/bcheckchain\n # closing \"...) &&\"\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.expect b/t/chainlint/multi-line-nested-command-substitution.expect\nindex 19c023b1c8..59b6c8b850 100644\n--- a/t/chainlint/multi-line-nested-command-substitution.expect\n+++ b/t/chainlint/multi-line-nested-command-substitution.expect\n@@ -6,4 +6,13 @@\n >>\t) &&\n \techo ok\n >) |\n-sort\n+sort &&\n+(\n+\tbar &&\n+\tx=$(echo bar |\n+\t\tcat\n+>>\t) &&\n+\ty=$(echo baz |\n+>>\t\tfip) &&\n+\techo fail\n+>)\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.test b/t/chainlint/multi-line-nested-command-substitution.test\nindex ca0620ab6b..300058341b 100644\n--- a/t/chainlint/multi-line-nested-command-substitution.test\n+++ b/t/chainlint/multi-line-nested-command-substitution.test\n@@ -6,4 +6,13 @@\n \t) &&\n \techo ok\n ) |\n-sort\n+sort &&\n+(\n+\tbar &&\n+\tx=$(echo bar |\n+\t\tcat\n+\t) &&\n+\ty=$(echo baz |\n+\t\tfip) &&\n+\techo fail\n+)\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355313","messageId":"20180813084739.16134-5-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180813084739.16134-1-sunshine@sunshineco.com","subject":"[PATCH v2 4/6] chainlint: let here-doc and multi-line string commence on same line","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T08:47:37Z","receivedAt":"2018-08-13T08:48:22Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"After swallowing a here-doc, chainlint.sed assumes that no other\nprocessing needs to be done on the line aside from checking for &&-chain\nbreakage; likewise, after folding a multi-line quoted string. However,\nit's conceivable (even if unlikely in practice) that both a here-doc and\na multi-line quoted string might commence on the same line:\n\n    cat <<\\EOF && echo \"foo\n    bar\"\n    data\n    EOF\n\nSupport this case by sending the line (after swallowing and folding)\nthrough the normal processing sequence rather than jumping directly to\nthe check for broken &&-chain.\n\nThis change also allows other somewhat pathological cases to be handled,\nsuch as closing a subshell on the same line starting a here-doc:\n\n    (\n        cat <<-\\INPUT)\n        data\n        INPUT\n\nor, for instance, opening a multi-line $(...) expression on the same\nline starting a here-doc:\n\n    x=$(cat <<-\\END &&\n        data\n        END\n        echo \"x\")\n\namong others.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                                      | 7 ++++---\n t/chainlint/here-doc-close-subshell.expect           | 2 ++\n t/chainlint/here-doc-close-subshell.test             | 5 +++++\n t/chainlint/here-doc-multi-line-command-subst.expect | 5 +++++\n t/chainlint/here-doc-multi-line-command-subst.test   | 9 +++++++++\n t/chainlint/here-doc-multi-line-string.expect        | 4 ++++\n t/chainlint/here-doc-multi-line-string.test          | 8 ++++++++\n 7 files changed, 37 insertions(+), 3 deletions(-)\n create mode 100644 t/chainlint/here-doc-close-subshell.expect\n create mode 100644 t/chainlint/here-doc-close-subshell.test\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.expect\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.test\n create mode 100644 t/chainlint/here-doc-multi-line-string.expect\n create mode 100644 t/chainlint/here-doc-multi-line-string.test\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex afb2443a9c..6661e21f20 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -157,6 +157,7 @@ s/.*\\n//\n /^[^']*'[^']*$/{\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n+:folded\n # here-doc -- swallow it\n /<<[ \t]*[-\\\\']*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n@@ -255,7 +256,7 @@ s/\"//g\n N\n s/\\n//\n /\"/!bdqstring\n-bcheckchain\n+bfolded\n \n # found multi-line single-quoted string '...\\n...' -- slurp until end of string\n :sqstring\n@@ -263,7 +264,7 @@ s/'//g\n N\n s/\\n//\n /'/!bsqstring\n-bcheckchain\n+bfolded\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\n # the command to which it was attached)\n@@ -278,7 +279,7 @@ N\n }\n s/^<[^>]*>//\n s/\\n.*$//\n-bcheckchain\n+bfolded\n \n # found \"case ... in\" -- pass through untouched\n :case\ndiff --git a/t/chainlint/here-doc-close-subshell.expect b/t/chainlint/here-doc-close-subshell.expect\nnew file mode 100644\nindex 0000000000..f011e335e5\n--- /dev/null\n+++ b/t/chainlint/here-doc-close-subshell.expect\n@@ -0,0 +1,2 @@\n+(\n+>\tcat)\ndiff --git a/t/chainlint/here-doc-close-subshell.test b/t/chainlint/here-doc-close-subshell.test\nnew file mode 100644\nindex 0000000000..b857ff5467\n--- /dev/null\n+++ b/t/chainlint/here-doc-close-subshell.test\n@@ -0,0 +1,5 @@\n+(\n+# LINT: line contains here-doc and closes nested subshell\n+\tcat <<-\\INPUT)\n+\tfizz\n+\tINPUT\ndiff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect\nnew file mode 100644\nindex 0000000000..e5fb752d2f\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-command-subst.expect\n@@ -0,0 +1,5 @@\n+(\n+\tx=$(bobble &&\n+?!AMP?!>>\t\twiffle)\n+\techo $x\n+>)\ndiff --git a/t/chainlint/here-doc-multi-line-command-subst.test b/t/chainlint/here-doc-multi-line-command-subst.test\nnew file mode 100644\nindex 0000000000..899bc5de8b\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-command-subst.test\n@@ -0,0 +1,9 @@\n+(\n+# LINT: line contains here-doc and opens multi-line $(...)\n+\tx=$(bobble <<-\\END &&\n+\t\tfossil\n+\t\tvegetable\n+\t\tEND\n+\t\twiffle)\n+\techo $x\n+)\ndiff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect\nnew file mode 100644\nindex 0000000000..1e5b724b9d\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-string.expect\n@@ -0,0 +1,4 @@\n+(\n+?!AMP?!\tcat && echo multi-line\tstring\"\n+\tbap\n+>)\ndiff --git a/t/chainlint/here-doc-multi-line-string.test b/t/chainlint/here-doc-multi-line-string.test\nnew file mode 100644\nindex 0000000000..a53edbcc8d\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-string.test\n@@ -0,0 +1,8 @@\n+(\n+# LINT: line contains here-doc and opens multi-line string\n+\tcat <<-\\TXT && echo \"multi-line\n+\tstring\"\n+\tfizzle\n+\tTXT\n+\tbap\n+)\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355314","messageId":"20180813084739.16134-6-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180813084739.16134-1-sunshine@sunshineco.com","subject":"[PATCH v2 5/6] chainlint: recognize multi-line quoted strings more robustly","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T08:47:38Z","receivedAt":"2018-08-13T08:48:22Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"chainlint.sed recognizes multi-line quoted strings within subshells:\n\n    echo \"abc\n        def\" >out &&\n\nso it can avoid incorrectly classifying lines internal to the string as\nbreaking the &&-chain. To identify the first line of a multi-line\nstring, it checks if the line contains a single quote. However, this is\nfragile and can be easily fooled by a line containing multiple strings:\n\n    echo \"xyz\" \"abc\n        def\" >out &&\n\nMake detection more robust by checking for an odd number of quotes\nrather than only a single one.\n\n(Escaped quotes are not handled, but support may be added later.)\n\nThe original multi-line string recognizer rather cavalierly threw away\nall but the final quote, whereas the new one is careful to retain all\nquotes, so the \"expected\" output of a couple existing chainlint tests is\nupdated to account for this new behavior.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                               | 32 +++++++++++++------\n t/chainlint/here-doc-multi-line-string.expect |  2 +-\n t/chainlint/multi-line-string.expect          | 10 ++++--\n t/chainlint/multi-line-string.test            | 12 +++++++\n 4 files changed, 43 insertions(+), 13 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 6661e21f20..8544df38df 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -151,10 +151,10 @@ s/.*\\n//\n :slurp\n # incomplete line \"...\\\"\n /\\\\$/bincomplete\n-# multi-line quoted string \"...\\n...\"\n-/^[^\"]*\"[^\"]*$/bdqstring\n-# multi-line quoted string '...\\n...' (but not contraction in string \"it's so\")\n-/^[^']*'[^']*$/{\n+# multi-line quoted string \"...\\n...\"?\n+/\"/bdqstring\n+# multi-line quoted string '...\\n...'? (but not contraction in string \"it's\")\n+/'/{\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n :folded\n@@ -250,20 +250,32 @@ N\n s/\\\\\\n//\n bslurp\n \n-# found multi-line double-quoted string \"...\\n...\" -- slurp until end of string\n+# check for multi-line double-quoted string \"...\\n...\" -- fold to one line\n :dqstring\n-s/\"//g\n+# remove all quote pairs\n+s/\"\\([^\"]*\\)\"/@!\\1@!/g\n+# done if no dangling quote\n+/\"/!bdqdone\n+# otherwise, slurp next line and try again\n N\n s/\\n//\n-/\"/!bdqstring\n+bdqstring\n+:dqdone\n+s/@!/\"/g\n bfolded\n \n-# found multi-line single-quoted string '...\\n...' -- slurp until end of string\n+# check for multi-line single-quoted string '...\\n...' -- fold to one line\n :sqstring\n-s/'//g\n+# remove all quote pairs\n+s/'\\([^']*\\)'/@!\\1@!/g\n+# done if no dangling quote\n+/'/!bsqdone\n+# otherwise, slurp next line and try again\n N\n s/\\n//\n-/'/!bsqstring\n+bsqstring\n+:sqdone\n+s/@!/'/g\n bfolded\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\ndiff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect\nindex 1e5b724b9d..32038a070c 100644\n--- a/t/chainlint/here-doc-multi-line-string.expect\n+++ b/t/chainlint/here-doc-multi-line-string.expect\n@@ -1,4 +1,4 @@\n (\n-?!AMP?!\tcat && echo multi-line\tstring\"\n+?!AMP?!\tcat && echo \"multi-line\tstring\"\n \tbap\n >)\ndiff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect\nindex 8334c4cc8e..170cb59993 100644\n--- a/t/chainlint/multi-line-string.expect\n+++ b/t/chainlint/multi-line-string.expect\n@@ -1,9 +1,15 @@\n (\n-\tx=line 1\t\tline 2\t\tline 3\" &&\n-?!AMP?!\ty=line 1\t\tline2'\n+\tx=\"line 1\t\tline 2\t\tline 3\" &&\n+?!AMP?!\ty='line 1\t\tline2'\n \tfoobar\n >) &&\n (\n \techo \"there's nothing to see here\" &&\n \texit\n+>) &&\n+(\n+\techo \"xyz\" \"abc\t\tdef\t\tghi\" &&\n+\techo 'xyz' 'abc\t\tdef\t\tghi' &&\n+\techo 'xyz' \"abc\t\tdef\t\tghi\" &&\n+\tbarfoo\n >)\ndiff --git a/t/chainlint/multi-line-string.test b/t/chainlint/multi-line-string.test\nindex 14cb44d51c..287ab89705 100644\n--- a/t/chainlint/multi-line-string.test\n+++ b/t/chainlint/multi-line-string.test\n@@ -12,4 +12,16 @@\n # LINT: starting multi-line single-quoted string\n \techo \"there's nothing to see here\" &&\n \texit\n+) &&\n+(\n+\techo \"xyz\" \"abc\n+\t\tdef\n+\t\tghi\" &&\n+\techo 'xyz' 'abc\n+\t\tdef\n+\t\tghi' &&\n+\techo 'xyz' \"abc\n+\t\tdef\n+\t\tghi\" &&\n+\tbarfoo\n )\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355315","messageId":"20180813084739.16134-7-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180813084739.16134-1-sunshine@sunshineco.com","subject":"[PATCH v2 6/6] chainlint: add test of pathological case which triggered false positive","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T08:47:39Z","receivedAt":"2018-08-13T08:48:24Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This extract from contrib/subtree/t7900 triggered a false positive due\nto three chainlint limitations:\n\n* recognizing only a \"blessed\" set of here-doc tag names in a subshell\n  (\"EOF\", \"EOT\", \"INPUT_END\"), of which \"TXT\" is not a member\n\n* inability to recognize multi-line $(...) when the first statement of\n  the body is cuddled with the opening \"$(\"\n\n* inability to recognize multiple constructs on a single line, such as\n  opening a multi-line $(...) and starting a here-doc\n\nNow that all of these shortcomings have been addressed, turn this rather\npathological bit of shell coding into a chainlint test case.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/t7900-subtree.expect | 10 ++++++++++\n t/chainlint/t7900-subtree.test   | 22 ++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n create mode 100644 t/chainlint/t7900-subtree.expect\n create mode 100644 t/chainlint/t7900-subtree.test\n\ndiff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect\nnew file mode 100644\nindex 0000000000..c9913429e6\n--- /dev/null\n+++ b/t/chainlint/t7900-subtree.expect\n@@ -0,0 +1,10 @@\n+(\n+\tchks=\"sub1sub2sub3sub4\" &&\n+\tchks_sub=$(cat | sed 's,^,sub dir/,'\n+>>) &&\n+\tchkms=\"main-sub1main-sub2main-sub3main-sub4\" &&\n+\tchkms_sub=$(cat | sed 's,^,sub dir/,'\n+>>) &&\n+\tsubfiles=$(git ls-files) &&\n+\tcheck_equal \"$subfiles\" \"$chkms$chks\"\n+>)\ndiff --git a/t/chainlint/t7900-subtree.test b/t/chainlint/t7900-subtree.test\nnew file mode 100644\nindex 0000000000..277d8358df\n--- /dev/null\n+++ b/t/chainlint/t7900-subtree.test\n@@ -0,0 +1,22 @@\n+(\n+\tchks=\"sub1\n+sub2\n+sub3\n+sub4\" &&\n+\tchks_sub=$(cat <<TXT | sed 's,^,sub dir/,'\n+$chks\n+TXT\n+) &&\n+\tchkms=\"main-sub1\n+main-sub2\n+main-sub3\n+main-sub4\" &&\n+\tchkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'\n+$chkms\n+TXT\n+) &&\n+\n+\tsubfiles=$(git ls-files) &&\n+\tcheck_equal \"$subfiles\" \"$chkms\n+$chks\"\n+)\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355464","messageId":"xmqqwosu3w4k.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180813084739.16134-3-sunshine@sunshineco.com","subject":"Re: [PATCH v2 2/6] chainlint: match 'quoted' here-doc tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-08-13T19:27:23Z","receivedAt":"2018-08-13T19:27:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> A here-doc tag can be quoted ('EOF') or escaped (\\EOF) to suppress\n> interpolation within the body. Although, chainlint recognizes escaped\n> tags, it does not know about quoted tags. For completeness, teach it to\n> recognize quoted tags, as well.\n\nIs this step merely completeness and future-proofing, or do we have\nexisting instances that the linter would be confused without this\npatch?  I am primarily wondering what the reason is that <<\"EOF\" is\nnot looked for while we are at it (the answer could be that we\nhave tests that use <<'EOF' but not <<\"EOF\").\n\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n>  t/chainlint.sed                      | 8 ++++----\n>  t/chainlint/here-doc.expect          | 2 ++\n>  t/chainlint/here-doc.test            | 7 +++++++\n>  t/chainlint/subshell-here-doc.expect | 1 +\n>  t/chainlint/subshell-here-doc.test   | 4 ++++\n>  5 files changed, 18 insertions(+), 4 deletions(-)\n>\n> diff --git a/t/chainlint.sed b/t/chainlint.sed\n> index 2af1a687f8..2901494e8a 100644\n> --- a/t/chainlint.sed\n> +++ b/t/chainlint.sed\n> @@ -94,8 +94,8 @@\n>  \n>  # here-doc -- swallow it to avoid false hits within its body (but keep the\n>  # command to which it was attached)\n> -/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/ {\n> -\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n> +/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/ {\n> +\ts/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n>  \ts/[ \t]*<<//\n>  \t:hereslurp\n>  \tN\n> @@ -158,7 +158,7 @@ s/.*\\n//\n>  \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n>  }\n>  # here-doc -- swallow it\n> -/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/bheredoc\n> +/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/bheredoc\n>  # comment or empty line -- discard since final non-comment, non-empty line\n>  # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n>  # re-visited to drop \"suspect\" marking since final line of those constructs\n> @@ -268,7 +268,7 @@ bcheckchain\n>  # found here-doc -- swallow it to avoid false hits within its body (but keep\n>  # the command to which it was attached)\n>  :heredoc\n> -s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n> +s/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n>  s/[ \t]*<<//\n>  :hereslurpsub\n>  N\n> diff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\n> index 33bc3cc0b4..aff6568716 100644\n> --- a/t/chainlint/here-doc.expect\n> +++ b/t/chainlint/here-doc.expect\n> @@ -2,4 +2,6 @@ boodle wobba        gorgo snoot        wafta snurb &&\n>  \n>  cat >foo &&\n>  \n> +cat >bar &&\n> +\n>  horticulture\n> diff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\n> index 8986eefe74..f2bb14b693 100644\n> --- a/t/chainlint/here-doc.test\n> +++ b/t/chainlint/here-doc.test\n> @@ -14,6 +14,13 @@ boz\n>  woz\n>  Arbitrary_Tag_42\n>  \n> +# LINT: swallow 'quoted' here-doc\n> +cat <<'FUMP' >bar &&\n> +snoz\n> +boz\n> +woz\n> +FUMP\n> +\n>  # LINT: swallow here-doc (EOF is last line of test)\n>  horticulture <<\\EOF\n>  gomez\n> diff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\n> index 7c2da63bc7..7663ea7fc4 100644\n> --- a/t/chainlint/subshell-here-doc.expect\n> +++ b/t/chainlint/subshell-here-doc.expect\n> @@ -5,5 +5,6 @@\n>  >) &&\n>  (\n>  \tcat >bup &&\n> +\tcat >bup2 &&\n>  \tmeep\n>  >)\n> diff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\n> index 05139af0b5..b6b5a9b33a 100644\n> --- a/t/chainlint/subshell-here-doc.test\n> +++ b/t/chainlint/subshell-here-doc.test\n> @@ -27,5 +27,9 @@\n>  \tglink\n>  \tFIZZ\n>  \tARBITRARY\n> +\tcat <<-'ARBITRARY2' >bup2 &&\n> +\tglink\n> +\tFIZZ\n> +\tARBITRARY2\n>  \tmeep\n>  )\n"},{"id":"355469","messageId":"CAPig+cQNXiZ8MhapGGikB0bwO2Cm-YHFRbZice22opWTAEhwgw@mail.gmail.com","threadId":"48778","inReplyTo":"xmqqwosu3w4k.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 2/6] chainlint: match 'quoted' here-doc tags","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-13T20:12:56Z","receivedAt":"2018-08-13T20:13:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Aug 13, 2018 at 3:27 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n>\n> > A here-doc tag can be quoted ('EOF') or escaped (\\EOF) to suppress\n> > interpolation within the body. Although, chainlint recognizes escaped\n> > tags, it does not know about quoted tags. For completeness, teach it to\n> > recognize quoted tags, as well.\n>\n> Is this step merely completeness and future-proofing, or do we have\n> existing instances that the linter would be confused without this\n> patch?\n\nWe do have quite a few instances of single-quoted 'EOF' in the tests\nalready, though none of them confuse the linter, so this change is for\ncompleteness and future-proofing.\n\n> I am primarily wondering what the reason is that <<\"EOF\" is\n> not looked for while we are at it (the answer could be that we\n> have tests that use <<'EOF' but not <<\"EOF\").\n\nIt's an oversight on my part. I've gotten so used to \\EOF (and before\nworking on Git, 'EOF'), that \"EOF\" just slipped my mind[1].\n\nI do think it's worth a re-roll. Thanks for noticing.\n\n[1]: What did not slip my mind is that quoting _any_ part of the tag\nsuppresses interpolation, so 'E'OF, E'O'F, and even E''OF are all\nvalid, however, that's so ugly that I really didn't want to support\nit.\n"},{"id":"355735","messageId":"20180815184552.8418-1-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180813084739.16134-1-sunshine@sunshineco.com","subject":"[PATCH v3 0/6] chainlint: improve robustness against \"unusual\" shell coding","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-15T18:45:46Z","receivedAt":"2018-08-15T18:46:17Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is a re-roll of [1] which improves chainlint's robustness in the\nface of unusual shell coding such as in contrib/subtree/t7900 which\ntriggered a false-positive[2].\n\nChanges since v2:\n\n* recognize \"quoted\" here-doc tag names (in addition to 'quoted'\n  and \\escaped)\n\nInterdiff below.\n\n[1]: https://public-inbox.org/git/20180813084739.16134-1-sunshine@sunshineco.com/\n[2]: https://public-inbox.org/git/20180730181356.GA156463@aiede.svl.corp.google.com/\n\nEric Sunshine (6):\n  chainlint: match arbitrary here-docs tags rather than hard-coded names\n  chainlint: match quoted here-doc tags\n  chainlint: recognize multi-line $(...) when command cuddled with \"$(\"\n  chainlint: let here-doc and multi-line string commence on same line\n  chainlint: recognize multi-line quoted strings more robustly\n  chainlint: add test of pathological case which triggered false\n    positive\n\n t/chainlint.sed                               | 98 ++++++++++++-------\n t/chainlint/here-doc-close-subshell.expect    |  2 +\n t/chainlint/here-doc-close-subshell.test      |  5 +\n .../here-doc-multi-line-command-subst.expect  |  5 +\n .../here-doc-multi-line-command-subst.test    |  9 ++\n t/chainlint/here-doc-multi-line-string.expect |  4 +\n t/chainlint/here-doc-multi-line-string.test   |  8 ++\n t/chainlint/here-doc.expect                   |  6 ++\n t/chainlint/here-doc.test                     | 21 ++++\n ...ti-line-nested-command-substitution.expect | 11 ++-\n ...ulti-line-nested-command-substitution.test | 11 ++-\n t/chainlint/multi-line-string.expect          | 10 +-\n t/chainlint/multi-line-string.test            | 12 +++\n t/chainlint/nested-here-doc.expect            |  2 +\n t/chainlint/nested-here-doc.test              | 10 ++\n t/chainlint/subshell-here-doc.expect          |  6 ++\n t/chainlint/subshell-here-doc.test            | 16 +++\n t/chainlint/t7900-subtree.expect              | 10 ++\n t/chainlint/t7900-subtree.test                | 22 +++++\n 19 files changed, 227 insertions(+), 41 deletions(-)\n create mode 100644 t/chainlint/here-doc-close-subshell.expect\n create mode 100644 t/chainlint/here-doc-close-subshell.test\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.expect\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.test\n create mode 100644 t/chainlint/here-doc-multi-line-string.expect\n create mode 100644 t/chainlint/here-doc-multi-line-string.test\n create mode 100644 t/chainlint/t7900-subtree.expect\n create mode 100644 t/chainlint/t7900-subtree.test\n\nInterdiff against v2:\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 8544df38df..1da58b554b 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -94,8 +94,8 @@\n \n # here-doc -- swallow it to avoid false hits within its body (but keep the\n # command to which it was attached)\n-/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/ {\n-\ts/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n+/<<[ \t]*[-\\\\'\"]*[A-Za-z0-9_]/ {\n+\ts/^\\(.*\\)<<[ \t]*[-\\\\'\"]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)['\"]*/<\\2>\\1<</\n \ts/[ \t]*<<//\n \t:hereslurp\n \tN\n@@ -159,7 +159,7 @@ s/.*\\n//\n }\n :folded\n # here-doc -- swallow it\n-/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/bheredoc\n+/<<[ \t]*[-\\\\'\"]*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n # re-visited to drop \"suspect\" marking since final line of those constructs\n@@ -281,7 +281,7 @@ bfolded\n # found here-doc -- swallow it to avoid false hits within its body (but keep\n # the command to which it was attached)\n :heredoc\n-s/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n+s/^\\(.*\\)<<[ \t]*[-\\\\'\"]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)['\"]*/<\\2>\\1<</\n s/[ \t]*<<//\n :hereslurpsub\n N\ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nindex aff6568716..534b065e38 100644\n--- a/t/chainlint/here-doc.expect\n+++ b/t/chainlint/here-doc.expect\n@@ -4,4 +4,6 @@ cat >foo &&\n \n cat >bar &&\n \n+cat >boo &&\n+\n horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nindex f2bb14b693..ad4ce8afd9 100644\n--- a/t/chainlint/here-doc.test\n+++ b/t/chainlint/here-doc.test\n@@ -21,6 +21,13 @@ boz\n woz\n FUMP\n \n+# LINT: swallow \"quoted\" here-doc\n+cat <<\"zump\" >boo &&\n+snoz\n+boz\n+woz\n+zump\n+\n # LINT: swallow here-doc (EOF is last line of test)\n horticulture <<\\EOF\n gomez\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nindex 7663ea7fc4..74723e7340 100644\n--- a/t/chainlint/subshell-here-doc.expect\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -6,5 +6,6 @@\n (\n \tcat >bup &&\n \tcat >bup2 &&\n+\tcat >bup3 &&\n \tmeep\n >)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nindex b6b5a9b33a..f6b3ba4214 100644\n--- a/t/chainlint/subshell-here-doc.test\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -31,6 +31,9 @@\n \tglink\n \tFIZZ\n \tARBITRARY2\n+\tcat <<-\"ARBITRARY3\" >bup3 &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY3\n \tmeep\n )\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355736","messageId":"20180815184552.8418-2-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180815184552.8418-1-sunshine@sunshineco.com","subject":"[PATCH v3 1/6] chainlint: match arbitrary here-docs tags rather than hard-coded names","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-15T18:45:47Z","receivedAt":"2018-08-15T18:46:18Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"chainlint.sed swallows top-level here-docs to avoid being fooled by\ncontent which might look like start-of-subshell. It likewise swallows\nhere-docs in subshells to avoid marking content lines as breaking the\n&&-chain, and to avoid being fooled by content which might look like\nend-of-subshell, start-of-nested-subshell, or other specially-recognized\nconstructs.\n\nAt the time of implementation, it was believed that it was not possible\nto support arbitrary here-doc tag names since 'sed' provides no way to\nstash the opening tag name in a variable for later comparison against a\nline signaling end-of-here-doc. Consequently, tag names are hard-coded,\nwith \"EOF\" being the only tag recognized at the top-level, and only\n\"EOF\", \"EOT\", and \"INPUT_END\" being recognized within subshells. Also,\nspecial care was taken to avoid being confused by here-docs nested\nwithin other here-docs.\n\nIn practice, this limited number of hard-coded tag names has been \"good\nenough\" for the 13000+ existing Git test, despite many of those tests\nusing tags other than the recognized ones, since the bodies of those\nhere-docs do not contain content which would fool the linter.\nNevertheless, the situation is not ideal since someone writing new\ntests, and choosing a name not in the \"blessed\" set could potentially\ntrigger a false-positive.\n\nTo address this shortcoming, upgrade chainlint.sed to handle arbitrary\nhere-doc tag names, both at the top-level and within subshells.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                      | 57 +++++++++++++++++-----------\n t/chainlint/here-doc.expect          |  2 +\n t/chainlint/here-doc.test            |  7 ++++\n t/chainlint/nested-here-doc.expect   |  2 +\n t/chainlint/nested-here-doc.test     | 10 +++++\n t/chainlint/subshell-here-doc.expect |  4 ++\n t/chainlint/subshell-here-doc.test   |  8 ++++\n 7 files changed, 67 insertions(+), 23 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 5f0882cb38..2af1a687f8 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -61,6 +61,22 @@\n # \"else\", and \"fi\" in if-then-else likewise must not end with \"&&\", thus\n # receives similar treatment.\n #\n+# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a\n+# line such as \"cat <<EOF >out\" is seen, the here-doc tag is moved to the front\n+# of the line enclosed in angle brackets as a sentinel, giving \"<EOF>cat >out\".\n+# As each subsequent line is read, it is appended to the target line and a\n+# (whitespace-loose) back-reference match /^<(.*)>\\n\\1$/ is attempted to see if\n+# the content inside \"<...>\" matches the entirety of the newly-read line. For\n+# instance, if the next line read is \"some data\", when concatenated with the\n+# target line, it becomes \"<EOF>cat >out\\nsome data\", and a match is attempted\n+# to see if \"EOF\" matches \"some data\". Since it doesn't, the next line is\n+# attempted. When a line consisting of only \"EOF\" (and possible whitespace) is\n+# encountered, it is appended to the target line giving \"<EOF>cat >out\\nEOF\",\n+# in which case the \"EOF\" inside \"<...>\" does match the text following the\n+# newline, thus the closing here-doc tag has been found. The closing tag line\n+# and the \"<...>\" prefix on the target line are then discarded, leaving just\n+# the target line \"cat >out\".\n+#\n # To facilitate regression testing (and manual debugging), a \">\" annotation is\n # applied to the line containing \")\" which closes a subshell, \">>\" to a line\n # closing a nested subshell, and \">>>\" to a line closing both at once. This\n@@ -78,14 +94,17 @@\n \n # here-doc -- swallow it to avoid false hits within its body (but keep the\n # command to which it was attached)\n-/<<[ \t]*[-\\\\]*EOF[ \t]*/ {\n-\ts/[ \t]*<<[ \t]*[-\\\\]*EOF//\n-\th\n+/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/ {\n+\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+\ts/[ \t]*<<//\n \t:hereslurp\n \tN\n-\ts/.*\\n//\n-\t/^[ \t]*EOF[ \t]*$/!bhereslurp\n-\tx\n+\t/^<\\([^>]*\\)>.*\\n[ \t]*\\1[ \t]*$/!{\n+\t\ts/\\n.*$//\n+\t\tbhereslurp\n+\t}\n+\ts/^<[^>]*>//\n+\ts/\\n.*$//\n }\n \n # one-liner \"(...) &&\"\n@@ -139,9 +158,7 @@ s/.*\\n//\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n # here-doc -- swallow it\n-/<<[ \t]*[-\\\\]*EOF/bheredoc\n-/<<[ \t]*[-\\\\]*EOT/bheredoc\n-/<<[ \t]*[-\\\\]*INPUT_END/bheredoc\n+/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n # re-visited to drop \"suspect\" marking since final line of those constructs\n@@ -249,23 +266,17 @@ s/\\n//\n bcheckchain\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\n-# the command to which it was attached); take care to handle here-docs nested\n-# within here-docs by only recognizing closing tag matching outer here-doc\n-# opening tag\n+# the command to which it was attached)\n :heredoc\n-/EOF/{ s/[ \t]*<<[ \t]*[-\\\\]*EOF//; s/^/EOF/; }\n-/EOT/{ s/[ \t]*<<[ \t]*[-\\\\]*EOT//; s/^/EOT/; }\n-/INPUT_END/{ s/[ \t]*<<[ \t]*[-\\\\]*INPUT_END//; s/^/INPUT_END/; }\n+s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+s/[ \t]*<<//\n :hereslurpsub\n N\n-/^EOF.*\\n[ \t]*EOF[ \t]*$/bhereclose\n-/^EOT.*\\n[ \t]*EOT[ \t]*$/bhereclose\n-/^INPUT_END.*\\n[ \t]*INPUT_END[ \t]*$/bhereclose\n-bhereslurpsub\n-:hereclose\n-s/^EOF//\n-s/^EOT//\n-s/^INPUT_END//\n+/^<\\([^>]*\\)>.*\\n[ \t]*\\1[ \t]*$/!{\n+\ts/\\n.*$//\n+\tbhereslurpsub\n+}\n+s/^<[^>]*>//\n s/\\n.*$//\n bcheckchain\n \ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nindex 2328fe7753..33bc3cc0b4 100644\n--- a/t/chainlint/here-doc.expect\n+++ b/t/chainlint/here-doc.expect\n@@ -1,3 +1,5 @@\n boodle wobba        gorgo snoot        wafta snurb &&\n \n+cat >foo &&\n+\n horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nindex bd36f6e1d3..8986eefe74 100644\n--- a/t/chainlint/here-doc.test\n+++ b/t/chainlint/here-doc.test\n@@ -7,6 +7,13 @@ quoth the raven,\n nevermore...\n EOF\n \n+# LINT: swallow here-doc with arbitrary tag\n+cat <<-Arbitrary_Tag_42 >foo &&\n+snoz\n+boz\n+woz\n+Arbitrary_Tag_42\n+\n # LINT: swallow here-doc (EOF is last line of test)\n horticulture <<\\EOF\n gomez\ndiff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect\nindex 559301e005..0c9ef1cfc6 100644\n--- a/t/chainlint/nested-here-doc.expect\n+++ b/t/chainlint/nested-here-doc.expect\n@@ -1,3 +1,5 @@\n+cat >foop &&\n+\n (\n \tcat &&\n ?!AMP?!\tcat\ndiff --git a/t/chainlint/nested-here-doc.test b/t/chainlint/nested-here-doc.test\nindex 027e0bb3ff..f35404bf0f 100644\n--- a/t/chainlint/nested-here-doc.test\n+++ b/t/chainlint/nested-here-doc.test\n@@ -1,3 +1,13 @@\n+# LINT: inner \"EOF\" not misintrepreted as closing ARBITRARY here-doc\n+cat <<ARBITRARY >foop &&\n+naddle\n+fub <<EOF\n+\tnozzle\n+\tnoodle\n+EOF\n+formp\n+ARBITRARY\n+\n (\n # LINT: inner \"EOF\" not misintrepreted as closing INPUT_END here-doc\n \tcat <<-\\INPUT_END &&\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nindex 19d5aff233..7c2da63bc7 100644\n--- a/t/chainlint/subshell-here-doc.expect\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -2,4 +2,8 @@\n \techo wobba \t       gorgo snoot \t       wafta snurb &&\n ?!AMP?!\tcat >bip\n \techo >bop\n+>) &&\n+(\n+\tcat >bup &&\n+\tmeep\n >)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nindex 9c3564c247..05139af0b5 100644\n--- a/t/chainlint/subshell-here-doc.test\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -20,4 +20,12 @@\n \twednesday\n \tpugsly\n \tEOF\n+) &&\n+(\n+# LINT: swallow here-doc with arbitrary tag\n+\tcat <<-\\ARBITRARY >bup &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY\n+\tmeep\n )\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355738","messageId":"20180815184552.8418-4-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180815184552.8418-1-sunshine@sunshineco.com","subject":"[PATCH v3 3/6] chainlint: recognize multi-line $(...) when command cuddled with \"$(\"","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-15T18:45:49Z","receivedAt":"2018-08-15T18:46:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"For multi-line $(...) expressions nested within subshells, chainlint.sed\nonly recognizes:\n\n    x=$(\n        echo foo &&\n        ...\n\nbut it is not unlikely that test authors may also cuddle the command\nwith the opening \"$(\", so support that style, as well:\n\n    x=$(echo foo &&\n        ...\n\nThe closing \")\" is already correctly recognized when cuddled or not.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                                       |  2 +-\n .../multi-line-nested-command-substitution.expect     | 11 ++++++++++-\n .../multi-line-nested-command-substitution.test       | 11 ++++++++++-\n 3 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 07c624fe09..a21c4b4d71 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -216,7 +216,7 @@ s/.*\\n//\n # \"$(...)\" -- command substitution; not closing \")\"\n /\\$([^)][^)]*)[^)]*$/bcheckchain\n # multi-line \"$(...\\n...)\" -- command substitution; treat as nested subshell\n-/\\$([ \t]*$/bnest\n+/\\$([^)]*$/bnest\n # \"=(...)\" -- Bash array assignment; not closing \")\"\n /=(/bcheckchain\n # closing \"...) &&\"\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.expect b/t/chainlint/multi-line-nested-command-substitution.expect\nindex 19c023b1c8..59b6c8b850 100644\n--- a/t/chainlint/multi-line-nested-command-substitution.expect\n+++ b/t/chainlint/multi-line-nested-command-substitution.expect\n@@ -6,4 +6,13 @@\n >>\t) &&\n \techo ok\n >) |\n-sort\n+sort &&\n+(\n+\tbar &&\n+\tx=$(echo bar |\n+\t\tcat\n+>>\t) &&\n+\ty=$(echo baz |\n+>>\t\tfip) &&\n+\techo fail\n+>)\ndiff --git a/t/chainlint/multi-line-nested-command-substitution.test b/t/chainlint/multi-line-nested-command-substitution.test\nindex ca0620ab6b..300058341b 100644\n--- a/t/chainlint/multi-line-nested-command-substitution.test\n+++ b/t/chainlint/multi-line-nested-command-substitution.test\n@@ -6,4 +6,13 @@\n \t) &&\n \techo ok\n ) |\n-sort\n+sort &&\n+(\n+\tbar &&\n+\tx=$(echo bar |\n+\t\tcat\n+\t) &&\n+\ty=$(echo baz |\n+\t\tfip) &&\n+\techo fail\n+)\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355737","messageId":"20180815184552.8418-5-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180815184552.8418-1-sunshine@sunshineco.com","subject":"[PATCH v3 4/6] chainlint: let here-doc and multi-line string commence on same line","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-15T18:45:50Z","receivedAt":"2018-08-15T18:46:20Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"After swallowing a here-doc, chainlint.sed assumes that no other\nprocessing needs to be done on the line aside from checking for &&-chain\nbreakage; likewise, after folding a multi-line quoted string. However,\nit's conceivable (even if unlikely in practice) that both a here-doc and\na multi-line quoted string might commence on the same line:\n\n    cat <<\\EOF && echo \"foo\n    bar\"\n    data\n    EOF\n\nSupport this case by sending the line (after swallowing and folding)\nthrough the normal processing sequence rather than jumping directly to\nthe check for broken &&-chain.\n\nThis change also allows other somewhat pathological cases to be handled,\nsuch as closing a subshell on the same line starting a here-doc:\n\n    (\n        cat <<-\\INPUT)\n        data\n        INPUT\n\nor, for instance, opening a multi-line $(...) expression on the same\nline starting a here-doc:\n\n    x=$(cat <<-\\END &&\n        data\n        END\n        echo \"x\")\n\namong others.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                                      | 7 ++++---\n t/chainlint/here-doc-close-subshell.expect           | 2 ++\n t/chainlint/here-doc-close-subshell.test             | 5 +++++\n t/chainlint/here-doc-multi-line-command-subst.expect | 5 +++++\n t/chainlint/here-doc-multi-line-command-subst.test   | 9 +++++++++\n t/chainlint/here-doc-multi-line-string.expect        | 4 ++++\n t/chainlint/here-doc-multi-line-string.test          | 8 ++++++++\n 7 files changed, 37 insertions(+), 3 deletions(-)\n create mode 100644 t/chainlint/here-doc-close-subshell.expect\n create mode 100644 t/chainlint/here-doc-close-subshell.test\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.expect\n create mode 100644 t/chainlint/here-doc-multi-line-command-subst.test\n create mode 100644 t/chainlint/here-doc-multi-line-string.expect\n create mode 100644 t/chainlint/here-doc-multi-line-string.test\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex a21c4b4d71..41cb6ef865 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -157,6 +157,7 @@ s/.*\\n//\n /^[^']*'[^']*$/{\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n+:folded\n # here-doc -- swallow it\n /<<[ \t]*[-\\\\'\"]*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n@@ -255,7 +256,7 @@ s/\"//g\n N\n s/\\n//\n /\"/!bdqstring\n-bcheckchain\n+bfolded\n \n # found multi-line single-quoted string '...\\n...' -- slurp until end of string\n :sqstring\n@@ -263,7 +264,7 @@ s/'//g\n N\n s/\\n//\n /'/!bsqstring\n-bcheckchain\n+bfolded\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\n # the command to which it was attached)\n@@ -278,7 +279,7 @@ N\n }\n s/^<[^>]*>//\n s/\\n.*$//\n-bcheckchain\n+bfolded\n \n # found \"case ... in\" -- pass through untouched\n :case\ndiff --git a/t/chainlint/here-doc-close-subshell.expect b/t/chainlint/here-doc-close-subshell.expect\nnew file mode 100644\nindex 0000000000..f011e335e5\n--- /dev/null\n+++ b/t/chainlint/here-doc-close-subshell.expect\n@@ -0,0 +1,2 @@\n+(\n+>\tcat)\ndiff --git a/t/chainlint/here-doc-close-subshell.test b/t/chainlint/here-doc-close-subshell.test\nnew file mode 100644\nindex 0000000000..b857ff5467\n--- /dev/null\n+++ b/t/chainlint/here-doc-close-subshell.test\n@@ -0,0 +1,5 @@\n+(\n+# LINT: line contains here-doc and closes nested subshell\n+\tcat <<-\\INPUT)\n+\tfizz\n+\tINPUT\ndiff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect\nnew file mode 100644\nindex 0000000000..e5fb752d2f\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-command-subst.expect\n@@ -0,0 +1,5 @@\n+(\n+\tx=$(bobble &&\n+?!AMP?!>>\t\twiffle)\n+\techo $x\n+>)\ndiff --git a/t/chainlint/here-doc-multi-line-command-subst.test b/t/chainlint/here-doc-multi-line-command-subst.test\nnew file mode 100644\nindex 0000000000..899bc5de8b\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-command-subst.test\n@@ -0,0 +1,9 @@\n+(\n+# LINT: line contains here-doc and opens multi-line $(...)\n+\tx=$(bobble <<-\\END &&\n+\t\tfossil\n+\t\tvegetable\n+\t\tEND\n+\t\twiffle)\n+\techo $x\n+)\ndiff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect\nnew file mode 100644\nindex 0000000000..1e5b724b9d\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-string.expect\n@@ -0,0 +1,4 @@\n+(\n+?!AMP?!\tcat && echo multi-line\tstring\"\n+\tbap\n+>)\ndiff --git a/t/chainlint/here-doc-multi-line-string.test b/t/chainlint/here-doc-multi-line-string.test\nnew file mode 100644\nindex 0000000000..a53edbcc8d\n--- /dev/null\n+++ b/t/chainlint/here-doc-multi-line-string.test\n@@ -0,0 +1,8 @@\n+(\n+# LINT: line contains here-doc and opens multi-line string\n+\tcat <<-\\TXT && echo \"multi-line\n+\tstring\"\n+\tfizzle\n+\tTXT\n+\tbap\n+)\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355740","messageId":"20180815184552.8418-6-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180815184552.8418-1-sunshine@sunshineco.com","subject":"[PATCH v3 5/6] chainlint: recognize multi-line quoted strings more robustly","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-15T18:45:51Z","receivedAt":"2018-08-15T18:46:21Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"chainlint.sed recognizes multi-line quoted strings within subshells:\n\n    echo \"abc\n        def\" >out &&\n\nso it can avoid incorrectly classifying lines internal to the string as\nbreaking the &&-chain. To identify the first line of a multi-line\nstring, it checks if the line contains a single quote. However, this is\nfragile and can be easily fooled by a line containing multiple strings:\n\n    echo \"xyz\" \"abc\n        def\" >out &&\n\nMake detection more robust by checking for an odd number of quotes\nrather than only a single one.\n\n(Escaped quotes are not handled, but support may be added later.)\n\nThe original multi-line string recognizer rather cavalierly threw away\nall but the final quote, whereas the new one is careful to retain all\nquotes, so the \"expected\" output of a couple existing chainlint tests is\nupdated to account for this new behavior.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                               | 32 +++++++++++++------\n t/chainlint/here-doc-multi-line-string.expect |  2 +-\n t/chainlint/multi-line-string.expect          | 10 ++++--\n t/chainlint/multi-line-string.test            | 12 +++++++\n 4 files changed, 43 insertions(+), 13 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 41cb6ef865..1da58b554b 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -151,10 +151,10 @@ s/.*\\n//\n :slurp\n # incomplete line \"...\\\"\n /\\\\$/bincomplete\n-# multi-line quoted string \"...\\n...\"\n-/^[^\"]*\"[^\"]*$/bdqstring\n-# multi-line quoted string '...\\n...' (but not contraction in string \"it's so\")\n-/^[^']*'[^']*$/{\n+# multi-line quoted string \"...\\n...\"?\n+/\"/bdqstring\n+# multi-line quoted string '...\\n...'? (but not contraction in string \"it's\")\n+/'/{\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n :folded\n@@ -250,20 +250,32 @@ N\n s/\\\\\\n//\n bslurp\n \n-# found multi-line double-quoted string \"...\\n...\" -- slurp until end of string\n+# check for multi-line double-quoted string \"...\\n...\" -- fold to one line\n :dqstring\n-s/\"//g\n+# remove all quote pairs\n+s/\"\\([^\"]*\\)\"/@!\\1@!/g\n+# done if no dangling quote\n+/\"/!bdqdone\n+# otherwise, slurp next line and try again\n N\n s/\\n//\n-/\"/!bdqstring\n+bdqstring\n+:dqdone\n+s/@!/\"/g\n bfolded\n \n-# found multi-line single-quoted string '...\\n...' -- slurp until end of string\n+# check for multi-line single-quoted string '...\\n...' -- fold to one line\n :sqstring\n-s/'//g\n+# remove all quote pairs\n+s/'\\([^']*\\)'/@!\\1@!/g\n+# done if no dangling quote\n+/'/!bsqdone\n+# otherwise, slurp next line and try again\n N\n s/\\n//\n-/'/!bsqstring\n+bsqstring\n+:sqdone\n+s/@!/'/g\n bfolded\n \n # found here-doc -- swallow it to avoid false hits within its body (but keep\ndiff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect\nindex 1e5b724b9d..32038a070c 100644\n--- a/t/chainlint/here-doc-multi-line-string.expect\n+++ b/t/chainlint/here-doc-multi-line-string.expect\n@@ -1,4 +1,4 @@\n (\n-?!AMP?!\tcat && echo multi-line\tstring\"\n+?!AMP?!\tcat && echo \"multi-line\tstring\"\n \tbap\n >)\ndiff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect\nindex 8334c4cc8e..170cb59993 100644\n--- a/t/chainlint/multi-line-string.expect\n+++ b/t/chainlint/multi-line-string.expect\n@@ -1,9 +1,15 @@\n (\n-\tx=line 1\t\tline 2\t\tline 3\" &&\n-?!AMP?!\ty=line 1\t\tline2'\n+\tx=\"line 1\t\tline 2\t\tline 3\" &&\n+?!AMP?!\ty='line 1\t\tline2'\n \tfoobar\n >) &&\n (\n \techo \"there's nothing to see here\" &&\n \texit\n+>) &&\n+(\n+\techo \"xyz\" \"abc\t\tdef\t\tghi\" &&\n+\techo 'xyz' 'abc\t\tdef\t\tghi' &&\n+\techo 'xyz' \"abc\t\tdef\t\tghi\" &&\n+\tbarfoo\n >)\ndiff --git a/t/chainlint/multi-line-string.test b/t/chainlint/multi-line-string.test\nindex 14cb44d51c..287ab89705 100644\n--- a/t/chainlint/multi-line-string.test\n+++ b/t/chainlint/multi-line-string.test\n@@ -12,4 +12,16 @@\n # LINT: starting multi-line single-quoted string\n \techo \"there's nothing to see here\" &&\n \texit\n+) &&\n+(\n+\techo \"xyz\" \"abc\n+\t\tdef\n+\t\tghi\" &&\n+\techo 'xyz' 'abc\n+\t\tdef\n+\t\tghi' &&\n+\techo 'xyz' \"abc\n+\t\tdef\n+\t\tghi\" &&\n+\tbarfoo\n )\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355739","messageId":"20180815184552.8418-7-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180815184552.8418-1-sunshine@sunshineco.com","subject":"[PATCH v3 6/6] chainlint: add test of pathological case which triggered false positive","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-15T18:45:52Z","receivedAt":"2018-08-15T18:46:22Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This extract from contrib/subtree/t7900 triggered a false positive due\nto three chainlint limitations:\n\n* recognizing only a \"blessed\" set of here-doc tag names in a subshell\n  (\"EOF\", \"EOT\", \"INPUT_END\"), of which \"TXT\" is not a member\n\n* inability to recognize multi-line $(...) when the first statement of\n  the body is cuddled with the opening \"$(\"\n\n* inability to recognize multiple constructs on a single line, such as\n  opening a multi-line $(...) and starting a here-doc\n\nNow that all of these shortcomings have been addressed, turn this rather\npathological bit of shell coding into a chainlint test case.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint/t7900-subtree.expect | 10 ++++++++++\n t/chainlint/t7900-subtree.test   | 22 ++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n create mode 100644 t/chainlint/t7900-subtree.expect\n create mode 100644 t/chainlint/t7900-subtree.test\n\ndiff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect\nnew file mode 100644\nindex 0000000000..c9913429e6\n--- /dev/null\n+++ b/t/chainlint/t7900-subtree.expect\n@@ -0,0 +1,10 @@\n+(\n+\tchks=\"sub1sub2sub3sub4\" &&\n+\tchks_sub=$(cat | sed 's,^,sub dir/,'\n+>>) &&\n+\tchkms=\"main-sub1main-sub2main-sub3main-sub4\" &&\n+\tchkms_sub=$(cat | sed 's,^,sub dir/,'\n+>>) &&\n+\tsubfiles=$(git ls-files) &&\n+\tcheck_equal \"$subfiles\" \"$chkms$chks\"\n+>)\ndiff --git a/t/chainlint/t7900-subtree.test b/t/chainlint/t7900-subtree.test\nnew file mode 100644\nindex 0000000000..277d8358df\n--- /dev/null\n+++ b/t/chainlint/t7900-subtree.test\n@@ -0,0 +1,22 @@\n+(\n+\tchks=\"sub1\n+sub2\n+sub3\n+sub4\" &&\n+\tchks_sub=$(cat <<TXT | sed 's,^,sub dir/,'\n+$chks\n+TXT\n+) &&\n+\tchkms=\"main-sub1\n+main-sub2\n+main-sub3\n+main-sub4\" &&\n+\tchkms_sub=$(cat <<TXT | sed 's,^,sub dir/,'\n+$chkms\n+TXT\n+) &&\n+\n+\tsubfiles=$(git ls-files) &&\n+\tcheck_equal \"$subfiles\" \"$chkms\n+$chks\"\n+)\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"355741","messageId":"20180815184552.8418-3-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180815184552.8418-1-sunshine@sunshineco.com","subject":"[PATCH v3 2/6] chainlint: match quoted here-doc tags","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-15T18:45:48Z","receivedAt":"2018-08-15T18:46:24Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"A here-doc tag can be quoted ('EOF'/\"EOF\") or escaped (\\EOF) to suppress\ninterpolation within the body. Although, chainlint recognizes escaped\ntags, it does not know about quoted tags. For completeness, teach it to\nrecognize quoted tags, as well.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n t/chainlint.sed                      |  8 ++++----\n t/chainlint/here-doc.expect          |  4 ++++\n t/chainlint/here-doc.test            | 14 ++++++++++++++\n t/chainlint/subshell-here-doc.expect |  2 ++\n t/chainlint/subshell-here-doc.test   |  8 ++++++++\n 5 files changed, 32 insertions(+), 4 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 2af1a687f8..07c624fe09 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -94,8 +94,8 @@\n \n # here-doc -- swallow it to avoid false hits within its body (but keep the\n # command to which it was attached)\n-/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/ {\n-\ts/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+/<<[ \t]*[-\\\\'\"]*[A-Za-z0-9_]/ {\n+\ts/^\\(.*\\)<<[ \t]*[-\\\\'\"]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)['\"]*/<\\2>\\1<</\n \ts/[ \t]*<<//\n \t:hereslurp\n \tN\n@@ -158,7 +158,7 @@ s/.*\\n//\n \t/\"[^'\"]*'[^'\"]*\"/!bsqstring\n }\n # here-doc -- swallow it\n-/<<[ \t]*[-\\\\]*[A-Za-z0-9_]/bheredoc\n+/<<[ \t]*[-\\\\'\"]*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n # re-visited to drop \"suspect\" marking since final line of those constructs\n@@ -268,7 +268,7 @@ bcheckchain\n # found here-doc -- swallow it to avoid false hits within its body (but keep\n # the command to which it was attached)\n :heredoc\n-s/^\\(.*\\)<<[ \t]*[-\\\\]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)/<\\2>\\1<</\n+s/^\\(.*\\)<<[ \t]*[-\\\\'\"]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)['\"]*/<\\2>\\1<</\n s/[ \t]*<<//\n :hereslurpsub\n N\ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nindex 33bc3cc0b4..534b065e38 100644\n--- a/t/chainlint/here-doc.expect\n+++ b/t/chainlint/here-doc.expect\n@@ -2,4 +2,8 @@ boodle wobba        gorgo snoot        wafta snurb &&\n \n cat >foo &&\n \n+cat >bar &&\n+\n+cat >boo &&\n+\n horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nindex 8986eefe74..ad4ce8afd9 100644\n--- a/t/chainlint/here-doc.test\n+++ b/t/chainlint/here-doc.test\n@@ -14,6 +14,20 @@ boz\n woz\n Arbitrary_Tag_42\n \n+# LINT: swallow 'quoted' here-doc\n+cat <<'FUMP' >bar &&\n+snoz\n+boz\n+woz\n+FUMP\n+\n+# LINT: swallow \"quoted\" here-doc\n+cat <<\"zump\" >boo &&\n+snoz\n+boz\n+woz\n+zump\n+\n # LINT: swallow here-doc (EOF is last line of test)\n horticulture <<\\EOF\n gomez\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nindex 7c2da63bc7..74723e7340 100644\n--- a/t/chainlint/subshell-here-doc.expect\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -5,5 +5,7 @@\n >) &&\n (\n \tcat >bup &&\n+\tcat >bup2 &&\n+\tcat >bup3 &&\n \tmeep\n >)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nindex 05139af0b5..f6b3ba4214 100644\n--- a/t/chainlint/subshell-here-doc.test\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -27,5 +27,13 @@\n \tglink\n \tFIZZ\n \tARBITRARY\n+\tcat <<-'ARBITRARY2' >bup2 &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY2\n+\tcat <<-\"ARBITRARY3\" >bup3 &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY3\n \tmeep\n )\n-- \n2.18.0.267.gbc8be36ecb\n\n"},{"id":"356388","messageId":"871sapezba.fsf@evledraar.gmail.com","threadId":"48778","inReplyTo":"20180711064642.6933-2-sunshine@sunshineco.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-08-23T18:02:33Z","receivedAt":"2018-08-23T18:02:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Jul 11 2018, Eric Sunshine wrote:\n\nFound in some 2.19 testing on AIX:\n\n> +# here-doc -- swallow it to avoid false hits within its body (but keep the\n> +# command to which it was attached)\n> +/<<[ \t]*[-\\\\]*EOF[ \t]*/ {\n> +\ts/[ \t]*<<[ \t]*[-\\\\]*EOF//\n> +\th\n> +\t:hereslurp\n> +\tN\n> +\ts/.*\\n//\n> +\t/^[ \t]*EOF[ \t]*$/!bhereslurp\n> +\tx\n> +}\n\nAIX sed doesn't like this, and will yell:\n\n    :hereslurp is greater than eight characters\n\nThis on top fixes it:\n\n    diff --git a/t/chainlint.sed b/t/chainlint.sed\n    index 8544df38df..2333705b27 100644\n    --- a/t/chainlint.sed\n    +++ b/t/chainlint.sed\n    @@ -100 +100 @@\n    -       :hereslurp\n    +       :hered\n    @@ -104 +104 @@\n    -               bhereslurp\n    +               bhered\n    @@ -286 +286 @@ s/[     ]*<<//\n    -:hereslurpsub\n    +:heredsub\n    @@ -290 +290 @@ N\n    -       bhereslurpsub\n    +       bheredsub\n\n> +:subshell\n> +# bare \"(\" line?\n> +/^[ \t]*([\t]*$/ {\n> +\t# stash for later printing\n> +\th\n> +\tbnextline\n> +}\n> +# \"(...\" line -- split off and stash \"(\", then process \"...\" as its own line\n\nAIX sed doesn't like this either, and prints:\n\n    sed:    # stash for later printing is not a recognized function.\n\nI have no idea what the fix is for that one.\n"},{"id":"356391","messageId":"CAPig+cTTbU5HFMKgNyrxTp3+kcK46-Fn=4ZH6zDt1oQChAc3KA@mail.gmail.com","threadId":"48778","inReplyTo":"871sapezba.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 01/10] t/test-lib: teach --chain-lint to detect broken &&-chains in subshells","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-23T18:27:57Z","receivedAt":"2018-08-23T18:28:10Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Aug 23, 2018 at 2:02 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n> Found in some 2.19 testing on AIX:\n\nThanks for reporting these issues.\n\n> > +     /^[     ]*EOF[  ]*$/!bhereslurp\n>\n> AIX sed doesn't like this, and will yell:\n>     :hereslurp is greater than eight characters\n> This on top fixes it:\n\nFix make sense, and checking POSIX[1] , I see that it says that\nbehavior is unspecified for labels longer than 8 bytes.\n\n[1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html\n\n> > +# bare \"(\" line?\n> > +/^[  ]*([    ]*$/ {\n> > +     # stash for later printing\n>\n> AIX sed doesn't like this either, and prints:\n>     sed:    # stash for later printing is not a recognized function.\n> I have no idea what the fix is for that one.\n\nThat's the only indented comment in the entire script, so it's almost\ncertainly that. How about we move the indented comment up to the\ncomment for the enclosing block, so it reads:\n\n    # bare \"(\" line? -- stash for later printing\n    /^[  ]*([    ]*$/ {\n        h\n        bnextline\n    }\n\nI could prepare such a patch, although perhaps it would be better for\nyou to do so since you are in a position to test it (and because you\ndiscovered the problems)?\n"},{"id":"356823","messageId":"20180829094532.41497-1-sunshine@sunshineco.com","threadId":"48778","inReplyTo":"20180815184552.8418-1-sunshine@sunshineco.com","subject":"[PATCH] chainlint: match \"quoted\" here-doc tags","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-08-29T09:45:32Z","receivedAt":"2018-08-29T09:46:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"A here-doc tag can be quoted ('EOF'/\"EOF\") or escaped (\\EOF) to suppress\ninterpolation within the body. chainlint recognizes single-quoted and\nescaped tags, but does not know about double-quoted tags. For\ncompleteness, teach it to recognize double-quoted tags, as well.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n\nThis is an extract from v3 of es/chain-lint-more[1] which never got\npicked up. Instead, v2 of that series[2] got merged to 'next' and later\n'master'. The only change between v2 and v3 was to make 'chainlint' also\nrecognize double-quoted here-doc tags. This solo patch makes that change\nincrementally atop v2.\n\nThis is built on 'master'. With the default \"recursive\" strategy, it\nmerges cleanly with Ævar's ab/portable[3] changes to 'chainlint' for AIX\ncompatibility, currently in 'next'.\n\n[1]: https://public-inbox.org/git/20180815184552.8418-1-sunshine@sunshineco.com/\n[2]: https://public-inbox.org/git/20180813084739.16134-1-sunshine@sunshineco.com/\n[3]: https://public-inbox.org/git/20180824152016.20286-1-avarab@gmail.com/\n\n t/chainlint.sed                      | 8 ++++----\n t/chainlint/here-doc.expect          | 2 ++\n t/chainlint/here-doc.test            | 7 +++++++\n t/chainlint/subshell-here-doc.expect | 1 +\n t/chainlint/subshell-here-doc.test   | 4 ++++\n 5 files changed, 18 insertions(+), 4 deletions(-)\n\ndiff --git a/t/chainlint.sed b/t/chainlint.sed\nindex 8544df38df..1da58b554b 100644\n--- a/t/chainlint.sed\n+++ b/t/chainlint.sed\n@@ -94,8 +94,8 @@\n \n # here-doc -- swallow it to avoid false hits within its body (but keep the\n # command to which it was attached)\n-/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/ {\n-\ts/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n+/<<[ \t]*[-\\\\'\"]*[A-Za-z0-9_]/ {\n+\ts/^\\(.*\\)<<[ \t]*[-\\\\'\"]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)['\"]*/<\\2>\\1<</\n \ts/[ \t]*<<//\n \t:hereslurp\n \tN\n@@ -159,7 +159,7 @@ s/.*\\n//\n }\n :folded\n # here-doc -- swallow it\n-/<<[ \t]*[-\\\\']*[A-Za-z0-9_]/bheredoc\n+/<<[ \t]*[-\\\\'\"]*[A-Za-z0-9_]/bheredoc\n # comment or empty line -- discard since final non-comment, non-empty line\n # before closing \")\", \"done\", \"elsif\", \"else\", or \"fi\" will need to be\n # re-visited to drop \"suspect\" marking since final line of those constructs\n@@ -281,7 +281,7 @@ bfolded\n # found here-doc -- swallow it to avoid false hits within its body (but keep\n # the command to which it was attached)\n :heredoc\n-s/^\\(.*\\)<<[ \t]*[-\\\\']*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)'*/<\\2>\\1<</\n+s/^\\(.*\\)<<[ \t]*[-\\\\'\"]*\\([A-Za-z0-9_][A-Za-z0-9_]*\\)['\"]*/<\\2>\\1<</\n s/[ \t]*<<//\n :hereslurpsub\n N\ndiff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect\nindex aff6568716..534b065e38 100644\n--- a/t/chainlint/here-doc.expect\n+++ b/t/chainlint/here-doc.expect\n@@ -4,4 +4,6 @@ cat >foo &&\n \n cat >bar &&\n \n+cat >boo &&\n+\n horticulture\ndiff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test\nindex f2bb14b693..ad4ce8afd9 100644\n--- a/t/chainlint/here-doc.test\n+++ b/t/chainlint/here-doc.test\n@@ -21,6 +21,13 @@ boz\n woz\n FUMP\n \n+# LINT: swallow \"quoted\" here-doc\n+cat <<\"zump\" >boo &&\n+snoz\n+boz\n+woz\n+zump\n+\n # LINT: swallow here-doc (EOF is last line of test)\n horticulture <<\\EOF\n gomez\ndiff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect\nindex 7663ea7fc4..74723e7340 100644\n--- a/t/chainlint/subshell-here-doc.expect\n+++ b/t/chainlint/subshell-here-doc.expect\n@@ -6,5 +6,6 @@\n (\n \tcat >bup &&\n \tcat >bup2 &&\n+\tcat >bup3 &&\n \tmeep\n >)\ndiff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test\nindex b6b5a9b33a..f6b3ba4214 100644\n--- a/t/chainlint/subshell-here-doc.test\n+++ b/t/chainlint/subshell-here-doc.test\n@@ -31,5 +31,9 @@\n \tglink\n \tFIZZ\n \tARBITRARY2\n+\tcat <<-\"ARBITRARY3\" >bup3 &&\n+\tglink\n+\tFIZZ\n+\tARBITRARY3\n \tmeep\n )\n-- \n2.19.0.rc1.352.gb1634b371d\n\n"},{"id":"356906","messageId":"xmqqzhx494vk.fsf@gitster-ct.c.googlers.com","threadId":"48778","inReplyTo":"20180829094532.41497-1-sunshine@sunshineco.com","subject":"Re: [PATCH] chainlint: match \"quoted\" here-doc tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-08-29T17:57:21Z","receivedAt":"2018-08-29T22:37:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> This is an extract from v3 of es/chain-lint-more[1] which never got\n> picked up. Instead, v2 of that series[2] got merged to 'next' and later\n> 'master'. The only change between v2 and v3 was to make 'chainlint' also\n> recognize double-quoted here-doc tags. This solo patch makes that change\n> incrementally atop v2.\n\nThanks.\n"}]}