{"thread":{"id":"54512","subject":"[PATCH 0/2] fix error handling in checkout-index","startedAt":"2020-10-27T07:30:02Z","lastAt":"2020-10-27T20:28:02Z","messageCount":5,"participants":["Jeff King","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"408491","messageId":"20201027073000.GA3651896@coredump.intra.peff.net","threadId":"54512","inReplyTo":null,"subject":"[PATCH 0/2] fix error handling in checkout-index","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-27T07:30:00Z","receivedAt":"2020-10-27T07:30:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"While working on another topic, I noticed that \"git checkout-index --\npath\" does not propagate errors through its exit code. It has been that\nway for so long that I almost wondered if it is intentional, but I'm\npretty sure it's not. A bit scary, though. :)\n\n  [1/2]: checkout-index: drop error message from empty --stage=all\n  [2/2]: checkout-index: propagate errors to exit code\n\n builtin/checkout-index.c        | 16 ++++++++++++++--\n t/t2004-checkout-cache-temp.sh  | 10 +++++++++-\n t/t2006-checkout-index-basic.sh | 11 +++++++++++\n 3 files changed, 34 insertions(+), 3 deletions(-)\n\n-Peff\n"},{"id":"408492","messageId":"20201027073602.GA3794137@coredump.intra.peff.net","threadId":"54512","inReplyTo":"20201027073000.GA3651896@coredump.intra.peff.net","subject":"[PATCH 1/2] checkout-index: drop error message from empty --stage=all","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-27T07:36:02Z","receivedAt":"2020-10-27T07:36:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"If checkout-index is given --stage=all for a specific path, it will try\nto write stages 1-3 (if present) for that path to temporary files.\nHowever, if the file is present only at stage 0, it writes nothing but\ngives a confusing message:\n\n  $ git checkout-index --stage=all -- Makefile\n  git checkout-index: Makefile does not exist at stage 4\n\nThis is nonsense. There is no stage 4 (it's just an internal enum value\nwe use for \"all\"), and the documentation clearly states:\n\n  Paths which only have a stage 0 entry will always be omitted from the\n  output.\n\nHere it's talking about the list of tempfiles written to stdout, but it\nseems clear that this case was not meant to be an error. We even have a\ntest which covers it, but it only checks that the command reports an\nexit code of 0, not its stderr. And it reports 0 only because of another\nbug which fails to propagate errors (which will be fixed in a subsequent\npatch).\n\nSo let's make the test more thorough. We'll also cover the case that we\nfound _no_ entry, not even a stage zero, which should still be an error.\nHowever, because of the other bug, we'll have to mark this as expecting\nfailure for the moment.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n builtin/checkout-index.c       |  8 ++++++++\n t/t2004-checkout-cache-temp.sh | 10 +++++++++-\n 2 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex a854fd16e7..195165d8bd 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -79,6 +79,14 @@ static int checkout_file(const char *name, const char *prefix)\n \t\treturn errs > 0 ? -1 : 0;\n \t}\n \n+\t/*\n+\t * At this point we know we didn't try to check anything out. If it was\n+\t * because we did find an entry but it was stage 0, that's not an\n+\t * error.\n+\t */\n+\tif (has_same_name && checkout_stage == CHECKOUT_ALL)\n+\t\treturn 0;\n+\n \tif (!state.quiet) {\n \t\tfprintf(stderr, \"git checkout-index: %s \", name);\n \t\tif (!has_same_name)\ndiff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh\nindex a12afe93f3..4308d698b9 100755\n--- a/t/t2004-checkout-cache-temp.sh\n+++ b/t/t2004-checkout-cache-temp.sh\n@@ -88,9 +88,17 @@ test_expect_success 'checkout all stage 2 to temporary files' '\n \tdone\n '\n \n+test_expect_failure 'checkout all stages of unknown path' '\n+\trm -f path* .merge_* actual &&\n+\ttest_must_fail git checkout-index --stage=all --temp \\\n+\t\t-- does-not-exist 2>stderr &&\n+\ttest_i18ngrep not.in.the.cache stderr\n+'\n+\n test_expect_success 'checkout all stages/one file to nothing' '\n \trm -f path* .merge_* actual &&\n-\tgit checkout-index --stage=all --temp -- path0 >actual &&\n+\tgit checkout-index --stage=all --temp -- path0 >actual 2>stderr &&\n+\ttest_must_be_empty stderr &&\n \ttest_line_count = 0 actual\n '\n \n-- \n2.29.1.634.g9e41dc1bf2\n\n"},{"id":"408493","messageId":"20201027073714.GB3794137@coredump.intra.peff.net","threadId":"54512","inReplyTo":"20201027073000.GA3651896@coredump.intra.peff.net","subject":"[PATCH 2/2] checkout-index: propagate errors to exit code","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-27T07:37:14Z","receivedAt":"2020-10-27T07:37:17Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"If we encounter an error while checking out an explicit path, we print a\nmessage to stderr but do not actually exit with a non-zero code. While\nthis is a plumbing command and the behavior goes all the way back to\n33db5f4d90 (Add a \"checkout-cache\" command which does what the name\nsuggests., 2005-04-09), this is almost certainly an oversight:\n\n  - we _do_ return an exit code from checkout_file(); the caller just\n    never reads it\n\n  - errors while checking out all paths (with \"-a\") do result in a\n    non-zero exit code.\n\n  - it would be quite unusual not to use the exit code for an error,\n    as otherwise the caller has no idea the command failed except by\n    scraping stderr\n\nTo keep our tests simple and portable, we can use the most obvious\nerror: asking to checkout a path which is not in the index at all.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n builtin/checkout-index.c        |  8 ++++++--\n t/t2004-checkout-cache-temp.sh  |  2 +-\n t/t2006-checkout-index-basic.sh | 11 +++++++++++\n 3 files changed, 18 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/checkout-index.c b/builtin/checkout-index.c\nindex 195165d8bd..4bbfc92dce 100644\n--- a/builtin/checkout-index.c\n+++ b/builtin/checkout-index.c\n@@ -167,6 +167,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \tint prefix_length;\n \tint force = 0, quiet = 0, not_new = 0;\n \tint index_opt = 0;\n+\tint err = 0;\n \tstruct option builtin_checkout_index_options[] = {\n \t\tOPT_BOOL('a', \"all\", &all,\n \t\t\tN_(\"check out all files in the index\")),\n@@ -231,7 +232,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \t\tif (read_from_stdin)\n \t\t\tdie(\"git checkout-index: don't mix '--stdin' and explicit filenames\");\n \t\tp = prefix_path(prefix, prefix_length, arg);\n-\t\tcheckout_file(p, prefix);\n+\t\terr |= checkout_file(p, prefix);\n \t\tfree(p);\n \t}\n \n@@ -253,13 +254,16 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tstrbuf_swap(&buf, &unquoted);\n \t\t\t}\n \t\t\tp = prefix_path(prefix, prefix_length, buf.buf);\n-\t\t\tcheckout_file(p, prefix);\n+\t\t\terr |= checkout_file(p, prefix);\n \t\t\tfree(p);\n \t\t}\n \t\tstrbuf_release(&unquoted);\n \t\tstrbuf_release(&buf);\n \t}\n \n+\tif (err)\n+\t\treturn 1;\n+\n \tif (all)\n \t\tcheckout_all(prefix, prefix_length);\n \ndiff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh\nindex 4308d698b9..a9352b08a8 100755\n--- a/t/t2004-checkout-cache-temp.sh\n+++ b/t/t2004-checkout-cache-temp.sh\n@@ -88,7 +88,7 @@ test_expect_success 'checkout all stage 2 to temporary files' '\n \tdone\n '\n \n-test_expect_failure 'checkout all stages of unknown path' '\n+test_expect_success 'checkout all stages of unknown path' '\n \trm -f path* .merge_* actual &&\n \ttest_must_fail git checkout-index --stage=all --temp \\\n \t\t-- does-not-exist 2>stderr &&\ndiff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh\nindex 57cbdfe9bc..8e181dbf01 100755\n--- a/t/t2006-checkout-index-basic.sh\n+++ b/t/t2006-checkout-index-basic.sh\n@@ -21,4 +21,15 @@ test_expect_success 'checkout-index -h in broken repository' '\n \ttest_i18ngrep \"[Uu]sage\" broken/usage\n '\n \n+test_expect_success 'checkout-index reports errors (cmdline)' '\n+\ttest_must_fail git checkout-index -- does-not-exist 2>stderr &&\n+\ttest_i18ngrep not.in.the.cache stderr\n+'\n+\n+test_expect_success 'checkout-index reports errors (stdin)' '\n+\techo does-not-exist |\n+\ttest_must_fail git checkout-index --stdin 2>stderr &&\n+\ttest_i18ngrep not.in.the.cache stderr\n+'\n+\n test_done\n-- \n2.29.1.634.g9e41dc1bf2\n"},{"id":"408522","messageId":"xmqq1rhjcwjk.fsf@gitster.c.googlers.com","threadId":"54512","inReplyTo":"20201027073000.GA3651896@coredump.intra.peff.net","subject":"Re: [PATCH 0/2] fix error handling in checkout-index","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-27T19:13:03Z","receivedAt":"2020-10-27T19:13:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> While working on another topic, I noticed that \"git checkout-index --\n> path\" does not propagate errors through its exit code. It has been that\n> way for so long that I almost wondered if it is intentional, but I'm\n> pretty sure it's not. A bit scary, though. :)\n\nQuite honestly, at this point, I do not think the intention matters\nany more.  If somebody depends on the behaviour and wrote\n\n    do some thing &&\n    git checkout-index -- $path_that_is_possibly_missing &&\n    do another thing &&\n\nthen this change _will_ be a regression, whether it was originally\ndone this way on purpose or not.\n\nI do not think it is the kind of regression that we should avoid,\nthough.  I'd say that we should bite the bullet and fix it, as it\nshould also be easy to fix/adjust such a collateral damage.  That\nwould make the world a better place in the end.\n\nThanks.\n\n>   [1/2]: checkout-index: drop error message from empty --stage=all\n>   [2/2]: checkout-index: propagate errors to exit code\n>\n>  builtin/checkout-index.c        | 16 ++++++++++++++--\n>  t/t2004-checkout-cache-temp.sh  | 10 +++++++++-\n>  t/t2006-checkout-index-basic.sh | 11 +++++++++++\n>  3 files changed, 34 insertions(+), 3 deletions(-)\n>\n> -Peff\n"},{"id":"408528","messageId":"20201027202759.GA643305@coredump.intra.peff.net","threadId":"54512","inReplyTo":"xmqq1rhjcwjk.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 0/2] fix error handling in checkout-index","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-27T20:27:59Z","receivedAt":"2020-10-27T20:28:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 27, 2020 at 12:13:03PM -0700, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > While working on another topic, I noticed that \"git checkout-index --\n> > path\" does not propagate errors through its exit code. It has been that\n> > way for so long that I almost wondered if it is intentional, but I'm\n> > pretty sure it's not. A bit scary, though. :)\n> \n> Quite honestly, at this point, I do not think the intention matters\n> any more.  If somebody depends on the behaviour and wrote\n> \n>     do some thing &&\n>     git checkout-index -- $path_that_is_possibly_missing &&\n>     do another thing &&\n> \n> then this change _will_ be a regression, whether it was originally\n> done this way on purpose or not.\n> \n> I do not think it is the kind of regression that we should avoid,\n> though.  I'd say that we should bite the bullet and fix it, as it\n> should also be easy to fix/adjust such a collateral damage.  That\n> would make the world a better place in the end.\n\nRight, agreed with all of that. What I meant more with \"intentional\"\nwas: is there some really clever reason I was missing that it was done\nthis way in the first place? And I think the answer is \"no\", it was just\nan oversight.\n\nWhether it is OK to change a plumbing command's behavior is somewhat\northogonal, but I agree it a bug fix and OK to do here.\n\n-Peff\n"}]}