summaryrefslogtreecommitdiff
path: root/t/chainlint
diff options
context:
space:
mode:
Diffstat (limited to 't/chainlint')
-rw-r--r--t/chainlint/arithmetic-expansion.expect9
-rw-r--r--t/chainlint/arithmetic-expansion.test13
-rw-r--r--t/chainlint/bash-array.expect10
-rw-r--r--t/chainlint/bash-array.test14
-rw-r--r--t/chainlint/blank-line-before-esac.expect18
-rw-r--r--t/chainlint/blank-line-before-esac.test21
-rw-r--r--t/chainlint/blank-line.expect8
-rw-r--r--t/chainlint/blank-line.test12
-rw-r--r--t/chainlint/block-comment.expect8
-rw-r--r--t/chainlint/block-comment.test10
-rw-r--r--t/chainlint/block.expect23
-rw-r--r--t/chainlint/block.test29
-rw-r--r--t/chainlint/broken-chain.expect6
-rw-r--r--t/chainlint/broken-chain.test10
-rw-r--r--t/chainlint/case-comment.expect11
-rw-r--r--t/chainlint/case-comment.test13
-rw-r--r--t/chainlint/case.expect19
-rw-r--r--t/chainlint/case.test25
-rw-r--r--t/chainlint/chain-break-background.expect9
-rw-r--r--t/chainlint/chain-break-background.test12
-rw-r--r--t/chainlint/chain-break-continue.expect12
-rw-r--r--t/chainlint/chain-break-continue.test15
-rw-r--r--t/chainlint/chain-break-false.expect9
-rw-r--r--t/chainlint/chain-break-false.test12
-rw-r--r--t/chainlint/chain-break-return-exit.expect19
-rw-r--r--t/chainlint/chain-break-return-exit.test25
-rw-r--r--t/chainlint/chain-break-status.expect9
-rw-r--r--t/chainlint/chain-break-status.test13
-rw-r--r--t/chainlint/chained-block.expect9
-rw-r--r--t/chainlint/chained-block.test13
-rw-r--r--t/chainlint/chained-subshell.expect10
-rw-r--r--t/chainlint/chained-subshell.test15
-rw-r--r--t/chainlint/close-nested-and-parent-together.expect3
-rw-r--r--t/chainlint/close-nested-and-parent-together.test5
-rw-r--r--t/chainlint/close-subshell.expect26
-rw-r--r--t/chainlint/close-subshell.test29
-rw-r--r--t/chainlint/command-substitution-subsubshell.expect2
-rw-r--r--t/chainlint/command-substitution-subsubshell.test5
-rw-r--r--t/chainlint/command-substitution.expect9
-rw-r--r--t/chainlint/command-substitution.test13
-rw-r--r--t/chainlint/comment.expect8
-rw-r--r--t/chainlint/comment.test13
-rw-r--r--t/chainlint/complex-if-in-cuddled-loop.expect9
-rw-r--r--t/chainlint/complex-if-in-cuddled-loop.test13
-rw-r--r--t/chainlint/cuddled-if-then-else.expect6
-rw-r--r--t/chainlint/cuddled-if-then-else.test9
-rw-r--r--t/chainlint/cuddled-loop.expect4
-rw-r--r--t/chainlint/cuddled-loop.test9
-rw-r--r--t/chainlint/cuddled.expect17
-rw-r--r--t/chainlint/cuddled.test24
-rw-r--r--t/chainlint/double-here-doc.expect12
-rw-r--r--t/chainlint/double-here-doc.test14
-rw-r--r--t/chainlint/dqstring-line-splice.expect5
-rw-r--r--t/chainlint/dqstring-line-splice.test9
-rw-r--r--t/chainlint/dqstring-no-interpolate.expect12
-rw-r--r--t/chainlint/dqstring-no-interpolate.test17
-rw-r--r--t/chainlint/empty-here-doc.expect4
-rw-r--r--t/chainlint/empty-here-doc.test7
-rw-r--r--t/chainlint/exclamation.expect4
-rw-r--r--t/chainlint/exclamation.test10
-rw-r--r--t/chainlint/exit-loop.expect24
-rw-r--r--t/chainlint/exit-loop.test29
-rw-r--r--t/chainlint/exit-subshell.expect5
-rw-r--r--t/chainlint/exit-subshell.test8
-rw-r--r--t/chainlint/for-loop-abbreviated.expect5
-rw-r--r--t/chainlint/for-loop-abbreviated.test8
-rw-r--r--t/chainlint/for-loop.expect14
-rw-r--r--t/chainlint/for-loop.test21
-rw-r--r--t/chainlint/function.expect11
-rw-r--r--t/chainlint/function.test15
-rw-r--r--t/chainlint/here-doc-body-indent.expect2
-rw-r--r--t/chainlint/here-doc-body-indent.test4
-rw-r--r--t/chainlint/here-doc-body-pathological.expect7
-rw-r--r--t/chainlint/here-doc-body-pathological.test9
-rw-r--r--t/chainlint/here-doc-body.expect7
-rw-r--r--t/chainlint/here-doc-body.test9
-rw-r--r--t/chainlint/here-doc-close-subshell.expect4
-rw-r--r--t/chainlint/here-doc-close-subshell.test7
-rw-r--r--t/chainlint/here-doc-double.expect2
-rw-r--r--t/chainlint/here-doc-double.test10
-rw-r--r--t/chainlint/here-doc-indent-operator.expect11
-rw-r--r--t/chainlint/here-doc-indent-operator.test15
-rw-r--r--t/chainlint/here-doc-multi-line-command-subst.expect8
-rw-r--r--t/chainlint/here-doc-multi-line-command-subst.test11
-rw-r--r--t/chainlint/here-doc-multi-line-string.expect7
-rw-r--r--t/chainlint/here-doc-multi-line-string.test10
-rw-r--r--t/chainlint/here-doc.expect25
-rw-r--r--t/chainlint/here-doc.test32
-rw-r--r--t/chainlint/if-condition-split.expect7
-rw-r--r--t/chainlint/if-condition-split.test10
-rw-r--r--t/chainlint/if-in-loop.expect12
-rw-r--r--t/chainlint/if-in-loop.test17
-rw-r--r--t/chainlint/if-then-else.expect22
-rw-r--r--t/chainlint/if-then-else.test31
-rw-r--r--t/chainlint/incomplete-line.expect10
-rw-r--r--t/chainlint/incomplete-line.test14
-rw-r--r--t/chainlint/inline-comment.expect8
-rw-r--r--t/chainlint/inline-comment.test14
-rw-r--r--t/chainlint/loop-detect-failure.expect15
-rw-r--r--t/chainlint/loop-detect-failure.test19
-rw-r--r--t/chainlint/loop-detect-status.expect18
-rw-r--r--t/chainlint/loop-detect-status.test21
-rw-r--r--t/chainlint/loop-in-if.expect12
-rw-r--r--t/chainlint/loop-in-if.test17
-rw-r--r--t/chainlint/loop-upstream-pipe.expect10
-rw-r--r--t/chainlint/loop-upstream-pipe.test13
-rw-r--r--t/chainlint/multi-line-nested-command-substitution.expect18
-rw-r--r--t/chainlint/multi-line-nested-command-substitution.test20
-rw-r--r--t/chainlint/multi-line-string.expect14
-rw-r--r--t/chainlint/multi-line-string.test17
-rw-r--r--t/chainlint/negated-one-liner.expect5
-rw-r--r--t/chainlint/negated-one-liner.test9
-rw-r--r--t/chainlint/nested-cuddled-subshell.expect25
-rw-r--r--t/chainlint/nested-cuddled-subshell.test33
-rw-r--r--t/chainlint/nested-here-doc.expect30
-rw-r--r--t/chainlint/nested-here-doc.test35
-rw-r--r--t/chainlint/nested-loop-detect-failure.expect31
-rw-r--r--t/chainlint/nested-loop-detect-failure.test37
-rw-r--r--t/chainlint/nested-subshell-comment.expect11
-rw-r--r--t/chainlint/nested-subshell-comment.test15
-rw-r--r--t/chainlint/nested-subshell.expect13
-rw-r--r--t/chainlint/nested-subshell.test15
-rw-r--r--t/chainlint/not-heredoc.expect14
-rw-r--r--t/chainlint/not-heredoc.test18
-rw-r--r--t/chainlint/one-liner-for-loop.expect9
-rw-r--r--t/chainlint/one-liner-for-loop.test12
-rw-r--r--t/chainlint/one-liner.expect9
-rw-r--r--t/chainlint/one-liner.test14
-rw-r--r--t/chainlint/p4-filespec.expect4
-rw-r--r--t/chainlint/p4-filespec.test7
-rw-r--r--t/chainlint/pipe.expect10
-rw-r--r--t/chainlint/pipe.test14
-rw-r--r--t/chainlint/return-loop.expect5
-rw-r--r--t/chainlint/return-loop.test8
-rw-r--r--t/chainlint/semicolon.expect19
-rw-r--r--t/chainlint/semicolon.test27
-rw-r--r--t/chainlint/sqstring-in-sqstring.expect4
-rw-r--r--t/chainlint/sqstring-in-sqstring.test7
-rw-r--r--t/chainlint/subshell-here-doc.expect30
-rw-r--r--t/chainlint/subshell-here-doc.test37
-rw-r--r--t/chainlint/subshell-one-liner.expect19
-rw-r--r--t/chainlint/subshell-one-liner.test26
-rw-r--r--t/chainlint/t7900-subtree.expect22
-rw-r--r--t/chainlint/t7900-subtree.test24
-rw-r--r--t/chainlint/token-pasting.expect27
-rw-r--r--t/chainlint/token-pasting.test34
-rw-r--r--t/chainlint/unclosed-here-doc-indent.expect4
-rw-r--r--t/chainlint/unclosed-here-doc-indent.test6
-rw-r--r--t/chainlint/unclosed-here-doc.expect7
-rw-r--r--t/chainlint/unclosed-here-doc.test9
-rw-r--r--t/chainlint/while-loop.expect14
-rw-r--r--t/chainlint/while-loop.test21
152 files changed, 2118 insertions, 0 deletions
diff --git a/t/chainlint/arithmetic-expansion.expect b/t/chainlint/arithmetic-expansion.expect
new file mode 100644
index 0000000000..5677e16cad
--- /dev/null
+++ b/t/chainlint/arithmetic-expansion.expect
@@ -0,0 +1,9 @@
+2 (
+3 foo &&
+4 bar=$((42 + 1)) &&
+5 baz
+6 ) &&
+7 (
+8 bar=$((42 + 1)) ?!LINT: missing '&&'?!
+9 baz
+10 )
diff --git a/t/chainlint/arithmetic-expansion.test b/t/chainlint/arithmetic-expansion.test
new file mode 100644
index 0000000000..7b4c5c9a41
--- /dev/null
+++ b/t/chainlint/arithmetic-expansion.test
@@ -0,0 +1,13 @@
+test_expect_success 'arithmetic-expansion' '
+(
+ foo &&
+# LINT: closing ")" of $((...)) not misinterpreted as subshell-closing ")"
+ bar=$((42 + 1)) &&
+ baz
+) &&
+(
+# LINT: missing "&&" on $((...))
+ bar=$((42 + 1))
+ baz
+)
+'
diff --git a/t/chainlint/bash-array.expect b/t/chainlint/bash-array.expect
new file mode 100644
index 0000000000..435dc8bdc8
--- /dev/null
+++ b/t/chainlint/bash-array.expect
@@ -0,0 +1,10 @@
+2 (
+3 foo &&
+4 bar=(gumbo stumbo wumbo) &&
+5 baz
+6 ) &&
+7 (
+8 foo &&
+9 bar=${#bar[@]} &&
+10 baz
+11 )
diff --git a/t/chainlint/bash-array.test b/t/chainlint/bash-array.test
new file mode 100644
index 0000000000..4ca977d299
--- /dev/null
+++ b/t/chainlint/bash-array.test
@@ -0,0 +1,14 @@
+test_expect_success 'bash-array' '
+(
+ foo &&
+# LINT: ")" in Bash array assignment not misinterpreted as subshell-closing ")"
+ bar=(gumbo stumbo wumbo) &&
+ baz
+) &&
+(
+ foo &&
+# LINT: Bash array length operator not misinterpreted as comment
+ bar=${#bar[@]} &&
+ baz
+)
+'
diff --git a/t/chainlint/blank-line-before-esac.expect b/t/chainlint/blank-line-before-esac.expect
new file mode 100644
index 0000000000..b88ba919eb
--- /dev/null
+++ b/t/chainlint/blank-line-before-esac.expect
@@ -0,0 +1,18 @@
+2 test_done () {
+3 case "$test_failure" in
+4 0)
+5 test_at_end_hook_
+6
+7 exit 0 ;;
+8
+9 *)
+10 if test $test_external_has_tap -eq 0
+11 then
+12 say_color error "# failed $test_failure among $msg"
+13 say "1..$test_count"
+14 fi
+15
+16 exit 1 ;;
+17
+18 esac
+19 }
diff --git a/t/chainlint/blank-line-before-esac.test b/t/chainlint/blank-line-before-esac.test
new file mode 100644
index 0000000000..51f02ea0c5
--- /dev/null
+++ b/t/chainlint/blank-line-before-esac.test
@@ -0,0 +1,21 @@
+test_expect_success 'blank-line-before-esac' '
+# LINT: blank line before "esac"
+test_done () {
+ case "$test_failure" in
+ 0)
+ test_at_end_hook_
+
+ exit 0 ;;
+
+ *)
+ if test $test_external_has_tap -eq 0
+ then
+ say_color error "# failed $test_failure among $msg"
+ say "1..$test_count"
+ fi
+
+ exit 1 ;;
+
+ esac
+}
+'
diff --git a/t/chainlint/blank-line.expect b/t/chainlint/blank-line.expect
new file mode 100644
index 0000000000..6ae39dd174
--- /dev/null
+++ b/t/chainlint/blank-line.expect
@@ -0,0 +1,8 @@
+2 (
+3
+4 nothing &&
+5
+6 something
+7
+8
+9 )
diff --git a/t/chainlint/blank-line.test b/t/chainlint/blank-line.test
new file mode 100644
index 0000000000..6f29a491de
--- /dev/null
+++ b/t/chainlint/blank-line.test
@@ -0,0 +1,12 @@
+test_expect_success 'blank-line' '
+(
+
+ nothing &&
+
+ something
+# LINT: ignore blank lines since final _statement_ before subshell end is
+# LINT: significant to "&&"-check, not final _line_ (which might be blank)
+
+
+)
+'
diff --git a/t/chainlint/block-comment.expect b/t/chainlint/block-comment.expect
new file mode 100644
index 0000000000..7926936c18
--- /dev/null
+++ b/t/chainlint/block-comment.expect
@@ -0,0 +1,8 @@
+2 (
+3 {
+4 # show a
+5 echo a &&
+6 # show b
+7 echo b
+8 }
+9 )
diff --git a/t/chainlint/block-comment.test b/t/chainlint/block-comment.test
new file mode 100644
index 0000000000..934ef4113a
--- /dev/null
+++ b/t/chainlint/block-comment.test
@@ -0,0 +1,10 @@
+test_expect_success 'block-comment' '
+(
+ {
+ # show a
+ echo a &&
+ # show b
+ echo b
+ }
+)
+'
diff --git a/t/chainlint/block.expect b/t/chainlint/block.expect
new file mode 100644
index 0000000000..3d3f854c0d
--- /dev/null
+++ b/t/chainlint/block.expect
@@ -0,0 +1,23 @@
+2 (
+3 foo &&
+4 {
+5 echo a ?!LINT: missing '&&'?!
+6 echo b
+7 } &&
+8 bar &&
+9 {
+10 echo c
+11 } ?!LINT: missing '&&'?!
+12 baz
+13 ) &&
+14
+15 {
+16 echo a; ?!LINT: missing '&&'?! echo b
+17 } &&
+18 { echo a; ?!LINT: missing '&&'?! echo b; } &&
+19
+20 {
+21 echo "${var}9" &&
+22 echo "done"
+23 } &&
+24 finis
diff --git a/t/chainlint/block.test b/t/chainlint/block.test
new file mode 100644
index 0000000000..a1b6b4dd32
--- /dev/null
+++ b/t/chainlint/block.test
@@ -0,0 +1,29 @@
+test_expect_success 'block' '
+(
+# LINT: missing "&&" after first "echo"
+ foo &&
+ {
+ echo a
+ echo b
+ } &&
+ bar &&
+# LINT: missing "&&" at closing "}"
+ {
+ echo c
+ }
+ baz
+) &&
+
+# LINT: ";" not allowed in place of "&&"
+{
+ echo a; echo b
+} &&
+{ echo a; echo b; } &&
+
+# LINT: "}" inside string not mistaken as end of block
+{
+ echo "${var}9" &&
+ echo "done"
+} &&
+finis
+'
diff --git a/t/chainlint/broken-chain.expect b/t/chainlint/broken-chain.expect
new file mode 100644
index 0000000000..b7b1ce8509
--- /dev/null
+++ b/t/chainlint/broken-chain.expect
@@ -0,0 +1,6 @@
+2 (
+3 foo &&
+4 bar ?!LINT: missing '&&'?!
+5 baz &&
+6 wop
+7 )
diff --git a/t/chainlint/broken-chain.test b/t/chainlint/broken-chain.test
new file mode 100644
index 0000000000..1966499ef9
--- /dev/null
+++ b/t/chainlint/broken-chain.test
@@ -0,0 +1,10 @@
+test_expect_success 'broken-chain' '
+(
+ foo &&
+# LINT: missing "&&" from "bar"
+ bar
+ baz &&
+# LINT: final statement before closing ")" legitimately lacks "&&"
+ wop
+)
+'
diff --git a/t/chainlint/case-comment.expect b/t/chainlint/case-comment.expect
new file mode 100644
index 0000000000..2442dd5f25
--- /dev/null
+++ b/t/chainlint/case-comment.expect
@@ -0,0 +1,11 @@
+2 (
+3 case "$x" in
+4 # found foo
+5 x) foo ;;
+6 # found other
+7 *)
+8 # treat it as bar
+9 bar
+10 ;;
+11 esac
+12 )
diff --git a/t/chainlint/case-comment.test b/t/chainlint/case-comment.test
new file mode 100644
index 0000000000..3f31ae9010
--- /dev/null
+++ b/t/chainlint/case-comment.test
@@ -0,0 +1,13 @@
+test_expect_success 'case-comment' '
+(
+ case "$x" in
+ # found foo
+ x) foo ;;
+ # found other
+ *)
+ # treat it as bar
+ bar
+ ;;
+ esac
+)
+'
diff --git a/t/chainlint/case.expect b/t/chainlint/case.expect
new file mode 100644
index 0000000000..0a3b09e470
--- /dev/null
+++ b/t/chainlint/case.expect
@@ -0,0 +1,19 @@
+2 (
+3 case "$x" in
+4 x) foo ;;
+5 *) bar ;;
+6 esac &&
+7 foobar
+8 ) &&
+9 (
+10 case "$x" in
+11 x) foo ;;
+12 *) bar ;;
+13 esac ?!LINT: missing '&&'?!
+14 foobar
+15 ) &&
+16 (
+17 case "$x" in 1) true;; esac &&
+18 case "$y" in 2) false;; esac ?!LINT: missing '&&'?!
+19 foobar
+20 )
diff --git a/t/chainlint/case.test b/t/chainlint/case.test
new file mode 100644
index 0000000000..bea21fee4f
--- /dev/null
+++ b/t/chainlint/case.test
@@ -0,0 +1,25 @@
+test_expect_success 'case' '
+(
+# LINT: "...)" arms in "case" not misinterpreted as subshell-closing ")"
+ case "$x" in
+ x) foo ;;
+ *) bar ;;
+ esac &&
+ foobar
+) &&
+(
+# LINT: missing "&&" on "esac"
+ case "$x" in
+ x) foo ;;
+ *) bar ;;
+ esac
+ foobar
+) &&
+(
+# LINT: "...)" arm in one-liner "case" not misinterpreted as closing ")"
+ case "$x" in 1) true;; esac &&
+# LINT: same but missing "&&"
+ case "$y" in 2) false;; esac
+ foobar
+)
+'
diff --git a/t/chainlint/chain-break-background.expect b/t/chainlint/chain-break-background.expect
new file mode 100644
index 0000000000..d06deadae7
--- /dev/null
+++ b/t/chainlint/chain-break-background.expect
@@ -0,0 +1,9 @@
+2 JGIT_DAEMON_PID= &&
+3 git init --bare empty.git &&
+4 >empty.git/git-daemon-export-ok &&
+5 mkfifo jgit_daemon_output &&
+6 {
+7 jgit daemon --port="$JGIT_DAEMON_PORT" . >jgit_daemon_output &
+8 JGIT_DAEMON_PID=$!
+9 } &&
+10 test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git
diff --git a/t/chainlint/chain-break-background.test b/t/chainlint/chain-break-background.test
new file mode 100644
index 0000000000..c68e1b04d5
--- /dev/null
+++ b/t/chainlint/chain-break-background.test
@@ -0,0 +1,12 @@
+test_expect_success 'chain-break-background' '
+JGIT_DAEMON_PID= &&
+git init --bare empty.git &&
+>empty.git/git-daemon-export-ok &&
+mkfifo jgit_daemon_output &&
+{
+# LINT: exit status of "&" is always 0 so &&-chaining immaterial
+ jgit daemon --port="$JGIT_DAEMON_PORT" . >jgit_daemon_output &
+ JGIT_DAEMON_PID=$!
+} &&
+test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git
+'
diff --git a/t/chainlint/chain-break-continue.expect b/t/chainlint/chain-break-continue.expect
new file mode 100644
index 0000000000..4bb60aae25
--- /dev/null
+++ b/t/chainlint/chain-break-continue.expect
@@ -0,0 +1,12 @@
+2 git ls-tree --name-only -r refs/notes/many_notes |
+3 while read path
+4 do
+5 test "$path" = "foobar/non-note.txt" && continue
+6 test "$path" = "deadbeef" && continue
+7 test "$path" = "de/adbeef" && continue
+8
+9 if test $(expr length "$path") -ne $hexsz
+10 then
+11 return 1
+12 fi
+13 done
diff --git a/t/chainlint/chain-break-continue.test b/t/chainlint/chain-break-continue.test
new file mode 100644
index 0000000000..de8119b204
--- /dev/null
+++ b/t/chainlint/chain-break-continue.test
@@ -0,0 +1,15 @@
+test_expect_success 'chain-break-continue' '
+git ls-tree --name-only -r refs/notes/many_notes |
+while read path
+do
+# LINT: broken &&-chain okay if explicit "continue"
+ test "$path" = "foobar/non-note.txt" && continue
+ test "$path" = "deadbeef" && continue
+ test "$path" = "de/adbeef" && continue
+
+ if test $(expr length "$path") -ne $hexsz
+ then
+ return 1
+ fi
+done
+'
diff --git a/t/chainlint/chain-break-false.expect b/t/chainlint/chain-break-false.expect
new file mode 100644
index 0000000000..f6a0a301e9
--- /dev/null
+++ b/t/chainlint/chain-break-false.expect
@@ -0,0 +1,9 @@
+2 if condition not satisified
+3 then
+4 echo it did not work...
+5 echo failed!
+6 false
+7 else
+8 echo it went okay ?!LINT: missing '&&'?!
+9 congratulate user
+10 fi
diff --git a/t/chainlint/chain-break-false.test b/t/chainlint/chain-break-false.test
new file mode 100644
index 0000000000..f78ad911fc
--- /dev/null
+++ b/t/chainlint/chain-break-false.test
@@ -0,0 +1,12 @@
+test_expect_success 'chain-break-false' '
+# LINT: broken &&-chain okay if explicit "false" signals failure
+if condition not satisified
+then
+ echo it did not work...
+ echo failed!
+ false
+else
+ echo it went okay
+ congratulate user
+fi
+'
diff --git a/t/chainlint/chain-break-return-exit.expect b/t/chainlint/chain-break-return-exit.expect
new file mode 100644
index 0000000000..ba0ec51aa0
--- /dev/null
+++ b/t/chainlint/chain-break-return-exit.expect
@@ -0,0 +1,19 @@
+2 case "$(git ls-files)" in
+3 one) echo pass one ;;
+4 *) echo bad one; return 1 ;;
+5 esac &&
+6 (
+7 case "$(git ls-files)" in
+8 two) echo pass two ;;
+9 *) echo bad two; exit 1 ;;
+10 esac
+11 ) &&
+12 case "$(git ls-files)" in
+13 dir/two"$LF"one) echo pass both ;;
+14 *) echo bad; return 1 ;;
+15 esac &&
+16
+17 for i in 1 2 3 4 ; do
+18 git checkout main -b $i || return $?
+19 test_commit $i $i $i tag$i || return $?
+20 done
diff --git a/t/chainlint/chain-break-return-exit.test b/t/chainlint/chain-break-return-exit.test
new file mode 100644
index 0000000000..b6f519bb4d
--- /dev/null
+++ b/t/chainlint/chain-break-return-exit.test
@@ -0,0 +1,25 @@
+test_expect_success 'chain-break-return-exit' '
+case "$(git ls-files)" in
+one) echo pass one ;;
+# LINT: broken &&-chain okay if explicit "return 1" signals failuire
+*) echo bad one; return 1 ;;
+esac &&
+(
+ case "$(git ls-files)" in
+ two) echo pass two ;;
+# LINT: broken &&-chain okay if explicit "exit 1" signals failuire
+ *) echo bad two; exit 1 ;;
+ esac
+) &&
+case "$(git ls-files)" in
+dir/two"$LF"one) echo pass both ;;
+# LINT: broken &&-chain okay if explicit "return 1" signals failuire
+*) echo bad; return 1 ;;
+esac &&
+
+for i in 1 2 3 4 ; do
+# LINT: broken &&-chain okay if explicit "return $?" signals failure
+ git checkout main -b $i || return $?
+ test_commit $i $i $i tag$i || return $?
+done
+'
diff --git a/t/chainlint/chain-break-status.expect b/t/chainlint/chain-break-status.expect
new file mode 100644
index 0000000000..23c0caa7d8
--- /dev/null
+++ b/t/chainlint/chain-break-status.expect
@@ -0,0 +1,9 @@
+2 OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) &&
+3 test_match_signal 13 "$OUT" &&
+4
+5 { test-tool sigchain >actual; ret=$?; } &&
+6 {
+7 test_match_signal 15 "$ret" ||
+8 test "$ret" = 3
+9 } &&
+10 test_cmp expect actual
diff --git a/t/chainlint/chain-break-status.test b/t/chainlint/chain-break-status.test
new file mode 100644
index 0000000000..d9fee190d9
--- /dev/null
+++ b/t/chainlint/chain-break-status.test
@@ -0,0 +1,13 @@
+test_expect_success 'chain-break-status' '
+# LINT: broken &&-chain okay if next command handles "$?" explicitly
+OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) &&
+test_match_signal 13 "$OUT" &&
+
+# LINT: broken &&-chain okay if next command handles "$?" explicitly
+{ test-tool sigchain >actual; ret=$?; } &&
+{
+ test_match_signal 15 "$ret" ||
+ test "$ret" = 3
+} &&
+test_cmp expect actual
+'
diff --git a/t/chainlint/chained-block.expect b/t/chainlint/chained-block.expect
new file mode 100644
index 0000000000..f2501bba90
--- /dev/null
+++ b/t/chainlint/chained-block.expect
@@ -0,0 +1,9 @@
+2 echo nobody home && {
+3 test the doohicky ?!LINT: missing '&&'?!
+4 right now
+5 } &&
+6
+7 GIT_EXTERNAL_DIFF=echo git diff | {
+8 read path oldfile oldhex oldmode newfile newhex newmode &&
+9 test "z$oh" = "z$oldhex"
+10 }
diff --git a/t/chainlint/chained-block.test b/t/chainlint/chained-block.test
new file mode 100644
index 0000000000..71ef1d0b7f
--- /dev/null
+++ b/t/chainlint/chained-block.test
@@ -0,0 +1,13 @@
+test_expect_success 'chained-block' '
+# LINT: start of block chained to preceding command
+echo nobody home && {
+ test the doohicky
+ right now
+} &&
+
+# LINT: preceding command pipes to block on same line
+GIT_EXTERNAL_DIFF=echo git diff | {
+ read path oldfile oldhex oldmode newfile newhex newmode &&
+ test "z$oh" = "z$oldhex"
+}
+'
diff --git a/t/chainlint/chained-subshell.expect b/t/chainlint/chained-subshell.expect
new file mode 100644
index 0000000000..93fb1a6578
--- /dev/null
+++ b/t/chainlint/chained-subshell.expect
@@ -0,0 +1,10 @@
+2 mkdir sub && (
+3 cd sub &&
+4 foo the bar ?!LINT: missing '&&'?!
+5 nuff said
+6 ) &&
+7
+8 cut "-d " -f actual | (read s1 s2 s3 &&
+9 test -f $s1 ?!LINT: missing '&&'?!
+10 test $(cat $s2) = tree2path1 &&
+11 test $(cat $s3) = tree3path1)
diff --git a/t/chainlint/chained-subshell.test b/t/chainlint/chained-subshell.test
new file mode 100644
index 0000000000..1f11f65398
--- /dev/null
+++ b/t/chainlint/chained-subshell.test
@@ -0,0 +1,15 @@
+test_expect_success 'chained-subshell' '
+# LINT: start of subshell chained to preceding command
+mkdir sub && (
+ cd sub &&
+ foo the bar
+ nuff said
+) &&
+
+# LINT: preceding command pipes to subshell on same line
+cut "-d " -f actual | (read s1 s2 s3 &&
+test -f $s1
+test $(cat $s2) = tree2path1 &&
+# LINT: closing subshell ")" correctly detected on same line as "$(...)"
+test $(cat $s3) = tree3path1)
+'
diff --git a/t/chainlint/close-nested-and-parent-together.expect b/t/chainlint/close-nested-and-parent-together.expect
new file mode 100644
index 0000000000..4167e54a59
--- /dev/null
+++ b/t/chainlint/close-nested-and-parent-together.expect
@@ -0,0 +1,3 @@
+2 (cd foo &&
+3 (bar &&
+4 baz))
diff --git a/t/chainlint/close-nested-and-parent-together.test b/t/chainlint/close-nested-and-parent-together.test
new file mode 100644
index 0000000000..56b28b186b
--- /dev/null
+++ b/t/chainlint/close-nested-and-parent-together.test
@@ -0,0 +1,5 @@
+test_expect_success 'close-nested-and-parent-together' '
+(cd foo &&
+ (bar &&
+ baz))
+'
diff --git a/t/chainlint/close-subshell.expect b/t/chainlint/close-subshell.expect
new file mode 100644
index 0000000000..a272cfe72e
--- /dev/null
+++ b/t/chainlint/close-subshell.expect
@@ -0,0 +1,26 @@
+2 (
+3 foo
+4 ) &&
+5 (
+6 bar
+7 ) >out &&
+8 (
+9 baz
+10 ) 2>err &&
+11 (
+12 boo
+13 ) <input &&
+14 (
+15 bip
+16 ) | wuzzle &&
+17 (
+18 bop
+19 ) | fazz \
+20 fozz &&
+21 (
+22 bup
+23 ) |
+24 fuzzle &&
+25 (
+26 yop
+27 )
diff --git a/t/chainlint/close-subshell.test b/t/chainlint/close-subshell.test
new file mode 100644
index 0000000000..b99f80569d
--- /dev/null
+++ b/t/chainlint/close-subshell.test
@@ -0,0 +1,29 @@
+test_expect_success 'close-subshell' '
+# LINT: closing ")" with various decorations ("&&", ">", "|", etc.)
+(
+ foo
+) &&
+(
+ bar
+) >out &&
+(
+ baz
+) 2>err &&
+(
+ boo
+) <input &&
+(
+ bip
+) | wuzzle &&
+(
+ bop
+) | fazz \
+ fozz &&
+(
+ bup
+) |
+fuzzle &&
+(
+ yop
+)
+'
diff --git a/t/chainlint/command-substitution-subsubshell.expect b/t/chainlint/command-substitution-subsubshell.expect
new file mode 100644
index 0000000000..f2a9312dc8
--- /dev/null
+++ b/t/chainlint/command-substitution-subsubshell.expect
@@ -0,0 +1,2 @@
+2 OUT=$( ((large_git 1>&3) | :) 3>&1 ) &&
+3 test_match_signal 13 "$OUT"
diff --git a/t/chainlint/command-substitution-subsubshell.test b/t/chainlint/command-substitution-subsubshell.test
new file mode 100644
index 0000000000..4ea772d60a
--- /dev/null
+++ b/t/chainlint/command-substitution-subsubshell.test
@@ -0,0 +1,5 @@
+test_expect_success 'command-substitution-subsubshell' '
+# LINT: subshell nested in subshell nested in command substitution
+OUT=$( ((large_git 1>&3) | :) 3>&1 ) &&
+test_match_signal 13 "$OUT"
+'
diff --git a/t/chainlint/command-substitution.expect b/t/chainlint/command-substitution.expect
new file mode 100644
index 0000000000..73809fd585
--- /dev/null
+++ b/t/chainlint/command-substitution.expect
@@ -0,0 +1,9 @@
+2 (
+3 foo &&
+4 bar=$(gobble) &&
+5 baz
+6 ) &&
+7 (
+8 bar=$(gobble blocks) ?!LINT: missing '&&'?!
+9 baz
+10 )
diff --git a/t/chainlint/command-substitution.test b/t/chainlint/command-substitution.test
new file mode 100644
index 0000000000..494d671e80
--- /dev/null
+++ b/t/chainlint/command-substitution.test
@@ -0,0 +1,13 @@
+test_expect_success 'command-substitution' '
+(
+ foo &&
+# LINT: closing ")" of $(...) not misinterpreted as subshell-closing ")"
+ bar=$(gobble) &&
+ baz
+) &&
+(
+# LINT: missing "&&" on $(...)
+ bar=$(gobble blocks)
+ baz
+)
+'
diff --git a/t/chainlint/comment.expect b/t/chainlint/comment.expect
new file mode 100644
index 0000000000..584098d6ba
--- /dev/null
+++ b/t/chainlint/comment.expect
@@ -0,0 +1,8 @@
+2 (
+3 # comment 1
+4 nothing &&
+5 # comment 2
+6 something
+7 # comment 3
+8 # comment 4
+9 )
diff --git a/t/chainlint/comment.test b/t/chainlint/comment.test
new file mode 100644
index 0000000000..c488beac0d
--- /dev/null
+++ b/t/chainlint/comment.test
@@ -0,0 +1,13 @@
+test_expect_success 'comment' '
+(
+# LINT: swallow comment lines
+ # comment 1
+ nothing &&
+ # comment 2
+ something
+# LINT: swallow comment lines since final _statement_ before subshell end is
+# LINT: significant to "&&"-check, not final _line_ (which might be comment)
+ # comment 3
+ # comment 4
+)
+'
diff --git a/t/chainlint/complex-if-in-cuddled-loop.expect b/t/chainlint/complex-if-in-cuddled-loop.expect
new file mode 100644
index 0000000000..e66bb2d5d0
--- /dev/null
+++ b/t/chainlint/complex-if-in-cuddled-loop.expect
@@ -0,0 +1,9 @@
+2 (for i in a b c; do
+3 if test "$(echo $(waffle bat))" = "eleventeen" &&
+4 test "$x" = "$y"; then
+5 :
+6 else
+7 echo >file
+8 fi ?!LINT: missing '|| exit 1'?!
+9 done) &&
+10 test ! -f file
diff --git a/t/chainlint/complex-if-in-cuddled-loop.test b/t/chainlint/complex-if-in-cuddled-loop.test
new file mode 100644
index 0000000000..f98ae4c42d
--- /dev/null
+++ b/t/chainlint/complex-if-in-cuddled-loop.test
@@ -0,0 +1,13 @@
+test_expect_success 'complex-if-in-cuddled-loop' '
+# LINT: "for" loop cuddled with "(" and ")" and nested "if" with complex
+# LINT: multi-line condition; indented with spaces, not tabs
+(for i in a b c; do
+ if test "$(echo $(waffle bat))" = "eleventeen" &&
+ test "$x" = "$y"; then
+ :
+ else
+ echo >file
+ fi
+ done) &&
+test ! -f file
+'
diff --git a/t/chainlint/cuddled-if-then-else.expect b/t/chainlint/cuddled-if-then-else.expect
new file mode 100644
index 0000000000..72da8794cb
--- /dev/null
+++ b/t/chainlint/cuddled-if-then-else.expect
@@ -0,0 +1,6 @@
+2 (if test -z ""; then
+3 echo empty
+4 else
+5 echo bizzy
+6 fi) &&
+7 echo foobar
diff --git a/t/chainlint/cuddled-if-then-else.test b/t/chainlint/cuddled-if-then-else.test
new file mode 100644
index 0000000000..b1b42e1aac
--- /dev/null
+++ b/t/chainlint/cuddled-if-then-else.test
@@ -0,0 +1,9 @@
+test_expect_success 'cuddled-if-then-else' '
+# LINT: "if" cuddled with "(" and ")"; indented with spaces, not tabs
+(if test -z ""; then
+ echo empty
+ else
+ echo bizzy
+ fi) &&
+echo foobar
+'
diff --git a/t/chainlint/cuddled-loop.expect b/t/chainlint/cuddled-loop.expect
new file mode 100644
index 0000000000..c38585c756
--- /dev/null
+++ b/t/chainlint/cuddled-loop.expect
@@ -0,0 +1,4 @@
+2 ( while read x
+3 do foobar bop || exit 1
+4 done <file ) &&
+5 outside subshell
diff --git a/t/chainlint/cuddled-loop.test b/t/chainlint/cuddled-loop.test
new file mode 100644
index 0000000000..6fccb6ac22
--- /dev/null
+++ b/t/chainlint/cuddled-loop.test
@@ -0,0 +1,9 @@
+test_expect_success 'cuddled-loop' '
+# LINT: "while" loop cuddled with "(" and ")", with embedded (allowed)
+# LINT: "|| exit {n}" to exit loop early, and using redirection "<" to feed
+# LINT: loop; indented with spaces, not tabs
+( while read x
+ do foobar bop || exit 1
+ done <file ) &&
+outside subshell
+'
diff --git a/t/chainlint/cuddled.expect b/t/chainlint/cuddled.expect
new file mode 100644
index 0000000000..1864b3fc8b
--- /dev/null
+++ b/t/chainlint/cuddled.expect
@@ -0,0 +1,17 @@
+2 (cd foo &&
+3 bar
+4 ) &&
+5
+6 (cd foo ?!LINT: missing '&&'?!
+7 bar
+8 ) &&
+9
+10 (
+11 cd foo &&
+12 bar) &&
+13
+14 (cd foo &&
+15 bar) &&
+16
+17 (cd foo ?!LINT: missing '&&'?!
+18 bar)
diff --git a/t/chainlint/cuddled.test b/t/chainlint/cuddled.test
new file mode 100644
index 0000000000..5a6ef7a4a6
--- /dev/null
+++ b/t/chainlint/cuddled.test
@@ -0,0 +1,24 @@
+test_expect_success 'cuddled' '
+# LINT: first subshell statement cuddled with opening "("
+(cd foo &&
+ bar
+) &&
+
+# LINT: same with missing "&&"
+(cd foo
+ bar
+) &&
+
+# LINT: closing ")" cuddled with final subshell statement
+(
+ cd foo &&
+ bar) &&
+
+# LINT: "(" and ")" cuddled with first and final subshell statements
+(cd foo &&
+ bar) &&
+
+# LINT: same with missing "&&"
+(cd foo
+ bar)
+'
diff --git a/t/chainlint/double-here-doc.expect b/t/chainlint/double-here-doc.expect
new file mode 100644
index 0000000000..48c04ecd58
--- /dev/null
+++ b/t/chainlint/double-here-doc.expect
@@ -0,0 +1,12 @@
+2 run_sub_test_lib_test_err run-inv-range-start \
+3 "--run invalid range start" \
+4 --run="a-5" <<-\EOF &&
+5 test_expect_success "passing test #1" "true"
+6 test_done
+7 EOF
+8 check_sub_test_lib_test_err run-inv-range-start \
+9 <<-\EOF_OUT 3<<-EOF_ERR
+10 > FATAL: Unexpected exit with code 1
+11 EOF_OUT
+12 > error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ}
+13 EOF_ERR
diff --git a/t/chainlint/double-here-doc.test b/t/chainlint/double-here-doc.test
new file mode 100644
index 0000000000..1b69b7a651
--- /dev/null
+++ b/t/chainlint/double-here-doc.test
@@ -0,0 +1,14 @@
+test_expect_success 'double-here-doc' '
+run_sub_test_lib_test_err run-inv-range-start \
+ "--run invalid range start" \
+ --run="a-5" <<-\EOF &&
+test_expect_success "passing test #1" "true"
+test_done
+EOF
+check_sub_test_lib_test_err run-inv-range-start \
+ <<-\EOF_OUT 3<<-EOF_ERR
+> FATAL: Unexpected exit with code 1
+EOF_OUT
+> error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ}
+EOF_ERR
+'
diff --git a/t/chainlint/dqstring-line-splice.expect b/t/chainlint/dqstring-line-splice.expect
new file mode 100644
index 0000000000..2ca1c92cd6
--- /dev/null
+++ b/t/chainlint/dqstring-line-splice.expect
@@ -0,0 +1,5 @@
+2
+3 echo 'fatal: reword option of --fixup is mutually exclusive with' '--patch/--interactive/--all/--include/--only' >expect &&
+4 test_must_fail git commit --fixup=reword:HEAD~ $1 2>actual &&
+5 test_cmp expect actual
+6
diff --git a/t/chainlint/dqstring-line-splice.test b/t/chainlint/dqstring-line-splice.test
new file mode 100644
index 0000000000..f6aa637be8
--- /dev/null
+++ b/t/chainlint/dqstring-line-splice.test
@@ -0,0 +1,9 @@
+test_expect_success 'dqstring-line-splice' '
+# LINT: line-splice within DQ-string
+'"
+echo 'fatal: reword option of --fixup is mutually exclusive with'\
+ '--patch/--interactive/--all/--include/--only' >expect &&
+test_must_fail git commit --fixup=reword:HEAD~ $1 2>actual &&
+test_cmp expect actual
+"'
+'
diff --git a/t/chainlint/dqstring-no-interpolate.expect b/t/chainlint/dqstring-no-interpolate.expect
new file mode 100644
index 0000000000..c9f75849c5
--- /dev/null
+++ b/t/chainlint/dqstring-no-interpolate.expect
@@ -0,0 +1,12 @@
+2 grep "^ ! [rejected][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" out &&
+3
+4 grep "^\.git$" output.txt &&
+5
+6
+7 (
+8 cd client$version &&
+9 GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. $(cat ../input)
+10 ) >output &&
+11 cut -d ' ' -f 2 <output | sort >actual &&
+12 test_cmp expect actual
+13
diff --git a/t/chainlint/dqstring-no-interpolate.test b/t/chainlint/dqstring-no-interpolate.test
new file mode 100644
index 0000000000..7ae079b558
--- /dev/null
+++ b/t/chainlint/dqstring-no-interpolate.test
@@ -0,0 +1,17 @@
+test_expect_success 'dqstring-no-interpolate' '
+# LINT: regex dollar-sign eol anchor in double-quoted string not special
+grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" out &&
+
+# LINT: escaped "$" not mistaken for variable expansion
+grep "^\\.git\$" output.txt &&
+
+'"
+(
+ cd client$version &&
+# LINT: escaped dollar-sign in double-quoted test body
+ GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. \$(cat ../input)
+) >output &&
+ cut -d ' ' -f 2 <output | sort >actual &&
+ test_cmp expect actual
+"'
+'
diff --git a/t/chainlint/empty-here-doc.expect b/t/chainlint/empty-here-doc.expect
new file mode 100644
index 0000000000..54b33f823a
--- /dev/null
+++ b/t/chainlint/empty-here-doc.expect
@@ -0,0 +1,4 @@
+2 git ls-tree $tree path >current &&
+3 cat >expected <<\EOF &&
+4 EOF
+5 test_output
diff --git a/t/chainlint/empty-here-doc.test b/t/chainlint/empty-here-doc.test
new file mode 100644
index 0000000000..8b7ab6eb5f
--- /dev/null
+++ b/t/chainlint/empty-here-doc.test
@@ -0,0 +1,7 @@
+test_expect_success 'empty-here-doc' '
+git ls-tree $tree path >current &&
+# LINT: empty here-doc
+cat >expected <<\EOF &&
+EOF
+test_output
+'
diff --git a/t/chainlint/exclamation.expect b/t/chainlint/exclamation.expect
new file mode 100644
index 0000000000..078744b61b
--- /dev/null
+++ b/t/chainlint/exclamation.expect
@@ -0,0 +1,4 @@
+2 if ! condition; then echo nope; else yep; fi &&
+3 test_prerequisite !MINGW &&
+4 mail uucp!address &&
+5 echo !whatever!
diff --git a/t/chainlint/exclamation.test b/t/chainlint/exclamation.test
new file mode 100644
index 0000000000..796de21b7c
--- /dev/null
+++ b/t/chainlint/exclamation.test
@@ -0,0 +1,10 @@
+test_expect_success 'exclamation' '
+# LINT: "! word" is two tokens
+if ! condition; then echo nope; else yep; fi &&
+# LINT: "!word" is single token, not two tokens "!" and "word"
+test_prerequisite !MINGW &&
+# LINT: "word!word" is single token, not three tokens "word", "!", and "word"
+mail uucp!address &&
+# LINT: "!word!" is single token, not three tokens "!", "word", and "!"
+echo !whatever!
+'
diff --git a/t/chainlint/exit-loop.expect b/t/chainlint/exit-loop.expect
new file mode 100644
index 0000000000..407278094c
--- /dev/null
+++ b/t/chainlint/exit-loop.expect
@@ -0,0 +1,24 @@
+2 (
+3 for i in a b c
+4 do
+5 foo || exit 1
+6 bar &&
+7 baz
+8 done
+9 ) &&
+10 (
+11 while true
+12 do
+13 foo || exit 1
+14 bar &&
+15 baz
+16 done
+17 ) &&
+18 (
+19 i=0 &&
+20 while test $i -lt 10
+21 do
+22 echo $i || exit
+23 i=$(($i + 1))
+24 done
+25 )
diff --git a/t/chainlint/exit-loop.test b/t/chainlint/exit-loop.test
new file mode 100644
index 0000000000..7e8b68b465
--- /dev/null
+++ b/t/chainlint/exit-loop.test
@@ -0,0 +1,29 @@
+test_expect_success 'exit-loop' '
+(
+ for i in a b c
+ do
+# LINT: "|| exit {n}" valid for-loop escape in subshell; no "&&" needed
+ foo || exit 1
+ bar &&
+ baz
+ done
+) &&
+(
+ while true
+ do
+# LINT: "|| exit {n}" valid while-loop escape in subshell; no "&&" needed
+ foo || exit 1
+ bar &&
+ baz
+ done
+) &&
+(
+ i=0 &&
+ while test $i -lt 10
+ do
+# LINT: "|| exit" (sans exit code) valid escape in subshell; no "&&" needed
+ echo $i || exit
+ i=$(($i + 1))
+ done
+)
+'
diff --git a/t/chainlint/exit-subshell.expect b/t/chainlint/exit-subshell.expect
new file mode 100644
index 0000000000..793db12453
--- /dev/null
+++ b/t/chainlint/exit-subshell.expect
@@ -0,0 +1,5 @@
+2 (
+3 foo || exit 1
+4 bar &&
+5 baz
+6 )
diff --git a/t/chainlint/exit-subshell.test b/t/chainlint/exit-subshell.test
new file mode 100644
index 0000000000..05dff55cd7
--- /dev/null
+++ b/t/chainlint/exit-subshell.test
@@ -0,0 +1,8 @@
+test_expect_success 'exit-subshell' '
+(
+# LINT: "|| exit {n}" valid subshell escape without hurting &&-chain
+ foo || exit 1
+ bar &&
+ baz
+)
+'
diff --git a/t/chainlint/for-loop-abbreviated.expect b/t/chainlint/for-loop-abbreviated.expect
new file mode 100644
index 0000000000..5574831976
--- /dev/null
+++ b/t/chainlint/for-loop-abbreviated.expect
@@ -0,0 +1,5 @@
+2 for it
+3 do
+4 path=$(expr "$it" : ([^:]*)) &&
+5 git update-index --add "$path" || exit
+6 done
diff --git a/t/chainlint/for-loop-abbreviated.test b/t/chainlint/for-loop-abbreviated.test
new file mode 100644
index 0000000000..1dd14f2a44
--- /dev/null
+++ b/t/chainlint/for-loop-abbreviated.test
@@ -0,0 +1,8 @@
+test_expect_success 'for-loop-abbreviated' '
+# LINT: for-loop lacking optional "in [word...]" before "do"
+for it
+do
+ path=$(expr "$it" : '\([^:]*\)') &&
+ git update-index --add "$path" || exit
+done
+'
diff --git a/t/chainlint/for-loop.expect b/t/chainlint/for-loop.expect
new file mode 100644
index 0000000000..5029eacce3
--- /dev/null
+++ b/t/chainlint/for-loop.expect
@@ -0,0 +1,14 @@
+2 (
+3 for i in a b c
+4 do
+5 echo $i ?!LINT: missing '&&'?!
+6 cat <<-\EOF ?!LINT: missing '|| exit 1'?!
+7 bar
+8 EOF
+9 done ?!LINT: missing '&&'?!
+10
+11 for i in a b c; do
+12 echo $i &&
+13 cat $i ?!LINT: missing '|| exit 1'?!
+14 done
+15 )
diff --git a/t/chainlint/for-loop.test b/t/chainlint/for-loop.test
new file mode 100644
index 0000000000..6f2489eb19
--- /dev/null
+++ b/t/chainlint/for-loop.test
@@ -0,0 +1,21 @@
+test_expect_success 'for-loop' '
+(
+# LINT: "for", "do", "done" do not need "&&"
+ for i in a b c
+ do
+# LINT: missing "&&" on "echo"
+ echo $i
+# LINT: last statement of while does not need "&&"
+ cat <<-\EOF
+ bar
+ EOF
+# LINT: missing "&&" on "done"
+ done
+
+# LINT: "do" on same line as "for"
+ for i in a b c; do
+ echo $i &&
+ cat $i
+ done
+)
+'
diff --git a/t/chainlint/function.expect b/t/chainlint/function.expect
new file mode 100644
index 0000000000..9e46a3554a
--- /dev/null
+++ b/t/chainlint/function.expect
@@ -0,0 +1,11 @@
+2 sha1_file() {
+3 echo "$*" | sed "s#..#.git/objects/&/#"
+4 } &&
+5
+6 remove_object() {
+7 file=$(sha1_file "$*") &&
+8 test -e "$file" ?!LINT: missing '&&'?!
+9 rm -f "$file"
+10 } ?!LINT: missing '&&'?!
+11
+12 sha1_file arg && remove_object arg
diff --git a/t/chainlint/function.test b/t/chainlint/function.test
new file mode 100644
index 0000000000..763fcf3f87
--- /dev/null
+++ b/t/chainlint/function.test
@@ -0,0 +1,15 @@
+test_expect_success 'function' '
+# LINT: "()" in function definition not mistaken for subshell
+sha1_file() {
+ echo "$*" | sed "s#..#.git/objects/&/#"
+} &&
+
+# LINT: broken &&-chain in function and after function
+remove_object() {
+ file=$(sha1_file "$*") &&
+ test -e "$file"
+ rm -f "$file"
+}
+
+sha1_file arg && remove_object arg
+'
diff --git a/t/chainlint/here-doc-body-indent.expect b/t/chainlint/here-doc-body-indent.expect
new file mode 100644
index 0000000000..4306faee86
--- /dev/null
+++ b/t/chainlint/here-doc-body-indent.expect
@@ -0,0 +1,2 @@
+2 echo "we should find this" ?!LINT: missing '&&'?!
+3 echo "even though our heredoc has its indent stripped"
diff --git a/t/chainlint/here-doc-body-indent.test b/t/chainlint/here-doc-body-indent.test
new file mode 100644
index 0000000000..39ff970ef3
--- /dev/null
+++ b/t/chainlint/here-doc-body-indent.test
@@ -0,0 +1,4 @@
+test_expect_success 'here-doc-body-indent' - <<-\EOT
+ echo "we should find this"
+ echo "even though our heredoc has its indent stripped"
+EOT
diff --git a/t/chainlint/here-doc-body-pathological.expect b/t/chainlint/here-doc-body-pathological.expect
new file mode 100644
index 0000000000..2f8ea03a47
--- /dev/null
+++ b/t/chainlint/here-doc-body-pathological.expect
@@ -0,0 +1,7 @@
+2 echo "outer here-doc does not allow indented end-tag" ?!LINT: missing '&&'?!
+3 cat >file <<-\EOF &&
+4 but this inner here-doc
+5 does allow indented EOF
+6 EOF
+7 echo "missing chain after" ?!LINT: missing '&&'?!
+8 echo "but this line is OK because it's the end"
diff --git a/t/chainlint/here-doc-body-pathological.test b/t/chainlint/here-doc-body-pathological.test
new file mode 100644
index 0000000000..7d2daa44f9
--- /dev/null
+++ b/t/chainlint/here-doc-body-pathological.test
@@ -0,0 +1,9 @@
+test_expect_success 'here-doc-body-pathological' - <<\EOF
+ echo "outer here-doc does not allow indented end-tag"
+ cat >file <<-\EOF &&
+ but this inner here-doc
+ does allow indented EOF
+ EOF
+ echo "missing chain after"
+ echo "but this line is OK because it's the end"
+EOF
diff --git a/t/chainlint/here-doc-body.expect b/t/chainlint/here-doc-body.expect
new file mode 100644
index 0000000000..df8d79bc0a
--- /dev/null
+++ b/t/chainlint/here-doc-body.expect
@@ -0,0 +1,7 @@
+2 echo "missing chain before" ?!LINT: missing '&&'?!
+3 cat >file <<-\EOF &&
+4 inside inner here-doc
+5 these are not shell commands
+6 EOF
+7 echo "missing chain after" ?!LINT: missing '&&'?!
+8 echo "but this line is OK because it's the end"
diff --git a/t/chainlint/here-doc-body.test b/t/chainlint/here-doc-body.test
new file mode 100644
index 0000000000..989ac2f4e1
--- /dev/null
+++ b/t/chainlint/here-doc-body.test
@@ -0,0 +1,9 @@
+test_expect_success 'here-doc-body' - <<\EOT
+ echo "missing chain before"
+ cat >file <<-\EOF &&
+ inside inner here-doc
+ these are not shell commands
+ EOF
+ echo "missing chain after"
+ echo "but this line is OK because it's the end"
+EOT
diff --git a/t/chainlint/here-doc-close-subshell.expect b/t/chainlint/here-doc-close-subshell.expect
new file mode 100644
index 0000000000..965813f463
--- /dev/null
+++ b/t/chainlint/here-doc-close-subshell.expect
@@ -0,0 +1,4 @@
+2 (
+3 cat <<-\INPUT)
+4 fizz
+5 INPUT
diff --git a/t/chainlint/here-doc-close-subshell.test b/t/chainlint/here-doc-close-subshell.test
new file mode 100644
index 0000000000..2458f3323b
--- /dev/null
+++ b/t/chainlint/here-doc-close-subshell.test
@@ -0,0 +1,7 @@
+test_expect_success 'here-doc-close-subshell' '
+(
+# LINT: line contains here-doc and closes nested subshell
+ cat <<-\INPUT)
+ fizz
+ INPUT
+'
diff --git a/t/chainlint/here-doc-double.expect b/t/chainlint/here-doc-double.expect
new file mode 100644
index 0000000000..e5e981889f
--- /dev/null
+++ b/t/chainlint/here-doc-double.expect
@@ -0,0 +1,2 @@
+8 echo "actual test commands" ?!LINT: missing '&&'?!
+9 echo "that should be checked"
diff --git a/t/chainlint/here-doc-double.test b/t/chainlint/here-doc-double.test
new file mode 100644
index 0000000000..777389f0d9
--- /dev/null
+++ b/t/chainlint/here-doc-double.test
@@ -0,0 +1,10 @@
+# This is obviously a ridiculous thing to do, but we should be able
+# to handle two here-docs on the same line, and attribute them
+# correctly.
+test_expect_success "$(cat <<END_OF_PREREQS)" 'here-doc-double' - <<\EOT
+SOME
+PREREQS
+END_OF_PREREQS
+ echo "actual test commands"
+ echo "that should be checked"
+EOT
diff --git a/t/chainlint/here-doc-indent-operator.expect b/t/chainlint/here-doc-indent-operator.expect
new file mode 100644
index 0000000000..ec0e61505b
--- /dev/null
+++ b/t/chainlint/here-doc-indent-operator.expect
@@ -0,0 +1,11 @@
+2 cat >expect <<- EOF &&
+3 header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
+4 num_commits: $1
+5 chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
+6 EOF
+7
+8 cat >expect << -EOF ?!LINT: missing '&&'?!
+9 this is not indented
+10 -EOF
+11
+12 cleanup
diff --git a/t/chainlint/here-doc-indent-operator.test b/t/chainlint/here-doc-indent-operator.test
new file mode 100644
index 0000000000..a2656f47c1
--- /dev/null
+++ b/t/chainlint/here-doc-indent-operator.test
@@ -0,0 +1,15 @@
+test_expect_success 'here-doc-indent-operator' '
+# LINT: whitespace between operator "<<-" and tag legal
+cat >expect <<- EOF &&
+header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
+num_commits: $1
+chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
+EOF
+
+# LINT: not an indented here-doc; just a plain here-doc with tag named "-EOF"
+cat >expect << -EOF
+this is not indented
+-EOF
+
+cleanup
+'
diff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect
new file mode 100644
index 0000000000..8128f15b92
--- /dev/null
+++ b/t/chainlint/here-doc-multi-line-command-subst.expect
@@ -0,0 +1,8 @@
+2 (
+3 x=$(bobble <<-\END &&
+4 fossil
+5 vegetable
+6 END
+7 wiffle) ?!LINT: missing '&&'?!
+8 echo $x
+9 )
diff --git a/t/chainlint/here-doc-multi-line-command-subst.test b/t/chainlint/here-doc-multi-line-command-subst.test
new file mode 100644
index 0000000000..8710a8c483
--- /dev/null
+++ b/t/chainlint/here-doc-multi-line-command-subst.test
@@ -0,0 +1,11 @@
+test_expect_success 'here-doc-multi-line-command-subst' '
+(
+# LINT: line contains here-doc and opens multi-line $(...)
+ x=$(bobble <<-\END &&
+ fossil
+ vegetable
+ END
+ wiffle)
+ echo $x
+)
+'
diff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect
new file mode 100644
index 0000000000..a03a04ff3d
--- /dev/null
+++ b/t/chainlint/here-doc-multi-line-string.expect
@@ -0,0 +1,7 @@
+2 (
+3 cat <<-\TXT && echo "multi-line
+4 string" ?!LINT: missing '&&'?!
+5 fizzle
+6 TXT
+7 bap
+8 )
diff --git a/t/chainlint/here-doc-multi-line-string.test b/t/chainlint/here-doc-multi-line-string.test
new file mode 100644
index 0000000000..2f496002fd
--- /dev/null
+++ b/t/chainlint/here-doc-multi-line-string.test
@@ -0,0 +1,10 @@
+test_expect_success 'here-doc-multi-line-string' '
+(
+# LINT: line contains here-doc and opens multi-line string
+ cat <<-\TXT && echo "multi-line
+ string"
+ fizzle
+ TXT
+ bap
+)
+'
diff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect
new file mode 100644
index 0000000000..2c382dd8eb
--- /dev/null
+++ b/t/chainlint/here-doc.expect
@@ -0,0 +1,25 @@
+2 boodle wobba \
+3 gorgo snoot \
+4 wafta snurb <<EOF &&
+5 quoth the raven,
+6 nevermore...
+7 EOF
+8
+9 cat <<-Arbitrary_Tag_42 >foo &&
+10 snoz
+11 boz
+12 woz
+13 Arbitrary_Tag_42
+14
+15 cat <<"zump" >boo &&
+16 snoz
+17 boz
+18 woz
+19 zump
+20
+21 horticulture <<\EOF
+22 gomez
+23 morticia
+24 wednesday
+25 pugsly
+26 EOF
diff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test
new file mode 100644
index 0000000000..c91b695319
--- /dev/null
+++ b/t/chainlint/here-doc.test
@@ -0,0 +1,32 @@
+test_expect_success 'here-doc' '
+# LINT: stitch together incomplete \-ending lines
+# LINT: swallow here-doc to avoid false positives in content
+boodle wobba \
+ gorgo snoot \
+ wafta snurb <<EOF &&
+quoth the raven,
+nevermore...
+EOF
+
+# LINT: swallow here-doc with arbitrary tag
+cat <<-Arbitrary_Tag_42 >foo &&
+snoz
+boz
+woz
+Arbitrary_Tag_42
+
+# LINT: swallow "quoted" here-doc
+cat <<"zump" >boo &&
+snoz
+boz
+woz
+zump
+
+# LINT: swallow here-doc (EOF is last line of test)
+horticulture <<\EOF
+gomez
+morticia
+wednesday
+pugsly
+EOF
+'
diff --git a/t/chainlint/if-condition-split.expect b/t/chainlint/if-condition-split.expect
new file mode 100644
index 0000000000..6d2a03dfdb
--- /dev/null
+++ b/t/chainlint/if-condition-split.expect
@@ -0,0 +1,7 @@
+2 if bob &&
+3 marcia ||
+4 kevin
+5 then
+6 echo "nomads" ?!LINT: missing '&&'?!
+7 echo "for sure"
+8 fi
diff --git a/t/chainlint/if-condition-split.test b/t/chainlint/if-condition-split.test
new file mode 100644
index 0000000000..9a3b3ed04a
--- /dev/null
+++ b/t/chainlint/if-condition-split.test
@@ -0,0 +1,10 @@
+test_expect_success 'if-condition-split' '
+# LINT: "if" condition split across multiple lines at "&&" or "||"
+if bob &&
+ marcia ||
+ kevin
+then
+ echo "nomads"
+ echo "for sure"
+fi
+'
diff --git a/t/chainlint/if-in-loop.expect b/t/chainlint/if-in-loop.expect
new file mode 100644
index 0000000000..7e3ba740de
--- /dev/null
+++ b/t/chainlint/if-in-loop.expect
@@ -0,0 +1,12 @@
+2 (
+3 for i in a b c
+4 do
+5 if false
+6 then
+7 echo "err"
+8 exit 1
+9 fi ?!LINT: missing '&&'?!
+10 foo
+11 done ?!LINT: missing '&&'?!
+12 bar
+13 )
diff --git a/t/chainlint/if-in-loop.test b/t/chainlint/if-in-loop.test
new file mode 100644
index 0000000000..5be9d1cfa5
--- /dev/null
+++ b/t/chainlint/if-in-loop.test
@@ -0,0 +1,17 @@
+test_expect_success 'if-in-loop' '
+(
+ for i in a b c
+ do
+ if false
+ then
+# LINT: missing "&&" on "echo" okay since "exit 1" signals error explicitly
+ echo "err"
+ exit 1
+# LINT: missing "&&" on "fi"
+ fi
+ foo
+# LINT: missing "&&" on "done"
+ done
+ bar
+)
+'
diff --git a/t/chainlint/if-then-else.expect b/t/chainlint/if-then-else.expect
new file mode 100644
index 0000000000..924caa2e4e
--- /dev/null
+++ b/t/chainlint/if-then-else.expect
@@ -0,0 +1,22 @@
+2 (
+3 if test -n ""
+4 then
+5 echo very ?!LINT: missing '&&'?!
+6 echo empty
+7 elif test -z ""
+8 then
+9 echo foo
+10 else
+11 echo foo &&
+12 cat <<-\EOF
+13 bar
+14 EOF
+15 fi ?!LINT: missing '&&'?!
+16 echo poodle
+17 ) &&
+18 (
+19 if test -n ""; then
+20 echo very &&
+21 echo empty
+22 fi
+23 )
diff --git a/t/chainlint/if-then-else.test b/t/chainlint/if-then-else.test
new file mode 100644
index 0000000000..6582a7f440
--- /dev/null
+++ b/t/chainlint/if-then-else.test
@@ -0,0 +1,31 @@
+test_expect_success 'if-then-else' '
+(
+# LINT: "if", "then", "elif", "else", "fi" do not need "&&"
+ if test -n ""
+ then
+# LINT: missing "&&" on "echo"
+ echo very
+# LINT: last statement before "elif" does not need "&&"
+ echo empty
+ elif test -z ""
+ then
+# LINT: last statement before "else" does not need "&&"
+ echo foo
+ else
+ echo foo &&
+# LINT: last statement before "fi" does not need "&&"
+ cat <<-\EOF
+ bar
+ EOF
+# LINT: missing "&&" on "fi"
+ fi
+ echo poodle
+) &&
+(
+# LINT: "then" on same line as "if"
+ if test -n ""; then
+ echo very &&
+ echo empty
+ fi
+)
+'
diff --git a/t/chainlint/incomplete-line.expect b/t/chainlint/incomplete-line.expect
new file mode 100644
index 0000000000..b15e00b901
--- /dev/null
+++ b/t/chainlint/incomplete-line.expect
@@ -0,0 +1,10 @@
+2 line 1 \
+3 line 2 \
+4 line 3 \
+5 line 4 &&
+6 (
+7 line 5 \
+8 line 6 \
+9 line 7 \
+10 line 8
+11 )
diff --git a/t/chainlint/incomplete-line.test b/t/chainlint/incomplete-line.test
new file mode 100644
index 0000000000..74a93021eb
--- /dev/null
+++ b/t/chainlint/incomplete-line.test
@@ -0,0 +1,14 @@
+test_expect_success 'incomplete-line' '
+# LINT: stitch together all incomplete \-ending lines
+line 1 \
+line 2 \
+line 3 \
+line 4 &&
+(
+# LINT: stitch together all incomplete \-ending lines (subshell)
+ line 5 \
+ line 6 \
+ line 7 \
+ line 8
+)
+'
diff --git a/t/chainlint/inline-comment.expect b/t/chainlint/inline-comment.expect
new file mode 100644
index 0000000000..4b4080124e
--- /dev/null
+++ b/t/chainlint/inline-comment.expect
@@ -0,0 +1,8 @@
+2 (
+3 foobar && # comment 1
+4 barfoo ?!LINT: missing '&&'?! # wrong position for &&
+5 flibble "not a # comment"
+6 ) &&
+7
+8 (cd foo &&
+9 flibble "not a # comment")
diff --git a/t/chainlint/inline-comment.test b/t/chainlint/inline-comment.test
new file mode 100644
index 0000000000..4fbbf1058a
--- /dev/null
+++ b/t/chainlint/inline-comment.test
@@ -0,0 +1,14 @@
+test_expect_success 'inline-comment' '
+(
+# LINT: swallow inline comment (leaving command intact)
+ foobar && # comment 1
+# LINT: mispositioned "&&" (correctly) swallowed with comment
+ barfoo # wrong position for &&
+# LINT: "#" in string not misinterpreted as comment
+ flibble "not a # comment"
+) &&
+
+# LINT: "#" in string in cuddled subshell not misinterpreted as comment
+(cd foo &&
+ flibble "not a # comment")
+'
diff --git a/t/chainlint/loop-detect-failure.expect b/t/chainlint/loop-detect-failure.expect
new file mode 100644
index 0000000000..7d846b878d
--- /dev/null
+++ b/t/chainlint/loop-detect-failure.expect
@@ -0,0 +1,15 @@
+2 git init r1 &&
+3 for n in 1 2 3 4 5
+4 do
+5 echo "This is file: $n" > r1/file.$n &&
+6 git -C r1 add file.$n &&
+7 git -C r1 commit -m "$n" || return 1
+8 done &&
+9
+10 git init r2 &&
+11 for n in 1000 10000
+12 do
+13 printf "%"$n"s" X > r2/large.$n &&
+14 git -C r2 add large.$n &&
+15 git -C r2 commit -m "$n" ?!LINT: missing '|| return 1'?!
+16 done
diff --git a/t/chainlint/loop-detect-failure.test b/t/chainlint/loop-detect-failure.test
new file mode 100644
index 0000000000..44673aa394
--- /dev/null
+++ b/t/chainlint/loop-detect-failure.test
@@ -0,0 +1,19 @@
+test_expect_success 'loop-detect-failure' '
+git init r1 &&
+# LINT: loop handles failure explicitly with "|| return 1"
+for n in 1 2 3 4 5
+do
+ echo "This is file: $n" > r1/file.$n &&
+ git -C r1 add file.$n &&
+ git -C r1 commit -m "$n" || return 1
+done &&
+
+git init r2 &&
+# LINT: loop fails to handle failure explicitly with "|| return 1"
+for n in 1000 10000
+do
+ printf "%"$n"s" X > r2/large.$n &&
+ git -C r2 add large.$n &&
+ git -C r2 commit -m "$n"
+done
+'
diff --git a/t/chainlint/loop-detect-status.expect b/t/chainlint/loop-detect-status.expect
new file mode 100644
index 0000000000..0f180b08de
--- /dev/null
+++ b/t/chainlint/loop-detect-status.expect
@@ -0,0 +1,18 @@
+2 (while test $i -le $blobcount
+3 do
+4 printf "Generating blob $i/$blobcount\r" >&2 &&
+5 printf "blob\nmark :$i\ndata $blobsize\n" &&
+6 #test-tool genrandom $i $blobsize &&
+7 printf "%-${blobsize}s" $i &&
+8 echo "M 100644 :$i $i" >> commit &&
+9 i=$(($i+1)) ||
+10 echo $? > exit-status
+11 done &&
+12 echo "commit refs/heads/main" &&
+13 echo "author A U Thor <author@email.com> 123456789 +0000" &&
+14 echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
+15 echo "data 5" &&
+16 echo ">2gb" &&
+17 cat commit) |
+18 git fast-import --big-file-threshold=2 &&
+19 test ! -f exit-status
diff --git a/t/chainlint/loop-detect-status.test b/t/chainlint/loop-detect-status.test
new file mode 100644
index 0000000000..8b639be073
--- /dev/null
+++ b/t/chainlint/loop-detect-status.test
@@ -0,0 +1,21 @@
+test_expect_success 'loop-detect-status' '
+# LINT: "$?" handled explicitly within loop body
+(while test $i -le $blobcount
+ do
+ printf "Generating blob $i/$blobcount\r" >&2 &&
+ printf "blob\nmark :$i\ndata $blobsize\n" &&
+ #test-tool genrandom $i $blobsize &&
+ printf "%-${blobsize}s" $i &&
+ echo "M 100644 :$i $i" >> commit &&
+ i=$(($i+1)) ||
+ echo $? > exit-status
+ done &&
+ echo "commit refs/heads/main" &&
+ echo "author A U Thor <author@email.com> 123456789 +0000" &&
+ echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
+ echo "data 5" &&
+ echo ">2gb" &&
+ cat commit) |
+git fast-import --big-file-threshold=2 &&
+test ! -f exit-status
+'
diff --git a/t/chainlint/loop-in-if.expect b/t/chainlint/loop-in-if.expect
new file mode 100644
index 0000000000..32e076ad1b
--- /dev/null
+++ b/t/chainlint/loop-in-if.expect
@@ -0,0 +1,12 @@
+2 (
+3 if true
+4 then
+5 while true
+6 do
+7 echo "pop" ?!LINT: missing '&&'?!
+8 echo "glup" ?!LINT: missing '|| exit 1'?!
+9 done ?!LINT: missing '&&'?!
+10 foo
+11 fi ?!LINT: missing '&&'?!
+12 bar
+13 )
diff --git a/t/chainlint/loop-in-if.test b/t/chainlint/loop-in-if.test
new file mode 100644
index 0000000000..b0d0d393cf
--- /dev/null
+++ b/t/chainlint/loop-in-if.test
@@ -0,0 +1,17 @@
+test_expect_success 'loop-in-if' '
+(
+ if true
+ then
+ while true
+ do
+# LINT: missing "&&" on "echo"
+ echo "pop"
+ echo "glup"
+# LINT: missing "&&" on "done"
+ done
+ foo
+# LINT: missing "&&" on "fi"
+ fi
+ bar
+)
+'
diff --git a/t/chainlint/loop-upstream-pipe.expect b/t/chainlint/loop-upstream-pipe.expect
new file mode 100644
index 0000000000..bef82479ca
--- /dev/null
+++ b/t/chainlint/loop-upstream-pipe.expect
@@ -0,0 +1,10 @@
+2 (
+3 git rev-list --objects --no-object-names base..loose |
+4 while read oid
+5 do
+6 path="$objdir/$(test_oid_to_path "$oid")" &&
+7 printf "%s %d\n" "$oid" "$(test-tool chmtime --get "$path")" ||
+8 echo "object list generation failed for $oid"
+9 done |
+10 sort -k1
+11 ) >expect &&
diff --git a/t/chainlint/loop-upstream-pipe.test b/t/chainlint/loop-upstream-pipe.test
new file mode 100644
index 0000000000..8415a4db27
--- /dev/null
+++ b/t/chainlint/loop-upstream-pipe.test
@@ -0,0 +1,13 @@
+test_expect_success 'loop-upstream-pipe' '
+(
+ git rev-list --objects --no-object-names base..loose |
+ while read oid
+ do
+# LINT: "|| echo" signals failure in loop upstream of a pipe
+ path="$objdir/$(test_oid_to_path "$oid")" &&
+ printf "%s %d\n" "$oid" "$(test-tool chmtime --get "$path")" ||
+ echo "object list generation failed for $oid"
+ done |
+ sort -k1
+) >expect &&
+'
diff --git a/t/chainlint/multi-line-nested-command-substitution.expect b/t/chainlint/multi-line-nested-command-substitution.expect
new file mode 100644
index 0000000000..ad27e43e05
--- /dev/null
+++ b/t/chainlint/multi-line-nested-command-substitution.expect
@@ -0,0 +1,18 @@
+2 (
+3 foo &&
+4 x=$(
+5 echo bar |
+6 cat
+7 ) &&
+8 echo ok
+9 ) |
+10 sort &&
+11 (
+12 bar &&
+13 x=$(echo bar |
+14 cat
+15 ) &&
+16 y=$(echo baz |
+17 fip) &&
+18 echo fail
+19 )
diff --git a/t/chainlint/multi-line-nested-command-substitution.test b/t/chainlint/multi-line-nested-command-substitution.test
new file mode 100644
index 0000000000..e811c63f2b
--- /dev/null
+++ b/t/chainlint/multi-line-nested-command-substitution.test
@@ -0,0 +1,20 @@
+test_expect_success 'multi-line-nested-command-substitution' '
+(
+ foo &&
+ x=$(
+ echo bar |
+ cat
+ ) &&
+ echo ok
+) |
+sort &&
+(
+ bar &&
+ x=$(echo bar |
+ cat
+ ) &&
+ y=$(echo baz |
+ fip) &&
+ echo fail
+)
+'
diff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect
new file mode 100644
index 0000000000..9d33297525
--- /dev/null
+++ b/t/chainlint/multi-line-string.expect
@@ -0,0 +1,14 @@
+2 (
+3 x="line 1
+4 line 2
+5 line 3" &&
+6 y="line 1
+7 line2" ?!LINT: missing '&&'?!
+8 foobar
+9 ) &&
+10 (
+11 echo "xyz" "abc
+12 def
+13 ghi" &&
+14 barfoo
+15 )
diff --git a/t/chainlint/multi-line-string.test b/t/chainlint/multi-line-string.test
new file mode 100644
index 0000000000..7b5048d2ea
--- /dev/null
+++ b/t/chainlint/multi-line-string.test
@@ -0,0 +1,17 @@
+test_expect_success 'multi-line-string' '
+(
+ x="line 1
+ line 2
+ line 3" &&
+# LINT: missing "&&" on assignment
+ y="line 1
+ line2"
+ foobar
+) &&
+(
+ echo "xyz" "abc
+ def
+ ghi" &&
+ barfoo
+)
+'
diff --git a/t/chainlint/negated-one-liner.expect b/t/chainlint/negated-one-liner.expect
new file mode 100644
index 0000000000..0a6f3c29b2
--- /dev/null
+++ b/t/chainlint/negated-one-liner.expect
@@ -0,0 +1,5 @@
+2 ! (foo && bar) &&
+3 ! (foo && bar) >baz &&
+4
+5 ! (foo; ?!LINT: missing '&&'?! bar) &&
+6 ! (foo; ?!LINT: missing '&&'?! bar) >baz
diff --git a/t/chainlint/negated-one-liner.test b/t/chainlint/negated-one-liner.test
new file mode 100644
index 0000000000..30f4cc5a9b
--- /dev/null
+++ b/t/chainlint/negated-one-liner.test
@@ -0,0 +1,9 @@
+test_expect_success 'negated-one-liner' '
+# LINT: top-level one-liner subshell
+! (foo && bar) &&
+! (foo && bar) >baz &&
+
+# LINT: top-level one-liner subshell missing internal "&&"
+! (foo; bar) &&
+! (foo; bar) >baz
+'
diff --git a/t/chainlint/nested-cuddled-subshell.expect b/t/chainlint/nested-cuddled-subshell.expect
new file mode 100644
index 0000000000..fec2c74274
--- /dev/null
+++ b/t/chainlint/nested-cuddled-subshell.expect
@@ -0,0 +1,25 @@
+2 (
+3 (cd foo &&
+4 bar
+5 ) &&
+6
+7 (cd foo &&
+8 bar
+9 ) ?!LINT: missing '&&'?!
+10
+11 (
+12 cd foo &&
+13 bar) &&
+14
+15 (
+16 cd foo &&
+17 bar) ?!LINT: missing '&&'?!
+18
+19 (cd foo &&
+20 bar) &&
+21
+22 (cd foo &&
+23 bar) ?!LINT: missing '&&'?!
+24
+25 foobar
+26 )
diff --git a/t/chainlint/nested-cuddled-subshell.test b/t/chainlint/nested-cuddled-subshell.test
new file mode 100644
index 0000000000..31e92d3be4
--- /dev/null
+++ b/t/chainlint/nested-cuddled-subshell.test
@@ -0,0 +1,33 @@
+test_expect_success 'nested-cuddled-subshell' '
+(
+# LINT: opening "(" cuddled with first nested subshell statement
+ (cd foo &&
+ bar
+ ) &&
+
+# LINT: same but "&&" missing
+ (cd foo &&
+ bar
+ )
+
+# LINT: closing ")" cuddled with final nested subshell statement
+ (
+ cd foo &&
+ bar) &&
+
+# LINT: same but "&&" missing
+ (
+ cd foo &&
+ bar)
+
+# LINT: "(" and ")" cuddled with first and final subshell statements
+ (cd foo &&
+ bar) &&
+
+# LINT: same but "&&" missing
+ (cd foo &&
+ bar)
+
+ foobar
+)
+'
diff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect
new file mode 100644
index 0000000000..571f4c9514
--- /dev/null
+++ b/t/chainlint/nested-here-doc.expect
@@ -0,0 +1,30 @@
+2 cat <<ARBITRARY >foop &&
+3 naddle
+4 fub <<EOF
+5 nozzle
+6 noodle
+7 EOF
+8 formp
+9 ARBITRARY
+10
+11 (
+12 cat <<-\INPUT_END &&
+13 fish are mice
+14 but geese go slow
+15 data <<EOF
+16 perl is lerp
+17 and nothing else
+18 EOF
+19 toink
+20 INPUT_END
+21
+22 cat <<-\EOT ?!LINT: missing '&&'?!
+23 text goes here
+24 data <<EOF
+25 data goes here
+26 EOF
+27 more test here
+28 EOT
+29
+30 foobar
+31 )
diff --git a/t/chainlint/nested-here-doc.test b/t/chainlint/nested-here-doc.test
new file mode 100644
index 0000000000..9505c47a34
--- /dev/null
+++ b/t/chainlint/nested-here-doc.test
@@ -0,0 +1,35 @@
+test_expect_success 'nested-here-doc' '
+# LINT: inner "EOF" not misintrepreted as closing ARBITRARY here-doc
+cat <<ARBITRARY >foop &&
+naddle
+fub <<EOF
+ nozzle
+ noodle
+EOF
+formp
+ARBITRARY
+
+(
+# LINT: inner "EOF" not misintrepreted as closing INPUT_END here-doc
+ cat <<-\INPUT_END &&
+ fish are mice
+ but geese go slow
+ data <<EOF
+ perl is lerp
+ and nothing else
+ EOF
+ toink
+ INPUT_END
+
+# LINT: same but missing "&&"
+ cat <<-\EOT
+ text goes here
+ data <<EOF
+ data goes here
+ EOF
+ more test here
+ EOT
+
+ foobar
+)
+'
diff --git a/t/chainlint/nested-loop-detect-failure.expect b/t/chainlint/nested-loop-detect-failure.expect
new file mode 100644
index 0000000000..b4aaa621a2
--- /dev/null
+++ b/t/chainlint/nested-loop-detect-failure.expect
@@ -0,0 +1,31 @@
+2 for i in 0 1 2 3 4 5 6 7 8 9;
+3 do
+4 for j in 0 1 2 3 4 5 6 7 8 9;
+5 do
+6 echo "$i$j" >"path$i$j" ?!LINT: missing '|| return 1'?!
+7 done ?!LINT: missing '|| return 1'?!
+8 done &&
+9
+10 for i in 0 1 2 3 4 5 6 7 8 9;
+11 do
+12 for j in 0 1 2 3 4 5 6 7 8 9;
+13 do
+14 echo "$i$j" >"path$i$j" || return 1
+15 done
+16 done &&
+17
+18 for i in 0 1 2 3 4 5 6 7 8 9;
+19 do
+20 for j in 0 1 2 3 4 5 6 7 8 9;
+21 do
+22 echo "$i$j" >"path$i$j" ?!LINT: missing '|| return 1'?!
+23 done || return 1
+24 done &&
+25
+26 for i in 0 1 2 3 4 5 6 7 8 9;
+27 do
+28 for j in 0 1 2 3 4 5 6 7 8 9;
+29 do
+30 echo "$i$j" >"path$i$j" || return 1
+31 done || return 1
+32 done
diff --git a/t/chainlint/nested-loop-detect-failure.test b/t/chainlint/nested-loop-detect-failure.test
new file mode 100644
index 0000000000..3d4b657412
--- /dev/null
+++ b/t/chainlint/nested-loop-detect-failure.test
@@ -0,0 +1,37 @@
+test_expect_success 'nested-loop-detect-failure' '
+# LINT: neither loop handles failure explicitly with "|| return 1"
+for i in 0 1 2 3 4 5 6 7 8 9;
+do
+ for j in 0 1 2 3 4 5 6 7 8 9;
+ do
+ echo "$i$j" >"path$i$j"
+ done
+done &&
+
+# LINT: inner loop handles failure explicitly with "|| return 1"
+for i in 0 1 2 3 4 5 6 7 8 9;
+do
+ for j in 0 1 2 3 4 5 6 7 8 9;
+ do
+ echo "$i$j" >"path$i$j" || return 1
+ done
+done &&
+
+# LINT: outer loop handles failure explicitly with "|| return 1"
+for i in 0 1 2 3 4 5 6 7 8 9;
+do
+ for j in 0 1 2 3 4 5 6 7 8 9;
+ do
+ echo "$i$j" >"path$i$j"
+ done || return 1
+done &&
+
+# LINT: inner & outer loops handles failure explicitly with "|| return 1"
+for i in 0 1 2 3 4 5 6 7 8 9;
+do
+ for j in 0 1 2 3 4 5 6 7 8 9;
+ do
+ echo "$i$j" >"path$i$j" || return 1
+ done || return 1
+done
+'
diff --git a/t/chainlint/nested-subshell-comment.expect b/t/chainlint/nested-subshell-comment.expect
new file mode 100644
index 0000000000..078c6f275f
--- /dev/null
+++ b/t/chainlint/nested-subshell-comment.expect
@@ -0,0 +1,11 @@
+2 (
+3 foo &&
+4 (
+5 bar &&
+6 # bottles wobble while fiddles gobble
+7 # minor numbers of cows (or do they?)
+8 baz &&
+9 snaff
+10 ) ?!LINT: missing '&&'?!
+11 fuzzy
+12 )
diff --git a/t/chainlint/nested-subshell-comment.test b/t/chainlint/nested-subshell-comment.test
new file mode 100644
index 0000000000..b430580ce0
--- /dev/null
+++ b/t/chainlint/nested-subshell-comment.test
@@ -0,0 +1,15 @@
+test_expect_success 'nested-subshell-comment' '
+(
+ foo &&
+ (
+ bar &&
+# LINT: ")" in comment in nested subshell not misinterpreted as closing ")"
+ # bottles wobble while fiddles gobble
+ # minor numbers of cows (or do they?)
+ baz &&
+ snaff
+# LINT: missing "&&" on ")"
+ )
+ fuzzy
+)
+'
diff --git a/t/chainlint/nested-subshell.expect b/t/chainlint/nested-subshell.expect
new file mode 100644
index 0000000000..a8d85d5d5b
--- /dev/null
+++ b/t/chainlint/nested-subshell.expect
@@ -0,0 +1,13 @@
+2 (
+3 cd foo &&
+4 (
+5 echo a &&
+6 echo b
+7 ) >file &&
+8
+9 cd foo &&
+10 (
+11 echo a ?!LINT: missing '&&'?!
+12 echo b
+13 ) >file
+14 )
diff --git a/t/chainlint/nested-subshell.test b/t/chainlint/nested-subshell.test
new file mode 100644
index 0000000000..c31da34b73
--- /dev/null
+++ b/t/chainlint/nested-subshell.test
@@ -0,0 +1,15 @@
+test_expect_success 'nested-subshell' '
+(
+ cd foo &&
+ (
+ echo a &&
+ echo b
+ ) >file &&
+
+ cd foo &&
+ (
+ echo a
+ echo b
+ ) >file
+)
+'
diff --git a/t/chainlint/not-heredoc.expect b/t/chainlint/not-heredoc.expect
new file mode 100644
index 0000000000..5d51705a7a
--- /dev/null
+++ b/t/chainlint/not-heredoc.expect
@@ -0,0 +1,14 @@
+2 echo "<<<<<<< ours" &&
+3 echo ourside &&
+4 echo "=======" &&
+5 echo theirside &&
+6 echo ">>>>>>> theirs" &&
+7
+8 (
+9 echo "<<<<<<< ours" &&
+10 echo ourside &&
+11 echo "=======" &&
+12 echo theirside &&
+13 echo ">>>>>>> theirs" ?!LINT: missing '&&'?!
+14 poodle
+15 ) >merged
diff --git a/t/chainlint/not-heredoc.test b/t/chainlint/not-heredoc.test
new file mode 100644
index 0000000000..09711e45e0
--- /dev/null
+++ b/t/chainlint/not-heredoc.test
@@ -0,0 +1,18 @@
+test_expect_success 'not-heredoc' '
+# LINT: "<< ours" inside string is not here-doc
+echo "<<<<<<< ours" &&
+echo ourside &&
+echo "=======" &&
+echo theirside &&
+echo ">>>>>>> theirs" &&
+
+(
+# LINT: "<< ours" inside string is not here-doc
+ echo "<<<<<<< ours" &&
+ echo ourside &&
+ echo "=======" &&
+ echo theirside &&
+ echo ">>>>>>> theirs"
+ poodle
+) >merged
+'
diff --git a/t/chainlint/one-liner-for-loop.expect b/t/chainlint/one-liner-for-loop.expect
new file mode 100644
index 0000000000..e1fcbd3639
--- /dev/null
+++ b/t/chainlint/one-liner-for-loop.expect
@@ -0,0 +1,9 @@
+2 git init dir-rename-and-content &&
+3 (
+4 cd dir-rename-and-content &&
+5 test_write_lines 1 2 3 4 5 >foo &&
+6 mkdir olddir &&
+7 for i in a b c; do echo $i >olddir/$i; ?!LINT: missing '|| exit 1'?! done ?!LINT: missing '&&'?!
+8 git add foo olddir &&
+9 git commit -m "original" &&
+10 )
diff --git a/t/chainlint/one-liner-for-loop.test b/t/chainlint/one-liner-for-loop.test
new file mode 100644
index 0000000000..00afd7ef76
--- /dev/null
+++ b/t/chainlint/one-liner-for-loop.test
@@ -0,0 +1,12 @@
+test_expect_success 'one-liner-for-loop' '
+git init dir-rename-and-content &&
+(
+ cd dir-rename-and-content &&
+ test_write_lines 1 2 3 4 5 >foo &&
+ mkdir olddir &&
+# LINT: one-liner for-loop missing "|| exit"; also broken &&-chain
+ for i in a b c; do echo $i >olddir/$i; done
+ git add foo olddir &&
+ git commit -m "original" &&
+)
+'
diff --git a/t/chainlint/one-liner.expect b/t/chainlint/one-liner.expect
new file mode 100644
index 0000000000..5deeb05070
--- /dev/null
+++ b/t/chainlint/one-liner.expect
@@ -0,0 +1,9 @@
+2 (foo && bar) &&
+3 (foo && bar) |
+4 (foo && bar) >baz &&
+5
+6 (foo; ?!LINT: missing '&&'?! bar) &&
+7 (foo; ?!LINT: missing '&&'?! bar) |
+8 (foo; ?!LINT: missing '&&'?! bar) >baz &&
+9
+10 (foo "bar; baz")
diff --git a/t/chainlint/one-liner.test b/t/chainlint/one-liner.test
new file mode 100644
index 0000000000..6e42ee1b5e
--- /dev/null
+++ b/t/chainlint/one-liner.test
@@ -0,0 +1,14 @@
+test_expect_success 'one-liner' '
+# LINT: top-level one-liner subshell
+(foo && bar) &&
+(foo && bar) |
+(foo && bar) >baz &&
+
+# LINT: top-level one-liner subshell missing internal "&&" and broken &&-chain
+(foo; bar) &&
+(foo; bar) |
+(foo; bar) >baz &&
+
+# LINT: ";" in string not misinterpreted as broken &&-chain
+(foo "bar; baz")
+'
diff --git a/t/chainlint/p4-filespec.expect b/t/chainlint/p4-filespec.expect
new file mode 100644
index 0000000000..cff3e4e3d1
--- /dev/null
+++ b/t/chainlint/p4-filespec.expect
@@ -0,0 +1,4 @@
+2 (
+3 p4 print -1 //depot/fiddle#42 >file &&
+4 foobar
+5 )
diff --git a/t/chainlint/p4-filespec.test b/t/chainlint/p4-filespec.test
new file mode 100644
index 0000000000..8ba6b911dc
--- /dev/null
+++ b/t/chainlint/p4-filespec.test
@@ -0,0 +1,7 @@
+test_expect_success 'p4-filespec' '
+(
+# LINT: Perforce revspec in filespec not misinterpreted as in-line comment
+ p4 print -1 //depot/fiddle#42 >file &&
+ foobar
+)
+'
diff --git a/t/chainlint/pipe.expect b/t/chainlint/pipe.expect
new file mode 100644
index 0000000000..d947c76584
--- /dev/null
+++ b/t/chainlint/pipe.expect
@@ -0,0 +1,10 @@
+2 (
+3 foo |
+4 bar |
+5 baz &&
+6
+7 fish |
+8 cow ?!LINT: missing '&&'?!
+9
+10 sunder
+11 )
diff --git a/t/chainlint/pipe.test b/t/chainlint/pipe.test
new file mode 100644
index 0000000000..1af81c243b
--- /dev/null
+++ b/t/chainlint/pipe.test
@@ -0,0 +1,14 @@
+test_expect_success 'pipe' '
+(
+# LINT: no "&&" needed on line ending with "|"
+ foo |
+ bar |
+ baz &&
+
+# LINT: final line of pipe sequence ("cow") lacking "&&"
+ fish |
+ cow
+
+ sunder
+)
+'
diff --git a/t/chainlint/return-loop.expect b/t/chainlint/return-loop.expect
new file mode 100644
index 0000000000..da8f9abea3
--- /dev/null
+++ b/t/chainlint/return-loop.expect
@@ -0,0 +1,5 @@
+2 while test $i -lt $((num - 5))
+3 do
+4 git notes add -m "notes for commit$i" HEAD~$i || return 1
+5 i=$((i + 1))
+6 done
diff --git a/t/chainlint/return-loop.test b/t/chainlint/return-loop.test
new file mode 100644
index 0000000000..ea76c3593a
--- /dev/null
+++ b/t/chainlint/return-loop.test
@@ -0,0 +1,8 @@
+test_expect_success 'return-loop' '
+while test $i -lt $((num - 5))
+do
+# LINT: "|| return {n}" valid loop escape outside subshell; no "&&" needed
+ git notes add -m "notes for commit$i" HEAD~$i || return 1
+ i=$((i + 1))
+done
+'
diff --git a/t/chainlint/semicolon.expect b/t/chainlint/semicolon.expect
new file mode 100644
index 0000000000..2b499fbe70
--- /dev/null
+++ b/t/chainlint/semicolon.expect
@@ -0,0 +1,19 @@
+2 (
+3 cat foo ; ?!LINT: missing '&&'?! echo bar ?!LINT: missing '&&'?!
+4 cat foo ; ?!LINT: missing '&&'?! echo bar
+5 ) &&
+6 (
+7 cat foo ; ?!LINT: missing '&&'?! echo bar &&
+8 cat foo ; ?!LINT: missing '&&'?! echo bar
+9 ) &&
+10 (
+11 echo "foo; bar" &&
+12 cat foo; ?!LINT: missing '&&'?! echo bar
+13 ) &&
+14 (
+15 foo;
+16 ) &&
+17 (cd foo &&
+18 for i in a b c; do
+19 echo; ?!LINT: missing '|| exit 1'?!
+20 done)
diff --git a/t/chainlint/semicolon.test b/t/chainlint/semicolon.test
new file mode 100644
index 0000000000..fc0ba1b539
--- /dev/null
+++ b/t/chainlint/semicolon.test
@@ -0,0 +1,27 @@
+test_expect_success 'semicolon' '
+(
+# LINT: missing internal "&&" and ending "&&"
+ cat foo ; echo bar
+# LINT: final statement before ")" only missing internal "&&"
+ cat foo ; echo bar
+) &&
+(
+# LINT: missing internal "&&"
+ cat foo ; echo bar &&
+ cat foo ; echo bar
+) &&
+(
+# LINT: not fooled by semicolon in string
+ echo "foo; bar" &&
+ cat foo; echo bar
+) &&
+(
+# LINT: semicolon unnecessary but legitimate
+ foo;
+) &&
+(cd foo &&
+ for i in a b c; do
+# LINT: semicolon unnecessary but legitimate
+ echo;
+ done)
+'
diff --git a/t/chainlint/sqstring-in-sqstring.expect b/t/chainlint/sqstring-in-sqstring.expect
new file mode 100644
index 0000000000..ba5d3c3a6d
--- /dev/null
+++ b/t/chainlint/sqstring-in-sqstring.expect
@@ -0,0 +1,4 @@
+2 perl -e '
+3 defined($_ = -s $_) or die for @ARGV;
+4 exit 1 if $ARGV[0] <= $ARGV[1];
+5 ' test-2-$packname_2.pack test-3-$packname_3.pack
diff --git a/t/chainlint/sqstring-in-sqstring.test b/t/chainlint/sqstring-in-sqstring.test
new file mode 100644
index 0000000000..24169724a5
--- /dev/null
+++ b/t/chainlint/sqstring-in-sqstring.test
@@ -0,0 +1,7 @@
+test_expect_success 'sqstring-in-sqstring' '
+# LINT: SQ-string Perl code fragment within SQ-string
+perl -e '\''
+ defined($_ = -s $_) or die for @ARGV;
+ exit 1 if $ARGV[0] <= $ARGV[1];
+'\'' test-2-$packname_2.pack test-3-$packname_3.pack
+'
diff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect
new file mode 100644
index 0000000000..e450caf948
--- /dev/null
+++ b/t/chainlint/subshell-here-doc.expect
@@ -0,0 +1,30 @@
+2 (
+3 echo wobba \
+4 gorgo snoot \
+5 wafta snurb <<-EOF &&
+6 quoth the raven,
+7 nevermore...
+8 EOF
+9
+10 cat <<EOF >bip ?!LINT: missing '&&'?!
+11 fish fly high
+12 EOF
+13
+14 echo <<-\EOF >bop
+15 gomez
+16 morticia
+17 wednesday
+18 pugsly
+19 EOF
+20 ) &&
+21 (
+22 cat <<-\ARBITRARY >bup &&
+23 glink
+24 FIZZ
+25 ARBITRARY
+26 cat <<-"ARBITRARY3" >bup3 &&
+27 glink
+28 FIZZ
+29 ARBITRARY3
+30 meep
+31 )
diff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test
new file mode 100644
index 0000000000..4a38f47f01
--- /dev/null
+++ b/t/chainlint/subshell-here-doc.test
@@ -0,0 +1,37 @@
+test_expect_success 'subshell-here-doc' '
+(
+# LINT: stitch together incomplete \-ending lines
+# LINT: swallow here-doc to avoid false positives in content
+ echo wobba \
+ gorgo snoot \
+ wafta snurb <<-EOF &&
+ quoth the raven,
+ nevermore...
+ EOF
+
+# LINT: missing "&&" on "cat"
+ cat <<EOF >bip
+ fish fly high
+EOF
+
+# LINT: swallow here-doc (EOF is last line of subshell)
+ echo <<-\EOF >bop
+ gomez
+ morticia
+ wednesday
+ pugsly
+ EOF
+) &&
+(
+# LINT: swallow here-doc with arbitrary tag
+ cat <<-\ARBITRARY >bup &&
+ glink
+ FIZZ
+ ARBITRARY
+ cat <<-"ARBITRARY3" >bup3 &&
+ glink
+ FIZZ
+ ARBITRARY3
+ meep
+)
+'
diff --git a/t/chainlint/subshell-one-liner.expect b/t/chainlint/subshell-one-liner.expect
new file mode 100644
index 0000000000..265d996a21
--- /dev/null
+++ b/t/chainlint/subshell-one-liner.expect
@@ -0,0 +1,19 @@
+2 (
+3 (foo && bar) &&
+4 (foo && bar) |
+5 (foo && bar) >baz &&
+6
+7 (foo; ?!LINT: missing '&&'?! bar) &&
+8 (foo; ?!LINT: missing '&&'?! bar) |
+9 (foo; ?!LINT: missing '&&'?! bar) >baz &&
+10
+11 (foo || exit 1) &&
+12 (foo || exit 1) |
+13 (foo || exit 1) >baz &&
+14
+15 (foo && bar) ?!LINT: missing '&&'?!
+16
+17 (foo && bar; ?!LINT: missing '&&'?! baz) ?!LINT: missing '&&'?!
+18
+19 foobar
+20 )
diff --git a/t/chainlint/subshell-one-liner.test b/t/chainlint/subshell-one-liner.test
new file mode 100644
index 0000000000..dac536afcc
--- /dev/null
+++ b/t/chainlint/subshell-one-liner.test
@@ -0,0 +1,26 @@
+test_expect_success 'subshell-one-liner' '
+(
+# LINT: nested one-liner subshell
+ (foo && bar) &&
+ (foo && bar) |
+ (foo && bar) >baz &&
+
+# LINT: nested one-liner subshell missing internal "&&"
+ (foo; bar) &&
+ (foo; bar) |
+ (foo; bar) >baz &&
+
+# LINT: nested one-liner subshell with "|| exit"
+ (foo || exit 1) &&
+ (foo || exit 1) |
+ (foo || exit 1) >baz &&
+
+# LINT: nested one-liner subshell lacking ending "&&"
+ (foo && bar)
+
+# LINT: nested one-liner subshell missing internal "&&" and lacking ending "&&"
+ (foo && bar; baz)
+
+ foobar
+)
+'
diff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect
new file mode 100644
index 0000000000..9e60338bcf
--- /dev/null
+++ b/t/chainlint/t7900-subtree.expect
@@ -0,0 +1,22 @@
+2 (
+3 chks="sub1
+4 sub2
+5 sub3
+6 sub4" &&
+7 chks_sub=$(cat <<TXT | sed "s,^,sub dir/,"
+8 $chks
+9 TXT
+10 ) &&
+11 chkms="main-sub1
+12 main-sub2
+13 main-sub3
+14 main-sub4" &&
+15 chkms_sub=$(cat <<TXT | sed "s,^,sub dir/,"
+16 $chkms
+17 TXT
+18 ) &&
+19
+20 subfiles=$(git ls-files) &&
+21 check_equal "$subfiles" "$chkms
+22 $chks"
+23 )
diff --git a/t/chainlint/t7900-subtree.test b/t/chainlint/t7900-subtree.test
new file mode 100644
index 0000000000..1f4f03300f
--- /dev/null
+++ b/t/chainlint/t7900-subtree.test
@@ -0,0 +1,24 @@
+test_expect_success 't7900-subtree' '
+(
+ chks="sub1
+sub2
+sub3
+sub4" &&
+ chks_sub=$(cat <<TXT | sed "s,^,sub dir/,"
+$chks
+TXT
+) &&
+ chkms="main-sub1
+main-sub2
+main-sub3
+main-sub4" &&
+ chkms_sub=$(cat <<TXT | sed "s,^,sub dir/,"
+$chkms
+TXT
+) &&
+
+ subfiles=$(git ls-files) &&
+ check_equal "$subfiles" "$chkms
+$chks"
+)
+'
diff --git a/t/chainlint/token-pasting.expect b/t/chainlint/token-pasting.expect
new file mode 100644
index 0000000000..387189b6de
--- /dev/null
+++ b/t/chainlint/token-pasting.expect
@@ -0,0 +1,27 @@
+2 git config filter.rot13.smudge ./rot13.sh &&
+3 git config filter.rot13.clean ./rot13.sh &&
+4
+5 {
+6 echo "*.t filter=rot13" ?!LINT: missing '&&'?!
+7 echo "*.i ident"
+8 } >.gitattributes &&
+9
+10 {
+11 echo a b c d e f g h i j k l m ?!LINT: missing '&&'?!
+12 echo n o p q r s t u v w x y z ?!LINT: missing '&&'?!
+13 echo '$Id$'
+14 } >test &&
+15 cat test >test.t &&
+16 cat test >test.o &&
+17 cat test >test.i &&
+18 git add test test.t test.i &&
+19 rm -f test test.t test.i &&
+20 git checkout -- test test.t test.i &&
+21
+22 echo "content-test2" >test2.o &&
+23 echo "content-test3 - filename with special characters" >"test3 'sq',$x=.o" ?!LINT: missing '&&'?!
+24
+25 downstream_url_for_sed=$(
+26 printf "%sn" "$downstream_url" |
+27 sed -e 's/\/\\/g' -e 's/[[/.*^$]/\&/g'
+28 )
diff --git a/t/chainlint/token-pasting.test b/t/chainlint/token-pasting.test
new file mode 100644
index 0000000000..590914b733
--- /dev/null
+++ b/t/chainlint/token-pasting.test
@@ -0,0 +1,34 @@
+test_expect_success 'token-pasting' '
+# LINT: single token; composite of multiple strings
+git config filter.rot13.smudge ./rot13.sh &&
+git config filter.rot13.clean ./rot13.sh &&
+
+{
+ echo "*.t filter=rot13"
+ echo "*.i ident"
+} >.gitattributes &&
+
+{
+ echo a b c d e f g h i j k l m
+ echo n o p q r s t u v w x y z
+# LINT: exit/enter string context and escaped-quote outside of string
+ echo '\''$Id$'\''
+} >test &&
+cat test >test.t &&
+cat test >test.o &&
+cat test >test.i &&
+git add test test.t test.i &&
+rm -f test test.t test.i &&
+git checkout -- test test.t test.i &&
+
+echo "content-test2" >test2.o &&
+# LINT: exit/enter string context and escaped-quote outside of string
+echo "content-test3 - filename with special characters" >"test3 '\''sq'\'',\$x=.o"
+
+# LINT: single token; composite of multiple strings
+downstream_url_for_sed=$(
+ printf "%s\n" "$downstream_url" |
+# LINT: exit/enter string context; "&" inside string not command terminator
+ sed -e '\''s/\\/\\\\/g'\'' -e '\''s/[[/.*^$]/\\&/g'\''
+)
+'
diff --git a/t/chainlint/unclosed-here-doc-indent.expect b/t/chainlint/unclosed-here-doc-indent.expect
new file mode 100644
index 0000000000..156906c85a
--- /dev/null
+++ b/t/chainlint/unclosed-here-doc-indent.expect
@@ -0,0 +1,4 @@
+2 command_which_is_run &&
+3 cat >expect <<-\EOF ?!LINT: unclosed heredoc?! &&
+4 we forget to end the here-doc
+5 command_which_is_gobbled
diff --git a/t/chainlint/unclosed-here-doc-indent.test b/t/chainlint/unclosed-here-doc-indent.test
new file mode 100644
index 0000000000..7ac9d0f7d7
--- /dev/null
+++ b/t/chainlint/unclosed-here-doc-indent.test
@@ -0,0 +1,6 @@
+test_expect_success 'unclosed-here-doc-indent' '
+command_which_is_run &&
+cat >expect <<-\EOF &&
+we forget to end the here-doc
+command_which_is_gobbled
+'
diff --git a/t/chainlint/unclosed-here-doc.expect b/t/chainlint/unclosed-here-doc.expect
new file mode 100644
index 0000000000..752c608862
--- /dev/null
+++ b/t/chainlint/unclosed-here-doc.expect
@@ -0,0 +1,7 @@
+2 command_which_is_run &&
+3 cat >expect <<\EOF ?!LINT: unclosed heredoc?! &&
+4 we try to end the here-doc below,
+5 but the indentation throws us off
+6 since the operator is not "<<-".
+7 EOF
+8 command_which_is_gobbled
diff --git a/t/chainlint/unclosed-here-doc.test b/t/chainlint/unclosed-here-doc.test
new file mode 100644
index 0000000000..68e78f06f3
--- /dev/null
+++ b/t/chainlint/unclosed-here-doc.test
@@ -0,0 +1,9 @@
+test_expect_success 'unclosed-here-doc' '
+command_which_is_run &&
+cat >expect <<\EOF &&
+ we try to end the here-doc below,
+ but the indentation throws us off
+ since the operator is not "<<-".
+ EOF
+command_which_is_gobbled
+'
diff --git a/t/chainlint/while-loop.expect b/t/chainlint/while-loop.expect
new file mode 100644
index 0000000000..2ba5582165
--- /dev/null
+++ b/t/chainlint/while-loop.expect
@@ -0,0 +1,14 @@
+2 (
+3 while true
+4 do
+5 echo foo ?!LINT: missing '&&'?!
+6 cat <<-\EOF ?!LINT: missing '|| exit 1'?!
+7 bar
+8 EOF
+9 done ?!LINT: missing '&&'?!
+10
+11 while true; do
+12 echo foo &&
+13 cat bar ?!LINT: missing '|| exit 1'?!
+14 done
+15 )
diff --git a/t/chainlint/while-loop.test b/t/chainlint/while-loop.test
new file mode 100644
index 0000000000..33a201906a
--- /dev/null
+++ b/t/chainlint/while-loop.test
@@ -0,0 +1,21 @@
+test_expect_success 'while-loop' '
+(
+# LINT: "while", "do", "done" do not need "&&"
+ while true
+ do
+# LINT: missing "&&" on "echo"
+ echo foo
+# LINT: last statement of while does not need "&&"
+ cat <<-\EOF
+ bar
+ EOF
+# LINT: missing "&&" on "done"
+ done
+
+# LINT: "do" on same line as "while"
+ while true; do
+ echo foo &&
+ cat bar
+ done
+)
+'