From: Eric Sunshine <sunshine@xxxxxxxxxxxxxx> This is a reroll of [1] which improves check-non-portable-shell's detection of one-shot environment variable assignment with shell functions. Changes since v1: * commit messages now state the behavior is undefined according to POSIX rather than focusing only on the original reason given (that the assignments could outlive the shell function invocation) * t3430 simplified by dropping the subshell altogether in favor of `test_commit --author` * new commit to improve the error message when one-shot assignment with shell function is detected [1]: https://lore.kernel.org/git/20240722065915.80760-1-ericsunshine@xxxxxxxxxxx/ Eric Sunshine (5): t3430: drop unnecessary one-shot "VAR=val shell-func" invocation t4034: fix use of one-shot variable assignment with shell function check-non-portable-shell: loosen one-shot assignment error message check-non-portable-shell: suggest alternative for `VAR=val shell-func` check-non-portable-shell: improve `VAR=val shell-func` detection t/check-non-portable-shell.pl | 4 ++-- t/t3430-rebase-merges.sh | 3 +-- t/t4034-diff-words.sh | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) Range-diff against v1: 1: 5bb6811f68 ! 1: 0d3c0593c9 t3430: modernize one-shot "VAR=val shell-func" invocation @@ Metadata Author: Eric Sunshine <sunshine@xxxxxxxxxxxxxx> ## Commit message ## - t3430: modernize one-shot "VAR=val shell-func" invocation + t3430: drop unnecessary one-shot "VAR=val shell-func" invocation - Unlike "VAR=val cmd" one-shot environment variable assignments which - exist only for the invocation of 'cmd', those assigned by "VAR=val - shell-func" exist within the running shell and continue to do so until - the process exits (or are explicitly unset). check-non-portable-shell.pl - warns when it detects such usage since, more often than not, the author - who writes such an invocation is unaware of the undesirable behavior. + The behavior of a one-shot environment variable assignment of the form + "VAR=val cmd" is undefined according to POSIX when "cmd" is a shell + function. Indeed the behavior differs between shell implementations and + even different versions of the same shell. One such ill-defined behavior + is that, with some shells, the assignment will outlive the invocation of + the function, thus may potentially impact subsequent commands in the + test, as well as subsequent tests. A common way to work around the + problem is to wrap a subshell around the one-shot assignment, thus + ensuring that the assignment is short-lived. - A common way to work around the problem is to wrap a subshell around the - variable assignments and function call, thus ensuring that the - assignments are short-lived. However, these days, a more ergonomic - approach is to employ test_env() which is tailor-made for this specific - use-case. + In this test, the subshell is employed precisely for this purpose; other + side-effects of the subshell, such as losing the effect of `test_tick` + which is invoked by `test_commit`, are immaterial. + + These days, we can take advantage of `test_commit --author` to more + clearly convey that the test is interested only in overriding the author + of the commit. Signed-off-by: Eric Sunshine <sunshine@xxxxxxxxxxxxxx> @@ t/t3430-rebase-merges.sh: test_expect_success 'refuse to merge ancestors of HEAD git checkout --orphan unrelated && - (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@xxxxxxxxxxx" \ - test_commit second-root) && -+ test_env GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@xxxxxxxxxxx" \ -+ test_commit second-root && ++ test_commit --author "Parsnip <root@xxxxxxxxxxx>" second-root && test_commit third-root && cat >script-from-scratch <<-\EOF && pick third-root 2: 1f35449847 ! 2: 19ee8295ef t4034: fix use of one-shot variable assignment with shell function @@ Metadata ## Commit message ## t4034: fix use of one-shot variable assignment with shell function - Unlike "VAR=val cmd" one-shot environment variable assignments which - exist only for the invocation of 'cmd', those assigned by "VAR=val - shell-func" exist within the running shell and continue to do so until - the process exits (or are explicitly unset). In most cases, it is - unlikely that this behavior was intended by the test author, and, even - if those leaked assignments do not impact other tests today, they can - negatively impact tests added later by authors unaware that the variable - assignments are still hanging around. Address this shortcoming by - ensuring that the assignments are short-lived. + The behavior of a one-shot environment variable assignment of the form + "VAR=val cmd" is undefined according to POSIX when "cmd" is a shell + function. Indeed the behavior differs between shell implementations and + even different versions of the same shell, thus should be avoided. Signed-off-by: Eric Sunshine <sunshine@xxxxxxxxxxxxxx> -: ---------- > 3: 220ca26d4f check-non-portable-shell: loosen one-shot assignment error message -: ---------- > 4: 4910756aab check-non-portable-shell: suggest alternative for `VAR=val shell-func` 3: 89621f72a2 ! 5: 7a15553a5a check-non-portable-shell: improve `VAR=val shell-func` detection @@ Metadata ## Commit message ## check-non-portable-shell: improve `VAR=val shell-func` detection - Unlike "VAR=val cmd" one-shot environment variable assignments which - exist only for the invocation of 'cmd', those assigned by "VAR=val - shell-func" exist within the running shell and continue to do so until - the process exits. check-non-portable-shell.pl warns when it detects - such usage since, more often than not, the author who writes such an - invocation is unaware of the undesirable behavior. + The behavior of a one-shot environment variable assignment of the form + "VAR=val cmd" is undefined according to POSIX when "cmd" is a shell + function. Indeed the behavior differs between shell implementations and + even different versions of the same shell, thus should be avoided. + As such, check-non-portable-shell.pl warns when it detects such usage. However, a limitation of the check is that it only detects such - invocations when variable assignment (i.e. `VAR=val`) is the first - thing on the line. Thus, it can easily be fooled by an invocation such - as: + invocations when variable assignment (i.e. `VAR=val`) is the first thing + on the line. Thus, it can easily be fooled by an invocation such as: echo X | VAR=val shell-func @@ t/check-non-portable-shell.pl: sub err { err q(quote "$val" in 'local var=$val'); - /^\s*([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and + /\b([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and !/test_env.+=/ and exists($func{$4}) and - err '"FOO=bar shell_func" assignment extends beyond "shell_func"'; + err '"FOO=bar shell_func" is not portable (use test_env FOO=bar shell_func)'; $line = ''; # this resets our $. for each file 4: 7b2e1dd895 < -: ---------- check-non-portable-shell: suggest alternative for `VAR=val shell-func` -- 2.45.2