diff mbox series

[v3,5/5] branch.c: replace questionable exit() codes

Message ID 20211209184928.71413-6-chooglen@google.com (mailing list archive)
State Superseded
Headers show
Series implement branch --recurse-submodules | expand

Commit Message

Glen Choo Dec. 9, 2021, 6:49 p.m. UTC
Replace exit() calls in branch.c that have questionable exit codes:

* in setup_tracking(), exit(-1) was introduced in 27852b2c53 (branch:
  report errors in tracking branch setup, 2016-02-22). This may have
  been a mechanical typo because the same commit changes the return type
  of setup_tracking() from int to void.

* in validate_branch_start(), the exit code changes depending on whether
  or not advice is enabled. This behavior was not discussed
  upstream (see caa2036b3b (branch: give advice when tracking
  start-point is missing, 2013-04-02)).

Signed-off-by: Glen Choo <chooglen@google.com>
---
I don't know what the 'correct' exit codes should be, only that Junio
makes a good case that the existing exit codes are wrong. My best,
non-prescriptive, choice is 128, to be consistent with the surrounding
code and Documentation/technical/api-error-handling.txt.

 branch.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Comments

Ævar Arnfjörð Bjarmason Dec. 10, 2021, 2:21 a.m. UTC | #1
On Thu, Dec 09 2021, Glen Choo wrote:

> Replace exit() calls in branch.c that have questionable exit codes:
>
> * in setup_tracking(), exit(-1) was introduced in 27852b2c53 (branch:
>   report errors in tracking branch setup, 2016-02-22). This may have
>   been a mechanical typo because the same commit changes the return type
>   of setup_tracking() from int to void.
>
> * in validate_branch_start(), the exit code changes depending on whether
>   or not advice is enabled. This behavior was not discussed
>   upstream (see caa2036b3b (branch: give advice when tracking
>   start-point is missing, 2013-04-02)).
>
> Signed-off-by: Glen Choo <chooglen@google.com>
> ---
> I don't know what the 'correct' exit codes should be, only that Junio
> makes a good case that the existing exit codes are wrong. My best,
> non-prescriptive, choice is 128, to be consistent with the surrounding
> code and Documentation/technical/api-error-handling.txt.
>
>  branch.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/branch.c b/branch.c
> index 305154de0b..ad70ddd120 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -324,7 +324,7 @@ static void validate_branch_start(struct repository *r, const char *start_name,
>  			if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {
>  				error(_(upstream_missing), start_name);
>  				advise(_(upstream_advice));
> -				exit(1);
> +				exit(128);
>  			}
>  			die(_(upstream_missing), start_name);
>  		}
> @@ -398,7 +398,7 @@ void setup_tracking(const char *new_ref, const char *orig_ref,
>  		string_list_append(tracking.srcs, full_orig_ref);
>  	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
>  			      tracking.srcs) < 0)
> -		exit(-1);
> +		exit(128);
>  
>  cleanup:
>  	string_list_clear(tracking.srcs, 0);

Junio noted in <xmqqbl1tcptq.fsf@gitster.g>:
    
    This is not a problem with this patch, and it should not be fixed as
    part of this series, but since I noticed it, I'll mention it as a
    leftover low-hanging fruit to be fixed after the dust settles.  The
    exit(1) looks wrong.  We should exit with 128 just like die() does.
    Issuing of an advice message should not affect the exit code.

I think it's good to fix these inconsistencies, but also that we
shouldn't be doing it as part of this series, or does it conflict in
some way that's hard to untangle?

FWIW the former hunk is a perfect candidate for the new die_message()
function[1]. I.e. we should be doing:

    int code = die_message(_(upsream_missing), start_name);
    if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))
        advise(_(upstream_advice));
    exit(code);

That we print an "error" when giving the advice but "fatal" when not is
really UX wart, and also that the exit code differs.

The latter should really be "exit(1)", not 128. We should reserve that
for die(). FWIW I had some local hacks to detect all these cases of exit
-1 via the test suite, they're almost all cases where we want to exit
with 1, but just conflated an error() return value with a return from
main() (or exit).

1. https://lore.kernel.org/git/cover-v2-0.6-00000000000-20211207T182419Z-avarab@gmail.com/#t
Glen Choo Dec. 10, 2021, 5:43 p.m. UTC | #2
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Thu, Dec 09 2021, Glen Choo wrote:
>
>> Replace exit() calls in branch.c that have questionable exit codes:
>>
>> * in setup_tracking(), exit(-1) was introduced in 27852b2c53 (branch:
>>   report errors in tracking branch setup, 2016-02-22). This may have
>>   been a mechanical typo because the same commit changes the return type
>>   of setup_tracking() from int to void.
>>
>> * in validate_branch_start(), the exit code changes depending on whether
>>   or not advice is enabled. This behavior was not discussed
>>   upstream (see caa2036b3b (branch: give advice when tracking
>>   start-point is missing, 2013-04-02)).
>>
>> Signed-off-by: Glen Choo <chooglen@google.com>
>> ---
>> I don't know what the 'correct' exit codes should be, only that Junio
>> makes a good case that the existing exit codes are wrong. My best,
>> non-prescriptive, choice is 128, to be consistent with the surrounding
>> code and Documentation/technical/api-error-handling.txt.
>>
>>  branch.c | 4 ++--
>>  1 file changed, 2 insertions(+), 2 deletions(-)
>>
>> diff --git a/branch.c b/branch.c
>> index 305154de0b..ad70ddd120 100644
>> --- a/branch.c
>> +++ b/branch.c
>> @@ -324,7 +324,7 @@ static void validate_branch_start(struct repository *r, const char *start_name,
>>  			if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {
>>  				error(_(upstream_missing), start_name);
>>  				advise(_(upstream_advice));
>> -				exit(1);
>> +				exit(128);
>>  			}
>>  			die(_(upstream_missing), start_name);
>>  		}
>> @@ -398,7 +398,7 @@ void setup_tracking(const char *new_ref, const char *orig_ref,
>>  		string_list_append(tracking.srcs, full_orig_ref);
>>  	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
>>  			      tracking.srcs) < 0)
>> -		exit(-1);
>> +		exit(128);
>>  
>>  cleanup:
>>  	string_list_clear(tracking.srcs, 0);
>
> Junio noted in <xmqqbl1tcptq.fsf@gitster.g>:
>     
>     This is not a problem with this patch, and it should not be fixed as
>     part of this series, but since I noticed it, I'll mention it as a
>     leftover low-hanging fruit to be fixed after the dust settles.  The
>     exit(1) looks wrong.  We should exit with 128 just like die() does.
>     Issuing of an advice message should not affect the exit code.
>
> I think it's good to fix these inconsistencies, but also that we
> shouldn't be doing it as part of this series, or does it conflict in
> some way that's hard to untangle?

There isn't any conflict. Probably a leftover habit from previous
projects, but I thought that this would be right time to clean up. Looks
like we think it'll be better to clean this up in a separate series, so
I'll do that instead.

> FWIW the former hunk is a perfect candidate for the new die_message()
> function[1]. I.e. we should be doing:
>
>     int code = die_message(_(upsream_missing), start_name);
>     if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))
>         advise(_(upstream_advice));
>     exit(code);
>
> That we print an "error" when giving the advice but "fatal" when not is
> really UX wart, and also that the exit code differs.

Ah, thanks!

> The latter should really be "exit(1)", not 128. We should reserve that
> for die().

Thanks, this is exactly what I was looking for guidance on.
Documentation/technical/api-error-handling.txt is silent on what exit
code to use when a command does 90% of what the caller wants (so it's
not really an application error) but fails on the 10% that the user
doesn't care so much about - in this case, creating a branch but failing
to setup tracking.

> FWIW I had some local hacks to detect all these cases of exit -1 via
> the test suite, they're almost all cases where we want to exit with 1,
> but just conflated an error() return value with a return from main()
> (or exit).

Yes, this sounds like what happened here.
Junio C Hamano Dec. 13, 2021, 9:02 a.m. UTC | #3
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> The latter should really be "exit(1)", not 128. We should reserve that
> for die().

Is it because install_branch_config_multiple_remotes() gives enough
information to the user that the caller exits without its own
message?  In other words, are messages given by the callee to the
users are morally equivalent to what the caller would call die()
with, if the callee were silent?  If so, 128 is perfectly fine.  If
not, 1 or anything positive that is not 128 may be more appropriate.

Either case, -1 is a definite no-no.
Ævar Arnfjörð Bjarmason Dec. 13, 2021, 9:19 a.m. UTC | #4
On Mon, Dec 13 2021, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> The latter should really be "exit(1)", not 128. We should reserve that
>> for die().
>
> Is it because install_branch_config_multiple_remotes() gives enough
> information to the user that the caller exits without its own
> message?  In other words, are messages given by the callee to the
> users are morally equivalent to what the caller would call die()
> with, if the callee were silent?  If so, 128 is perfectly fine.  If
> not, 1 or anything positive that is not 128 may be more appropriate.

We don't really document this outside of this tidbit:
    
    Documentation/technical/api-error-handling.txt-- `die` is for fatal application errors.  It prints a message to
    Documentation/technical/api-error-handling.txt:  the user and exits with status 128.
    Documentation/technical/api-error-handling.txt-
    Documentation/technical/api-error-handling.txt-- `usage` is for errors in command line usage.  After printing its
    Documentation/technical/api-error-handling.txt-  message, it exits with status 129.  (See also `usage_with_options`
    Documentation/technical/api-error-handling.txt-  in the link:api-parse-options.html[parse-options API].)

But while that doesn't say that you can *only* use 128 for die, and I
wouldn't consider the existing code that calls exit(128) in dire need of
a change, most of the builtins simply return with 1 for generic errors,
and reserve 128 for die()..

So for any new code it makes sense to follow that convention.

> Either case, -1 is a definite no-no.

I've got a local WIP patch to fix those that I can polish up, if you're
interested. It's the result of adding the below & running the test suite
against it:

diff --git a/git.c b/git.c
index 60c2784be45..d6bdb3571df 100644
--- a/git.c
+++ b/git.c
@@ -419,6 +419,7 @@ static int handle_alias(int *argcp, const char ***argv)
 static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
 {
 	int status, help;
+	int posix_status;
 	struct stat st;
 	const char *prefix;
 
@@ -459,6 +460,9 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
 
 	validate_cache_entries(the_repository->index);
 	status = p->fn(argc, argv, prefix);
+	posix_status = status & 0xFF;
+	if (status != posix_status)
+		BUG("got status %d which will be cast to %d, returning error() perhaps?", status, posix_status);
 	validate_cache_entries(the_repository->index);
 
 	if (status)
Junio C Hamano Dec. 13, 2021, 7:26 p.m. UTC | #5
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Mon, Dec 13 2021, Junio C Hamano wrote:
>
>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>
>>> The latter should really be "exit(1)", not 128. We should reserve that
>>> for die().
>>
>> Is it because install_branch_config_multiple_remotes() gives enough
>> information to the user that the caller exits without its own
>> message?  In other words, are messages given by the callee to the
>> users are morally equivalent to what the caller would call die()
>> with, if the callee were silent?  If so, 128 is perfectly fine.  If
>> not, 1 or anything positive that is not 128 may be more appropriate.
>
> We don't really document this outside of this tidbit:
>     
>     Documentation/technical/api-error-handling.txt-- `die` is for fatal application errors.  It prints a message to
>     Documentation/technical/api-error-handling.txt:  the user and exits with status 128.
>     Documentation/technical/api-error-handling.txt-
>     Documentation/technical/api-error-handling.txt-- `usage` is for errors in command line usage.  After printing its
>     Documentation/technical/api-error-handling.txt-  message, it exits with status 129.  (See also `usage_with_options`
>     Documentation/technical/api-error-handling.txt-  in the link:api-parse-options.html[parse-options API].)
>
> But while that doesn't say that you can *only* use 128 for die, and I
> wouldn't consider the existing code that calls exit(128) in dire need of
> a change, most of the builtins simply return with 1 for generic errors,
> and reserve 128 for die()..
>
> So for any new code it makes sense to follow that convention.

Only when they are not calling die() for some technical reasons,
though.  IOW, if you would have called die() if you could, that is a
good indication that you would want to consistently use 128.

Capturing return value from die_message(), giving more message and
then dying with the 128 squarely falls into that pattern.  I am not
sure if the install_branch_config_multiple_remotes() case falls into
it, though.

And more importantly in the context of this topic, I am not
convinced install_branch_config_multiple_remotes() helper function
itself is a good idea to begin with.  It is to handle a case where
branch.$name.remote is set multiple times right?

This is because I do not think I saw anybody defined the right
semantics during the discussion (or written in the documentation)
and explained why being able to do so makes sense in the first
place, and it is not known if it makes sense to copy such a
configuration blindly to a new branch.  If it punts without doing
anything with a warning(), or calls a die(), it would be a more
sensible first step for this topic.  Users with real need for such a
configuration will then come to us with real use case, and what they
need may turn out to be something different from a blind copying of
the original.

Thanks.
diff mbox series

Patch

diff --git a/branch.c b/branch.c
index 305154de0b..ad70ddd120 100644
--- a/branch.c
+++ b/branch.c
@@ -324,7 +324,7 @@  static void validate_branch_start(struct repository *r, const char *start_name,
 			if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) {
 				error(_(upstream_missing), start_name);
 				advise(_(upstream_advice));
-				exit(1);
+				exit(128);
 			}
 			die(_(upstream_missing), start_name);
 		}
@@ -398,7 +398,7 @@  void setup_tracking(const char *new_ref, const char *orig_ref,
 		string_list_append(tracking.srcs, full_orig_ref);
 	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
 			      tracking.srcs) < 0)
-		exit(-1);
+		exit(128);
 
 cleanup:
 	string_list_clear(tracking.srcs, 0);