Subject: Re: [PATCH 1/3] rebase: avoid non-function use of "return" on FreeBSD

From: Junio C Hamano <[email-masked]>
Date: Thu, 17 Apr 2014 10:15:19 -0700

"Kyle J. McKay" <[email-masked]> writes:

> Either "return to the function that dot-sourced us" or "return from
> the dot command that dot-sourced us",

> but using the original wording
> implies to me that the function that dot-sourced us will return as
> soon as the dot-sourced script executes the return and that is exactly
> one of the bugs we're working around.

True. "return to the function that dot-sourced us" is what I meant.
To be more pedantic, "return to the point in the function that
dot-sourced this file".

> I think just the s/from/to/ would fix it so it does not give me the
> wrong impression, but that doesn't mean that would not confuse
> everyone else.  ;)

Yeah, let's do that.  Thanks for carefully reading them.

>> So "while this is allowed by POSIX" may be a bit misleading and
>> needs to be reworded, I guess?
>
> "allowed by POSIX" is not incorrect. ;)

Calling something that is required as "allowed" is a bit misleading,
if not outright dishonest.  Allowed implies that you are free not to
do so and can still be in compliance, but I do not think that is the
case.

I'd think it makes it clearer to say that we take "return stopping
the dot-sourced file" as a given and FreeBSD does not behave that
way.

-- >8 --
From: "Kyle J. McKay" <[email-masked]>
Date: Fri, 11 Apr 2014 01:28:17 -0700
Subject: [PATCH] rebase: avoid non-function use of "return" on FreeBSD

Since a1549e10, 15d4bf2e and 01a1e646 (first appearing in v1.8.4)
the git-rebase--*.sh scripts have used a "return" to stop execution
of the dot-sourced file and return to the "dot" command that
dot-sourced it.  The /bin/sh utility on FreeBSD however behaves
poorly under some circumstances when such a "return" is executed.

In particular, if the "dot" command is contained within a function,
then when a "return" is executed by the script it runs (that is not
itself inside a function), control will return from the function
that contains the "dot" command skipping any statements that might
follow the dot command inside that function.  Commit 99855ddf (first
appearing in v1.8.4.1) addresses this by making the "dot" command
the last line in the function.

Unfortunately the FreeBSD /bin/sh may also execute some statements
in the script run by the "dot" command that appear after the
troublesome "return".  The fix in 99855ddf does not address this
problem.

For example, if you have script1.sh with these contents:

run_script2() {
        . "$(dirname -- "$0")/script2.sh"
        _e=$?
        echo only this line should show
        [ $_e -eq 5 ] || echo expected status 5 got $_e
        return 3
}
run_script2
e=$?
[ $e -eq 3 ] || { echo expected status 3 got $e; exit 1; }

And script2.sh with these contents:

if [ 5 -gt 3 ]; then
        return 5
fi
case bad in *)
        echo always shows
esac
echo should not get here
! :

When running script1.sh (e.g. '/bin/sh script1.sh' or './script1.sh'
after making it executable), the expected output from a POSIX shell
is simply the single line:

only this line should show

However, when run using FreeBSD's /bin/sh, the following output
appears instead:

should not get here
expected status 3 got 1

Not only did the lines following the "dot" command in the run_script2
function in script1.sh get skipped, but additional lines in script2.sh
following the "return" got executed -- but not all of them (e.g. the
"echo always shows" line did not run).

These issues can be avoided by not using a top-level "return" in
script2.sh.  If script2.sh is changed to this:

main() {
        if [ 5 -gt 3 ]; then
                return 5
        fi
        case bad in *)
                echo always shows
        esac
        echo should not get here
        ! :
}
main

Then it behaves the same when using FreeBSD's /bin/sh as when using
other more POSIX compliant /bin/sh implementations.

We fix the git-rebase--*.sh scripts in a similar fashion by moving
the top-level code that contains "return" statements into its own
function and then calling that as the last line in the script.

Signed-off-by: Kyle J. McKay <[email-masked]>
Acked-by: Matthieu Moy <[email-masked]>
Signed-off-by: Junio C Hamano <[email-masked]>
---
 git-rebase--am.sh          | 15 +++++++++++++++
 git-rebase--interactive.sh | 15 +++++++++++++++
 git-rebase--merge.sh       | 15 +++++++++++++++
 3 files changed, 45 insertions(+)

diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index a4f683a..1cdc139 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -4,6 +4,17 @@
 # Copyright (c) 2010 Junio C Hamano.
 #
 
+# The whole contents of this file is run by dot-sourcing it from
+# inside a shell function.  It used to be that "return"s we see
+# below were not inside any function, and expected to return
+# to the function that dot-sourced us.
+#
+# However, FreeBSD /bin/sh misbehaves on such a construct and
+# continues to run the statements that follow such a "return".
+# As a work-around, we introduce an extra layer of a function
+# here, and immediately call it after defining it.
+git_rebase__am () {
+
 case "$action" in
 continue)
 	git am --resolved --resolvemsg="$resolvemsg" &&
@@ -73,3 +84,7 @@ then
 fi
 
 move_to_original_branch
+
+}
+# ... and then we call the whole thing.
+git_rebase__am
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 43631b4..9e1dd1e 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -810,6 +810,17 @@ add_exec_commands () {
 	mv "$1.new" "$1"
 }
 
+# The whole contents of this file is run by dot-sourcing it from
+# inside a shell function.  It used to be that "return"s we see
+# below were not inside any function, and expected to return
+# to the function that dot-sourced us.
+#
+# However, FreeBSD /bin/sh misbehaves on such a construct and
+# continues to run the statements that follow such a "return".
+# As a work-around, we introduce an extra layer of a function
+# here, and immediately call it after defining it.
+git_rebase__interactive () {
+
 case "$action" in
 continue)
 	# do we have anything to commit?
@@ -1042,3 +1053,7 @@ GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
 output git checkout $onto || die_abort "could not detach HEAD"
 git update-ref ORIG_HEAD $orig_head
 do_rest
+
+}
+# ... and then we call the whole thing.
+git_rebase__interactive
diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh
index e7d96de..838fbed 100644
--- a/git-rebase--merge.sh
+++ b/git-rebase--merge.sh
@@ -101,6 +101,17 @@ finish_rb_merge () {
 	say All done.
 }
 
+# The whole contents of this file is run by dot-sourcing it from
+# inside a shell function.  It used to be that "return"s we see
+# below were not inside any function, and expected to return
+# to the function that dot-sourced us.
+#
+# However, FreeBSD /bin/sh misbehaves on such a construct and
+# continues to run the statements that follow such a "return".
+# As a work-around, we introduce an extra layer of a function
+# here, and immediately call it after defining it.
+git_rebase__merge () {
+
 case "$action" in
 continue)
 	read_state
@@ -151,3 +162,7 @@ do
 done
 
 finish_rb_merge
+
+}
+# ... and then we call the whole thing.
+git_rebase__merge
-- 
1.9.2-630-gc3ddd0c