{"thread":{"id":"59378","subject":"[PATCH] Add `restore.defaultLocation` option","startedAt":"2023-03-11T23:42:46Z","lastAt":"2023-03-26T10:53:56Z","messageCount":6,"participants":["Hugo Sales via GitGitGadget","Junio C Hamano","Hugo Sales"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"473412","messageId":"pull.1467.git.git.1678578153640.gitgitgadget@gmail.com","threadId":"59378","inReplyTo":null,"subject":"[PATCH] Add `restore.defaultLocation` option","fromName":"Hugo Sales via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-11T23:42:33Z","receivedAt":"2023-03-11T23:42:46Z","isPatch":true,"sender":{"key":"hugo@hsal.es","avatar":null},"body":"From: Hugo Sales <hugo@hsal.es>\n\nThis options allows control over which of `--worktree` or `--staged` is\napplied when `git restore` is invoked with neither\n\nThis patch is intended to reduce lost work to accidental `git restore .`\nwhen `git restore --staged .` was intended.\n\nSigned-off-by: Hugo Sales <hugo@hsal.es>\n---\n    Add restore.defaultLocation option\n    \n    This options allows control over which of --worktree or --staged is\n    applied when git restore is invoked with neither\n    \n    This patch is intended to reduce lost work to accidental git restore .\n    when git restore --staged . was intended.\n    \n    CC: Ævar Arnfjörð Bjarmason avarab@gmail.com, Jeff King peff@peff.net,\n    Victoria Dye vdye@github.com\n    \n    ------------------------------------------------------------------------\n    \n    I tried to send with git send-email, but I'm having problems. My mail\n    provider is mailbox.org and I'm getting Command unknown: 'AUTH' at\n    /usr/lib/git-core/git-send-email line 1691. Apologies if it actually\n    went through.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1467%2Fsomeonewithpc%2Fmaster-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1467/someonewithpc/master-v1\nPull-Request: https://github.com/git/git/pull/1467\n\n Documentation/config.txt         |   2 +\n Documentation/config/restore.txt |  13 ++++\n Documentation/git-restore.txt    |  17 +++--\n builtin/checkout.c               |  27 +++++++\n t/t2070-restore.sh               | 124 +++++++++++++++++++++++++++++++\n 5 files changed, 178 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/config/restore.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0e93aef8626..4359c63794e 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -501,6 +501,8 @@ include::config/repack.txt[]\n \n include::config/rerere.txt[]\n \n+include::config/restore.txt[]\n+\n include::config/revert.txt[]\n \n include::config/safe.txt[]\ndiff --git a/Documentation/config/restore.txt b/Documentation/config/restore.txt\nnew file mode 100644\nindex 00000000000..479fd13ca24\n--- /dev/null\n+++ b/Documentation/config/restore.txt\n@@ -0,0 +1,13 @@\n+restore.defaultLocation::\n+\tValid values: \"worktree\", \"staged\" or \"both\". Controls the default\n+\tbehavior of `git restore` without `--worktree` or `--staged`. If\n+\t\"worktree\", `git restore` without `--worktree` or `--staged` is\n+\tequivalent to `git restore --worktree`. If \"staged\", `git restore`\n+\twithout `--worktree` or `--staged` is equivalent to `git restore\n+\t--staged`. If \"both\", `git restore` without `--worktree` or `--staged`\n+\tis equivalent to `git restore --worktree --staged`. Adding an option\n+\toverrides the default, such that if the option is set to \"staged\",\n+\tspecifying `--worktree` will only affect the worktree, not both. This\n+\toption can be used to prevent accidentally losing work by running `git\n+\trestore .` when `git restore --staged .` was intended.\n+\tSee linkgit:git-restore[1]\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex 5964810caa4..28165861f55 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -14,14 +14,18 @@ SYNOPSIS\n \n DESCRIPTION\n -----------\n-Restore specified paths in the working tree with some contents from a\n+Restore specified paths in the working tree or index with some contents from a\n restore source. If a path is tracked but does not exist in the restore\n source, it will be removed to match the source.\n \n-The command can also be used to restore the content in the index with\n+The command can be used to restore the content in the index with\n `--staged`, or restore both the working tree and the index with\n `--staged --worktree`.\n \n+The config options `restore.defaultLocation`, which accepts values \"worktree\",\n+\"staged\" or \"both\", can be used to control the default behavior for which\n+flag(s) apply if neither `--staged` nor `--worktree` is supplied.\n+\n By default, if `--staged` is given, the contents are restored from `HEAD`,\n otherwise from the index. Use `--source` to restore from a different commit.\n \n@@ -59,9 +63,12 @@ all modified paths.\n --worktree::\n -S::\n --staged::\n-\tSpecify the restore location. If neither option is specified,\n-\tby default the working tree is restored. Specifying `--staged`\n-\twill only restore the index. Specifying both restores both.\n+\tSpecify the restore location. If neither option is specified, the\n+\tdefault depends on the `'restore.defaultLocation` config option, which\n+\tcan be \"worktree\" (the default), \"staged\" or \"both\", to control which of\n+\tthe two flags is assumed if none are given. Specifying `--worktree` will\n+\tonly restore the worktree. Specifying `--staged` will only restore the\n+\tindex. Specifying both restores both.\n \n -q::\n --quiet::\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex a5155cf55c1..5067753030b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1922,6 +1922,30 @@ int cmd_switch(int argc, const char **argv, const char *prefix)\n \treturn ret;\n }\n \n+static const char *checkout_default_index_worktree;\n+static int git_restore_config(const char *var, const char *value, void *cb)\n+{\n+\tstruct checkout_opts *opts = cb;\n+\n+\tif (!strcmp(var, \"restore.defaultlocation\")) {\n+\t\tgit_config_string(&checkout_default_index_worktree, var, value);\n+\n+\t\tif (!strcmp(checkout_default_index_worktree, \"both\")) {\n+\t\t\topts->checkout_index = -2;    /* default on */\n+\t\t\topts->checkout_worktree = -2; /* default on */\n+\t\t} else if (!strcmp(checkout_default_index_worktree, \"staged\")) {\n+\t\t\topts->checkout_index = -2;    /* default on */\n+\t\t\topts->checkout_worktree = -1; /* default off */\n+\t\t} else {\n+\t\t\topts->checkout_index = -1;    /* default off */\n+\t\t\topts->checkout_worktree = -2; /* default on */\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\treturn git_xmerge_config(var, value, NULL);\n+}\n+\n+\n int cmd_restore(int argc, const char **argv, const char *prefix)\n {\n \tstruct checkout_opts opts;\n@@ -1942,6 +1966,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix)\n \tstruct branch_info new_branch_info = { 0 };\n \n \tmemset(&opts, 0, sizeof(opts));\n+\n \topts.accept_ref = 0;\n \topts.accept_pathspec = 1;\n \topts.empty_pathspec_ok = 0;\n@@ -1950,6 +1975,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)\n \topts.checkout_worktree = -2; /* default on */\n \topts.ignore_unmerged_opt = \"--ignore-unmerged\";\n \n+\tgit_config(git_restore_config, &opts);\n+\n \toptions = parse_options_dup(restore_options);\n \toptions = add_common_options(&opts, options);\n \toptions = add_checkout_path_options(&opts, options);\ndiff --git a/t/t2070-restore.sh b/t/t2070-restore.sh\nindex 7c43ddf1d99..6e9b06e0bf4 100755\n--- a/t/t2070-restore.sh\n+++ b/t/t2070-restore.sh\n@@ -137,4 +137,128 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '\n \ttest_must_fail git rev-parse HEAD:new1\n '\n \n+test_expect_success 'restore with restore.defaultLocation unset works as if --worktree given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_commit root-unset-restore.defaultLocation &&\n+\ttest_commit unset-restore.defaultLocation one one &&\n+\t> one &&\n+\n+\tgit restore one &&\n+\ttest -z $(git status --porcelain --untracked-files=no) &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --worktree one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\ttest -z $(git status --porcelain --untracked-files=no)\n+'\n+\n+test_expect_success 'restore with restore.defaultLocation set to worktree works as if --worktree given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_when_finished git config --unset restore.defaultLocation &&\n+\ttest_commit root-worktree-restore.defaultLocation &&\n+\ttest_commit worktree-restore.defaultLocation one one &&\n+\tgit config restore.defaultLocation worktree &&\n+\t> one &&\n+\n+\tgit restore one &&\n+\ttest -z $(git status --porcelain --untracked-files=no) &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --worktree one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\ttest -z $(git status --porcelain --untracked-files=no)\n+'\n+\n+test_expect_success 'restore with restore.defaultLocation set to staged works as if --staged given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_when_finished git config --unset restore.defaultLocation &&\n+\ttest_commit root-staged-restore.defaultLocation &&\n+\ttest_commit staged-restore.defaultLocation one one &&\n+\tgit config restore.defaultLocation staged &&\n+\t> one &&\n+\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit add one &&\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit add one &&\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit restore --worktree one &&\n+\ttest -z $(git status --porcelain --untracked-files=no) &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\ttest -z $(git status --porcelain --untracked-files=no)\n+'\n+\n+test_expect_success 'restore with restore.defaultLocation set to both works as if --worktree --staged given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_when_finished git config --unset restore.defaultLocation &&\n+\ttest_commit root-both-restore.defaultLocation &&\n+\ttest_commit both-restore.defaultLocation one one &&\n+\tgit config restore.defaultLocation both &&\n+\t> one &&\n+\n+\tgit restore one &&\n+\ttest -z $(git status --porcelain --untracked-files=no) &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\"  &&\n+\n+\tgit add one &&\n+\tgit restore one &&\n+\ttest -z $(git status --porcelain --untracked-files=no) &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit restore --worktree one &&\n+\ttest -z $(git status --porcelain --untracked-files=no) &&\n+\n+\t> one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\ttest -z $(git status --porcelain --untracked-files=no)\n+'\n+\n+\n test_done\n\nbase-commit: 725f57037d81e24eacfda6e59a19c60c0b4c8062\n-- \ngitgitgadget\n"},{"id":"473443","messageId":"xmqqfsa9y7t5.fsf@gitster.g","threadId":"59378","inReplyTo":"pull.1467.git.git.1678578153640.gitgitgadget@gmail.com","subject":"Re: [PATCH] Add `restore.defaultLocation` option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-12T21:32:38Z","receivedAt":"2023-03-12T21:32:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Hugo Sales via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Hugo Sales <hugo@hsal.es>\n>\n> This options allows control over which of `--worktree` or `--staged` is\n> applied when `git restore` is invoked with neither\n\nWe do not want to do this.  Tutorials and documents will be written\nassuming the official default, and those users who set it to use a\ndifferent default, only because they are told to use it before they\nknow better, would waste a lot of time wondering why the procedures\nthey read from tutorials and documents would not work for them.\nEven an expert who is asked to help a novice, who has this variable\nset, would end up getting confused becaue \"git restore\" without the\nexplict options does not work as they expect.\n\nIndividual users are welcome to use \"[alias] ri = restore --staged\"\nand this will hurt nobody.\n"},{"id":"473465","messageId":"xmqqfsa8v8av.fsf@gitster.g","threadId":"59378","inReplyTo":"xmqqfsa9y7t5.fsf@gitster.g","subject":"Re: [PATCH] Add `restore.defaultLocation` option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-13T18:02:32Z","receivedAt":"2023-03-13T18:02:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Hugo Sales via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Hugo Sales <hugo@hsal.es>\n>>\n>> This options allows control over which of `--worktree` or `--staged` is\n>> applied when `git restore` is invoked with neither\n>\n> We do not want to do this.  Tutorials and documents will be written\n> assuming the official default, ...\n\nWell, I think this change is no different from any other\nconfiguration option and it may be OK.\n\nMy initial reaction primarily came from the fact that the choice\nbetween the index and the working tree is so fundamental aspect of\nthe behaviour of the command that people who wrote their script\nwould be relying on it not to change.  But given that the command is\nstill marked as experimental, as long as the new behaviour is\nclearly documented to warn those who care *not* to rely on the\ndefault behaviour and tell them to instead always give explicitly\nthese \"--worktree\" and/or \"--staged\" options, it would be OK.\n\nThis actually is a more important tangent, but if you think the\ncommand invites mistakes from users who forget to give \"--staged\",\nit may indicate that the command is too overloaded, and the UI might\nbe improved by removing the feature from this command and instead\nencouraging people to use \"git reset\" to futz with the contents of\nthe index.  I dunno.\n"},{"id":"473483","messageId":"xmqq1qlss0un.fsf@gitster.g","threadId":"59378","inReplyTo":"pull.1467.git.git.1678578153640.gitgitgadget@gmail.com","subject":"Re: [PATCH] Add `restore.defaultLocation` option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-13T23:11:44Z","receivedAt":"2023-03-13T23:11:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Hugo Sales via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Subject: Re: [PATCH] Add `restore.defaultLocation` option\n\nAs to the form, perhaps\n\n\tSubject: [PATCH] restore: make it configurable what gets updated\n\ncf. Documentation/SubmittingPatches[[describe-changes]]\n\nAlso on substance,\n\n * \"option\" usually refers to the --option given on the command\n   line; when you mean \"configuration variable\", it is a misleading\n   word to use.\n\n * \"restore\" command deals with two quite conceptually different\n   locations.  Where it gets the contents from (i.e. the source\n   location), and where the contents are used to update (i.e. the\n   destination location).  The \".defaultLocation\" is a poor name for\n   a configuration variable because it does not convey which one is\n   meant.\n\n> From: Hugo Sales <hugo@hsal.es>\n>\n> This options allows control over which of `--worktree` or `--staged` is\n> applied when `git restore` is invoked with neither\n>\n> This patch is intended to reduce lost work to accidental `git restore .`\n> when `git restore --staged .` was intended.\n\nWe usually describe the status quo (what the current system does),\nwhat bad things can happen with the current system, and then what\nchange is proposed to improve the situation, in this order.  So the\nabove is backwards.  Perhaps like\n\n    \"git restore\" takes \"--worktree\" and/or \"--staged\" options to\n    specify which one of the working tree files and/or the index\n    entries are updated.  With neither options, the command by\n    default updates the working tree files.\n\n    A user who wanted to reset the index entries from HEAD may by\n    mistake run \"git restore\" without the \"--staged\" option.  When\n    such a mistake happens, the work made in the working tree files\n    that are not yet added to the index will be forever lost.\n\n    Introduce the restore.defaultDestination configuration variable,\n    which can be set to one of \"both\", \"index\", or \"worktree\", to\n    help those users who want to set it to \"index\" to avoid touching\n    the working tree files by mistake.  They now force themselves to\n    use the \"--worktree\" option explicitly when they want to restore\n    the working tree files.\n\nor something along that line would make it more like our log messages.\n\nNote that even though I wrote the above by guessing what your\nmotivation behind this change is, I do not very much agree with the\nthird paragraph myself.  I think a better solution would be to teach\nthese users to use \"git reset -- <path>\" when they want to reset the\nindex entries, and not run the \"git restore\" command.\n\n\n> +\tThis\n> +\toption can be used to prevent accidentally losing work by running `git\n> +\trestore .` when `git restore --staged .` was intended.\n\nThis is better left out, as it cuts both ways.  With it set to\n'index', this option will clobber the index entries the user\ncarefully prepared so far with \"git add -p\" and friends, when the\nuser wanted to update the working tree files (either from the index\nor from an existing commit) by running \"git restore\", which will\nlose work.\n\nIf we wanted to advertise it as a feature, then the sentence should\nsay something like\n\n\tThis variable can be set to 'index' to prevent accidentally\n\tlosing work ...\n\n\"can be used\" is too vague when you are talking about setting it to\na particular value.  IOW, setting it to any other value would *NOT*\nprevent \"git restore\" from behaving differently from \"git resetore --staged\".\n\nAgain, I do not think it is a good idea to sell it as a feature in\nthe first place, as it cuts both ways.\n\n> diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\n> index 5964810caa4..28165861f55 100644\n> --- a/Documentation/git-restore.txt\n> +++ b/Documentation/git-restore.txt\n> @@ -14,14 +14,18 @@ SYNOPSIS\n>  \n>  DESCRIPTION\n>  -----------\n> -Restore specified paths in the working tree with some contents from a\n> +Restore specified paths in the working tree or index with some contents from a\n\nShouldn't it be \"and/or\"?\n\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index a5155cf55c1..5067753030b 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1942,6 +1966,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix)\n>  \tstruct branch_info new_branch_info = { 0 };\n>  \n>  \tmemset(&opts, 0, sizeof(opts));\n> +\n>  \topts.accept_ref = 0;\n>  \topts.accept_pathspec = 1;\n>  \topts.empty_pathspec_ok = 0;\n\nWhy?\n\n\n> diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh\n> index 7c43ddf1d99..6e9b06e0bf4 100755\n> --- a/t/t2070-restore.sh\n> +++ b/t/t2070-restore.sh\n> @@ -137,4 +137,128 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '\n>  \ttest_must_fail git rev-parse HEAD:new1\n>  '\n>  \n> +test_expect_success 'restore with restore.defaultLocation unset works as if --worktree given' '\n> +\ttest_when_finished git reset --hard HEAD^ &&\n> +\ttest_commit root-unset-restore.defaultLocation &&\n> +\ttest_commit unset-restore.defaultLocation one one &&\n> +\t> one &&\n\nLose SP between \">\" and \"one\", its redirection target.\n\ncf. Documentation/CodingGuidelines, look for \"Redirection operators\nshould be written with space before, but no space after them.\" and\nstudy the entire paragraph.\n\n> +\tgit restore one &&\n> +\ttest -z $(git status --porcelain --untracked-files=no) &&\n\nThis (together with many other uses of \"git status\" in the new\ntests) will not catch a segfaulting \"git status\".\n\nThanks.\n"},{"id":"473511","messageId":"00a07e7d-7605-48df-0192-775c2fee39a3@hsal.es","threadId":"59378","inReplyTo":"xmqq1qlss0un.fsf@gitster.g","subject":"Re: [PATCH] Add `restore.defaultLocation` option","fromName":"Hugo Sales","fromEmail":"hugo@hsal.es","sentAt":"2023-03-14T23:51:02Z","receivedAt":"2023-03-14T23:51:47Z","isPatch":true,"sender":{"key":"hugo@hsal.es","avatar":null},"body":"I think I addressed your comments in the patch below.\n\n> I think a better solution would be to teach\n> these users to use \"git reset -- <path>\" when they want to reset the\n> index entries, and not run the \"git restore\" command.\n\nI read _somewhere_ that `git restore` was the Modern Replacement for `git\ncheckout` and `git reset`, along with `git switch`. I do find it more intuitive,\ntoo. Friends I talked with liked this change, as they've also read similar\nthings and lost work due to a missing `--staged`, so they stopped using it. I\nfind `git reset` somewhat confusing, as I use it for `git reset --soft HEAD~`\nand `git reset --hard <commit-ish>`, so I don't really associate it with the\nsame usage as `git restore`.\n\n> +static const char *checkout_default_index_worktree;\n> +static int git_restore_config(const char *var, const char *value, void *cb)\n> +{\n> +    struct checkout_opts *opts = cb;\n> +\n> +    if (!strcmp(var, \"restore.defaultdestination\")) {\n> +        git_config_string(&checkout_default_index_worktree, var, value);\n\nI'm not sure if this use of `git_config_string` is required, as `value` seems to\ncontain the desired value, but this seems to be how it's done elsewhere.\n\n> > +    git restore one &&\n> > +    test -z $(git status --porcelain --untracked-files=no) &&\n>\n> This (together with many other uses of \"git status\" in the new\n> tests) will not catch a segfaulting \"git status\".\n\nI replaced this with `test_must_be_empty`, I'm not sure if that's what you\nmeant. I wasn't able to find a version that doesn't need to write to a file.\n\nI'm aware I'm not supposed to send patches as an attachment, but I spent at least 3 hours\ntoday and 3-4 more the day I sent the first patch, and it just doesn't work, so I hope this works\n\n\n\nFrom ec878a84db4dc7aabbc5cddf0897b717ac772494 Mon Sep 17 00:00:00 2001\nFrom: Hugo Sales <hugo@hsal.es>\nDate: Sat, 11 Mar 2023 12:43:34 +0000\nSubject: [PATCH v2] restore: add `restore.defaultDestination` to configure what\n gets updated\n\n`git restore` takes `--worktree` and/or `--staged` options to specify\nwhich one of the working tree files and/or the index entries are\nupdated. With neither option, the command, by default, updates the\nworking tree files.\n\nIf a user attempts to reset the index entries from HEAD, they may, by\nmistake, run `git restore` without the `--staged` option. When such a\nmistake happens, the work made in the working tree files that are not\nyet added to the index will be forever lost. This patch is intended to\nmitigate this. This is a trade-off between lost worktree changes,\nwhich may not be present anywhere else, and lost index modifications,\nwhich can be recreated.\n\nIntroduce the `restore.defaultDestination` configuration variable,\nwhich can be set to one of \"both\", \"index\", or \"worktree\", useful for\nusers who want to set it to \"index\" to avoid touching the working tree\nfiles by mistake. They now force themselves to use the `--worktree`\noption explicitly when they want to restore the working tree files.\n\nSigned-off-by: Hugo Sales <hugo@hsal.es>\n---\nI think I addressed your comments in the patch below.\n\n> I think a better solution would be to teach\n> these users to use \"git reset -- <path>\" when they want to reset the\n> index entries, and not run the \"git restore\" command.\n\nI read _somewhere_ that `git restore` was the Modern Replacement for `git\ncheckout` and `git reset`, along with `git switch`. I do find it more intuite,\ntoo. Friends I talked with liked this change, as they've also read similar\nthings and lost work due to a missing `--staged`, so they stopped using it. I\nfind `git reset` somewhat confusing, as I use it for `git reset --soft HEAD~`\nand `git reset --hard <commit-ish>`, so I don't really associate it with the\nsame usage as `git restore`.\n\n> +static const char *checkout_default_index_worktree;\n> +static int git_restore_config(const char *var, const char *value, void *cb)\n> +{\n> +    struct checkout_opts *opts = cb;\n> +\n> +    if (!strcmp(var, \"restore.defaultdestination\")) {\n> +        git_config_string(&checkout_default_index_worktree, var, value);\n\nI'm not sure if this use of `git_config_string` is required, as `value` seems to\ncontain the desired value, but this seems to be how it's done elsewhere.\n\n> > +    git restore one &&\n> > +    test -z $(git status --porcelain --untracked-files=no) &&\n>\n> This (together with many other uses of \"git status\" in the new\n> tests) will not catch a segfaulting \"git status\".\n\nI replaced this with `test_must_be_empty`, I'm not sure if that's what you\nmeant. I wasn't able to find a version that doesn't need to write to a file.\n\nP.S. I hope the following reaches you correctly, as I'm still struggling to\nconnect to mailbox.org with send-email.\n\n Documentation/config.txt         |   2 +\n Documentation/config/restore.txt |  16 ++++\n Documentation/git-restore.txt    |  17 ++--\n builtin/checkout.c               |  25 ++++++\n t/t2070-restore.sh               | 143 +++++++++++++++++++++++++++++++\n 5 files changed, 198 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/config/restore.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0e93aef862..4359c63794 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -501,6 +501,8 @@ include::config/repack.txt[]\n \n include::config/rerere.txt[]\n \n+include::config/restore.txt[]\n+\n include::config/revert.txt[]\n \n include::config/safe.txt[]\ndiff --git a/Documentation/config/restore.txt b/Documentation/config/restore.txt\nnew file mode 100644\nindex 0000000000..6a06310b4a\n--- /dev/null\n+++ b/Documentation/config/restore.txt\n@@ -0,0 +1,16 @@\n+restore.defaultDestination::\n+    Valid values: \"worktree\", \"staged\" or \"both\". Controls the default\n+    behavior of `git restore` without `--worktree` or `--staged`. If\n+    \"worktree\", `git restore` without `--worktree` or `--staged` is\n+    equivalent to `git restore --worktree`. If \"staged\", `git restore`\n+    without `--worktree` or `--staged` is equivalent to `git restore\n+    --staged`. If \"both\", `git restore` without `--worktree` or `--staged`\n+    is equivalent to `git restore --worktree --staged`. Adding an option\n+    overrides the default, such that if the configuration variable is set to\n+    \"staged\", specifying `--worktree` will only affect the worktree, not\n+    both. This variable can be set to \"staged\" to help prevent accidentally\n+    losing modifications to the worktree, caused by running `git restore .`\n+    when `git restore --staged .` was intended. In this case, modifications\n+    to the index would be lost, which could also be a significant amount of\n+    work, so care is highly recommended.\n+    See linkgit:git-restore[1]\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex 5964810caa..391c367d32 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -14,14 +14,18 @@ SYNOPSIS\n \n DESCRIPTION\n -----------\n-Restore specified paths in the working tree with some contents from a\n+Restore specified paths in the working tree and/or index with some contents from a\n restore source. If a path is tracked but does not exist in the restore\n source, it will be removed to match the source.\n \n-The command can also be used to restore the content in the index with\n+The command can be used to restore the content in the index with\n `--staged`, or restore both the working tree and the index with\n `--staged --worktree`.\n \n+The configuration vairable `restore.defaultDestination`, which accepts values\n+\"worktree\", \"staged\" or \"both\", can be used to control the default behavior for\n+which flag(s) apply if neither `--staged` nor `--worktree` is supplied.\n+\n By default, if `--staged` is given, the contents are restored from `HEAD`,\n otherwise from the index. Use `--source` to restore from a different commit.\n \n@@ -59,9 +63,12 @@ all modified paths.\n --worktree::\n -S::\n --staged::\n-    Specify the restore location. If neither option is specified,\n-    by default the working tree is restored. Specifying `--staged`\n-    will only restore the index. Specifying both restores both.\n+    Specify the restore destination. If neither option is specified, the default\n+    depends on the `'restore.defaultDestination` configuration variable,\n+    which can be \"worktree\" (the default), \"staged\" or \"both\", to control\n+    which of the two flags is assumed if none are given. Specifying\n+    `--worktree` will only restore the worktree. Specifying `--staged` will\n+    only restore the index. Specifying both restores both.\n \n -q::\n --quiet::\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex a5155cf55c..4a9131ec29 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1922,6 +1922,29 @@ int cmd_switch(int argc, const char **argv, const char *prefix)\n     return ret;\n }\n \n+static const char *checkout_default_index_worktree;\n+static int git_restore_config(const char *var, const char *value, void *cb)\n+{\n+    struct checkout_opts *opts = cb;\n+\n+    if (!strcmp(var, \"restore.defaultdestination\")) {\n+        git_config_string(&checkout_default_index_worktree, var, value);\n+        if (!strcmp(checkout_default_index_worktree, \"both\")) {\n+            opts->checkout_index = -2;    /* default on */\n+            opts->checkout_worktree = -2; /* default on */\n+        } else if (!strcmp(checkout_default_index_worktree, \"staged\")) {\n+            opts->checkout_index = -2;    /* default on */\n+            opts->checkout_worktree = -1; /* default off */\n+        } else {\n+            opts->checkout_index = -1;    /* default off */\n+            opts->checkout_worktree = -2; /* default on */\n+        }\n+        return 0;\n+    }\n+    return git_xmerge_config(var, value, NULL);\n+}\n+\n+\n int cmd_restore(int argc, const char **argv, const char *prefix)\n {\n     struct checkout_opts opts;\n@@ -1950,6 +1973,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)\n     opts.checkout_worktree = -2; /* default on */\n     opts.ignore_unmerged_opt = \"--ignore-unmerged\";\n \n+    git_config(git_restore_config, &opts);\n+\n     options = parse_options_dup(restore_options);\n     options = add_common_options(&opts, options);\n     options = add_checkout_path_options(&opts, options);\ndiff --git a/t/t2070-restore.sh b/t/t2070-restore.sh\nindex 7c43ddf1d9..c3a35dd878 100755\n--- a/t/t2070-restore.sh\n+++ b/t/t2070-restore.sh\n@@ -137,4 +137,147 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '\n     test_must_fail git rev-parse HEAD:new1\n '\n \n+test_expect_success 'restore with restore.defaultDestination unset works as if --worktree given' '\n+    test_when_finished git reset --hard HEAD^ &&\n+    test_commit root-unset-restore.defaultDestination &&\n+    test_commit unset-restore.defaultDestination one one &&\n+    >one &&\n+\n+    git restore one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status &&\n+\n+    >one &&\n+    git add one &&\n+    git restore one &&\n+    git status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --worktree one &&\n+    git status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+    git restore --staged one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --worktree --staged one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status\n+'\n+\n+test_expect_success 'restore with restore.defaultDestination set to worktree works as if --worktree given' '\n+    test_when_finished git reset --hard HEAD^ &&\n+    test_when_finished git config --unset restore.defaultDestination &&\n+    test_commit root-worktree-restore.defaultDestination &&\n+    test_commit worktree-restore.defaultDestination one one &&\n+    git config restore.defaultDestination worktree &&\n+    >one &&\n+\n+    git restore one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status &&\n+\n+    >one &&\n+    git add one &&\n+    git restore one &&\n+    git status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --worktree one &&\n+    git status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+    git restore --staged one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --worktree --staged one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status\n+'\n+\n+test_expect_success 'restore with restore.defaultDestination set to staged works as if --staged given' '\n+    test_when_finished git reset --hard HEAD^ &&\n+    test_when_finished git config --unset restore.defaultDestination &&\n+    test_commit root-staged-restore.defaultDestination &&\n+    test_commit staged-restore.defaultDestination one one &&\n+    git config restore.defaultDestination staged &&\n+    >one &&\n+\n+    git restore one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+    git restore --staged one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+    git add one &&\n+    git restore one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+    git add one &&\n+    git restore --staged one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+    git restore --worktree one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --worktree --staged one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status\n+'\n+\n+test_expect_success 'restore with restore.defaultDestination set to both works as if --worktree --staged given' '\n+    test_when_finished git reset --hard HEAD^ &&\n+    test_when_finished git config --unset restore.defaultDestination &&\n+    test_commit root-both-restore.defaultDestination &&\n+    test_commit both-restore.defaultDestination one one &&\n+    git config restore.defaultDestination both &&\n+    >one &&\n+\n+    git restore one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --staged one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\"  &&\n+\n+    git add one &&\n+    git restore one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --staged one &&\n+    git status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+    git restore --worktree one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status &&\n+\n+    >one &&\n+    git add one &&\n+    git restore --worktree --staged one &&\n+    git status --porcelain --untracked-files=no >status &&\n+    test_must_be_empty status &&\n+    rm status\n+'\n+\n test_done\n-- \n2.39.2\n\n\n\nOn 3/13/23 23:11, Junio C Hamano wrote:\n> \"Hugo Sales via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> Subject: Re: [PATCH] Add `restore.defaultLocation` option\n> As to the form, perhaps\n>\n> \tSubject: [PATCH] restore: make it configurable what gets updated\n>\n> cf. Documentation/SubmittingPatches[[describe-changes]]\n>\n> Also on substance,\n>\n>  * \"option\" usually refers to the --option given on the command\n>    line; when you mean \"configuration variable\", it is a misleading\n>    word to use.\n>\n>  * \"restore\" command deals with two quite conceptually different\n>    locations.  Where it gets the contents from (i.e. the source\n>    location), and where the contents are used to update (i.e. the\n>    destination location).  The \".defaultLocation\" is a poor name for\n>    a configuration variable because it does not convey which one is\n>    meant.\n>\n>> From: Hugo Sales <hugo@hsal.es>\n>>\n>> This options allows control over which of `--worktree` or `--staged` is\n>> applied when `git restore` is invoked with neither\n>>\n>> This patch is intended to reduce lost work to accidental `git restore .`\n>> when `git restore --staged .` was intended.\n> We usually describe the status quo (what the current system does),\n> what bad things can happen with the current system, and then what\n> change is proposed to improve the situation, in this order.  So the\n> above is backwards.  Perhaps like\n>\n>     \"git restore\" takes \"--worktree\" and/or \"--staged\" options to\n>     specify which one of the working tree files and/or the index\n>     entries are updated.  With neither options, the command by\n>     default updates the working tree files.\n>\n>     A user who wanted to reset the index entries from HEAD may by\n>     mistake run \"git restore\" without the \"--staged\" option.  When\n>     such a mistake happens, the work made in the working tree files\n>     that are not yet added to the index will be forever lost.\n>\n>     Introduce the restore.defaultDestination configuration variable,\n>     which can be set to one of \"both\", \"index\", or \"worktree\", to\n>     help those users who want to set it to \"index\" to avoid touching\n>     the working tree files by mistake.  They now force themselves to\n>     use the \"--worktree\" option explicitly when they want to restore\n>     the working tree files.\n>\n> or something along that line would make it more like our log messages.\n>\n> Note that even though I wrote the above by guessing what your\n> motivation behind this change is, I do not very much agree with the\n> third paragraph myself.  I think a better solution would be to teach\n> these users to use \"git reset -- <path>\" when they want to reset the\n> index entries, and not run the \"git restore\" command.\n>\n>\n>> +\tThis\n>> +\toption can be used to prevent accidentally losing work by running `git\n>> +\trestore .` when `git restore --staged .` was intended.\n> This is better left out, as it cuts both ways.  With it set to\n> 'index', this option will clobber the index entries the user\n> carefully prepared so far with \"git add -p\" and friends, when the\n> user wanted to update the working tree files (either from the index\n> or from an existing commit) by running \"git restore\", which will\n> lose work.\n>\n> If we wanted to advertise it as a feature, then the sentence should\n> say something like\n>\n> \tThis variable can be set to 'index' to prevent accidentally\n> \tlosing work ...\n>\n> \"can be used\" is too vague when you are talking about setting it to\n> a particular value.  IOW, setting it to any other value would *NOT*\n> prevent \"git restore\" from behaving differently from \"git resetore --staged\".\n>\n> Again, I do not think it is a good idea to sell it as a feature in\n> the first place, as it cuts both ways.\n>\n>> diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\n>> index 5964810caa4..28165861f55 100644\n>> --- a/Documentation/git-restore.txt\n>> +++ b/Documentation/git-restore.txt\n>> @@ -14,14 +14,18 @@ SYNOPSIS\n>>  \n>>  DESCRIPTION\n>>  -----------\n>> -Restore specified paths in the working tree with some contents from a\n>> +Restore specified paths in the working tree or index with some contents from a\n> Shouldn't it be \"and/or\"?\n>\n>> diff --git a/builtin/checkout.c b/builtin/checkout.c\n>> index a5155cf55c1..5067753030b 100644\n>> --- a/builtin/checkout.c\n>> +++ b/builtin/checkout.c\n>> @@ -1942,6 +1966,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix)\n>>  \tstruct branch_info new_branch_info = { 0 };\n>>  \n>>  \tmemset(&opts, 0, sizeof(opts));\n>> +\n>>  \topts.accept_ref = 0;\n>>  \topts.accept_pathspec = 1;\n>>  \topts.empty_pathspec_ok = 0;\n> Why?\n>\n>\n>> diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh\n>> index 7c43ddf1d99..6e9b06e0bf4 100755\n>> --- a/t/t2070-restore.sh\n>> +++ b/t/t2070-restore.sh\n>> @@ -137,4 +137,128 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '\n>>  \ttest_must_fail git rev-parse HEAD:new1\n>>  '\n>>  \n>> +test_expect_success 'restore with restore.defaultLocation unset works as if --worktree given' '\n>> +\ttest_when_finished git reset --hard HEAD^ &&\n>> +\ttest_commit root-unset-restore.defaultLocation &&\n>> +\ttest_commit unset-restore.defaultLocation one one &&\n>> +\t> one &&\n> Lose SP between \">\" and \"one\", its redirection target.\n>\n> cf. Documentation/CodingGuidelines, look for \"Redirection operators\n> should be written with space before, but no space after them.\" and\n> study the entire paragraph.\n>\n>> +\tgit restore one &&\n>> +\ttest -z $(git status --porcelain --untracked-files=no) &&\n> This (together with many other uses of \"git status\" in the new\n> tests) will not catch a segfaulting \"git status\".\n>\n> Thanks.\n\n\nFrom ec878a84db4dc7aabbc5cddf0897b717ac772494 Mon Sep 17 00:00:00 2001\nFrom: Hugo Sales <hugo@hsal.es>\nDate: Sat, 11 Mar 2023 12:43:34 +0000\nSubject: [PATCH v2] restore: add `restore.defaultDestination` to configure what\n gets updated\n\n`git restore` takes `--worktree` and/or `--staged` options to specify\nwhich one of the working tree files and/or the index entries are\nupdated. With neither option, the command, by default, updates the\nworking tree files.\n\nIf a user attempts to reset the index entries from HEAD, they may, by\nmistake, run `git restore` without the `--staged` option. When such a\nmistake happens, the work made in the working tree files that are not\nyet added to the index will be forever lost. This patch is intended to\nmitigate this. This is a trade-off between lost worktree changes,\nwhich may not be present anywhere else, and lost index modifications,\nwhich can be recreated.\n\nIntroduce the `restore.defaultDestination` configuration variable,\nwhich can be set to one of \"both\", \"index\", or \"worktree\", useful for\nusers who want to set it to \"index\" to avoid touching the working tree\nfiles by mistake. They now force themselves to use the `--worktree`\noption explicitly when they want to restore the working tree files.\n\nSigned-off-by: Hugo Sales <hugo@hsal.es>\n---\nI think I addressed your comments in the patch below.\n\n> I think a better solution would be to teach\n> these users to use \"git reset -- <path>\" when they want to reset the\n> index entries, and not run the \"git restore\" command.\n\nI read _somewhere_ that `git restore` was the Modern Replacement for `git\ncheckout` and `git reset`, along with `git switch`. I do find it more intuite,\ntoo. Friends I talked with liked this change, as they've also read similar\nthings and lost work due to a missing `--staged`, so they stopped using it. I\nfind `git reset` somewhat confusing, as I use it for `git reset --soft HEAD~`\nand `git reset --hard <commit-ish>`, so I don't really associate it with the\nsame usage as `git restore`.\n\n> +static const char *checkout_default_index_worktree;\n> +static int git_restore_config(const char *var, const char *value, void *cb)\n> +{\n> +\tstruct checkout_opts *opts = cb;\n> +\n> +\tif (!strcmp(var, \"restore.defaultdestination\")) {\n> +\t\tgit_config_string(&checkout_default_index_worktree, var, value);\n\nI'm not sure if this use of `git_config_string` is required, as `value` seems to\ncontain the desired value, but this seems to be how it's done elsewhere.\n\n> > +\tgit restore one &&\n> > +\ttest -z $(git status --porcelain --untracked-files=no) &&\n> \n> This (together with many other uses of \"git status\" in the new\n> tests) will not catch a segfaulting \"git status\".\n\nI replaced this with `test_must_be_empty`, I'm not sure if that's what you\nmeant. I wasn't able to find a version that doesn't need to write to a file.\n\nP.S. I hope the following reaches you correctly, as I'm still struggling to\nconnect to mailbox.org with send-email.\n\n Documentation/config.txt         |   2 +\n Documentation/config/restore.txt |  16 ++++\n Documentation/git-restore.txt    |  17 ++--\n builtin/checkout.c               |  25 ++++++\n t/t2070-restore.sh               | 143 +++++++++++++++++++++++++++++++\n 5 files changed, 198 insertions(+), 5 deletions(-)\n create mode 100644 Documentation/config/restore.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0e93aef862..4359c63794 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -501,6 +501,8 @@ include::config/repack.txt[]\n \n include::config/rerere.txt[]\n \n+include::config/restore.txt[]\n+\n include::config/revert.txt[]\n \n include::config/safe.txt[]\ndiff --git a/Documentation/config/restore.txt b/Documentation/config/restore.txt\nnew file mode 100644\nindex 0000000000..6a06310b4a\n--- /dev/null\n+++ b/Documentation/config/restore.txt\n@@ -0,0 +1,16 @@\n+restore.defaultDestination::\n+\tValid values: \"worktree\", \"staged\" or \"both\". Controls the default\n+\tbehavior of `git restore` without `--worktree` or `--staged`. If\n+\t\"worktree\", `git restore` without `--worktree` or `--staged` is\n+\tequivalent to `git restore --worktree`. If \"staged\", `git restore`\n+\twithout `--worktree` or `--staged` is equivalent to `git restore\n+\t--staged`. If \"both\", `git restore` without `--worktree` or `--staged`\n+\tis equivalent to `git restore --worktree --staged`. Adding an option\n+\toverrides the default, such that if the configuration variable is set to\n+\t\"staged\", specifying `--worktree` will only affect the worktree, not\n+\tboth. This variable can be set to \"staged\" to help prevent accidentally\n+\tlosing modifications to the worktree, caused by running `git restore .`\n+\twhen `git restore --staged .` was intended. In this case, modifications\n+\tto the index would be lost, which could also be a significant amount of\n+\twork, so care is highly recommended.\n+\tSee linkgit:git-restore[1]\ndiff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt\nindex 5964810caa..391c367d32 100644\n--- a/Documentation/git-restore.txt\n+++ b/Documentation/git-restore.txt\n@@ -14,14 +14,18 @@ SYNOPSIS\n \n DESCRIPTION\n -----------\n-Restore specified paths in the working tree with some contents from a\n+Restore specified paths in the working tree and/or index with some contents from a\n restore source. If a path is tracked but does not exist in the restore\n source, it will be removed to match the source.\n \n-The command can also be used to restore the content in the index with\n+The command can be used to restore the content in the index with\n `--staged`, or restore both the working tree and the index with\n `--staged --worktree`.\n \n+The configuration vairable `restore.defaultDestination`, which accepts values\n+\"worktree\", \"staged\" or \"both\", can be used to control the default behavior for\n+which flag(s) apply if neither `--staged` nor `--worktree` is supplied.\n+\n By default, if `--staged` is given, the contents are restored from `HEAD`,\n otherwise from the index. Use `--source` to restore from a different commit.\n \n@@ -59,9 +63,12 @@ all modified paths.\n --worktree::\n -S::\n --staged::\n-\tSpecify the restore location. If neither option is specified,\n-\tby default the working tree is restored. Specifying `--staged`\n-\twill only restore the index. Specifying both restores both.\n+\tSpecify the restore destination. If neither option is specified, the default\n+\tdepends on the `'restore.defaultDestination` configuration variable,\n+\twhich can be \"worktree\" (the default), \"staged\" or \"both\", to control\n+\twhich of the two flags is assumed if none are given. Specifying\n+\t`--worktree` will only restore the worktree. Specifying `--staged` will\n+\tonly restore the index. Specifying both restores both.\n \n -q::\n --quiet::\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex a5155cf55c..4a9131ec29 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1922,6 +1922,29 @@ int cmd_switch(int argc, const char **argv, const char *prefix)\n \treturn ret;\n }\n \n+static const char *checkout_default_index_worktree;\n+static int git_restore_config(const char *var, const char *value, void *cb)\n+{\n+\tstruct checkout_opts *opts = cb;\n+\n+\tif (!strcmp(var, \"restore.defaultdestination\")) {\n+\t\tgit_config_string(&checkout_default_index_worktree, var, value);\n+\t\tif (!strcmp(checkout_default_index_worktree, \"both\")) {\n+\t\t\topts->checkout_index = -2;    /* default on */\n+\t\t\topts->checkout_worktree = -2; /* default on */\n+\t\t} else if (!strcmp(checkout_default_index_worktree, \"staged\")) {\n+\t\t\topts->checkout_index = -2;    /* default on */\n+\t\t\topts->checkout_worktree = -1; /* default off */\n+\t\t} else {\n+\t\t\topts->checkout_index = -1;    /* default off */\n+\t\t\topts->checkout_worktree = -2; /* default on */\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\treturn git_xmerge_config(var, value, NULL);\n+}\n+\n+\n int cmd_restore(int argc, const char **argv, const char *prefix)\n {\n \tstruct checkout_opts opts;\n@@ -1950,6 +1973,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)\n \topts.checkout_worktree = -2; /* default on */\n \topts.ignore_unmerged_opt = \"--ignore-unmerged\";\n \n+\tgit_config(git_restore_config, &opts);\n+\n \toptions = parse_options_dup(restore_options);\n \toptions = add_common_options(&opts, options);\n \toptions = add_checkout_path_options(&opts, options);\ndiff --git a/t/t2070-restore.sh b/t/t2070-restore.sh\nindex 7c43ddf1d9..c3a35dd878 100755\n--- a/t/t2070-restore.sh\n+++ b/t/t2070-restore.sh\n@@ -137,4 +137,147 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '\n \ttest_must_fail git rev-parse HEAD:new1\n '\n \n+test_expect_success 'restore with restore.defaultDestination unset works as if --worktree given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_commit root-unset-restore.defaultDestination &&\n+\ttest_commit unset-restore.defaultDestination one one &&\n+\t>one &&\n+\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --worktree one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status\n+'\n+\n+test_expect_success 'restore with restore.defaultDestination set to worktree works as if --worktree given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_when_finished git config --unset restore.defaultDestination &&\n+\ttest_commit root-worktree-restore.defaultDestination &&\n+\ttest_commit worktree-restore.defaultDestination one one &&\n+\tgit config restore.defaultDestination worktree &&\n+\t>one &&\n+\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --worktree one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^M \" &&\n+\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status\n+'\n+\n+test_expect_success 'restore with restore.defaultDestination set to staged works as if --staged given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_when_finished git config --unset restore.defaultDestination &&\n+\ttest_commit root-staged-restore.defaultDestination &&\n+\ttest_commit staged-restore.defaultDestination one one &&\n+\tgit config restore.defaultDestination staged &&\n+\t>one &&\n+\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit add one &&\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit add one &&\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit restore --worktree one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status\n+'\n+\n+test_expect_success 'restore with restore.defaultDestination set to both works as if --worktree --staged given' '\n+\ttest_when_finished git reset --hard HEAD^ &&\n+\ttest_when_finished git config --unset restore.defaultDestination &&\n+\ttest_commit root-both-restore.defaultDestination &&\n+\ttest_commit both-restore.defaultDestination one one &&\n+\tgit config restore.defaultDestination both &&\n+\t>one &&\n+\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\"  &&\n+\n+\tgit add one &&\n+\tgit restore one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --staged one &&\n+\tgit status --porcelain --untracked-files=no | grep \"^ M\" &&\n+\n+\tgit restore --worktree one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status &&\n+\n+\t>one &&\n+\tgit add one &&\n+\tgit restore --worktree --staged one &&\n+\tgit status --porcelain --untracked-files=no >status &&\n+\ttest_must_be_empty status &&\n+\trm status\n+'\n+\n test_done\n-- \n2.39.2\n\n"},{"id":"474188","messageId":"20230326105339.609573-1-hugo@hsal.es","threadId":"59378","inReplyTo":"00a07e7d-7605-48df-0192-775c2fee39a3@hsal.es","subject":"Re: [PATCH] Add `restore.defaultLocation` option","fromName":"Hugo Sales","fromEmail":"hugo@hsal.es","sentAt":"2023-03-26T10:53:39Z","receivedAt":"2023-03-26T10:53:56Z","isPatch":true,"sender":{"key":"hugo@hsal.es","avatar":null},"body":"Wanted to add that since `git status` suggests using `git restore --staged` to\nreset index entries, I'm sure it has happened many times that a user ran `git\nrestore` and lost work\n"}]}