Re: [PATCH] Add `restore.defaultLocation` option

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I think I addressed your comments in the patch below.

> I think a better solution would be to teach
> these users to use "git reset -- <path>" when they want to reset the
> index entries, and not run the "git restore" command.

I read _somewhere_ that `git restore` was the Modern Replacement for `git
checkout` and `git reset`, along with `git switch`. I do find it more intuitive,
too. Friends I talked with liked this change, as they've also read similar
things and lost work due to a missing `--staged`, so they stopped using it. I
find `git reset` somewhat confusing, as I use it for `git reset --soft HEAD~`
and `git reset --hard <commit-ish>`, so I don't really associate it with the
same usage as `git restore`.

> +static const char *checkout_default_index_worktree;
> +static int git_restore_config(const char *var, const char *value, void *cb)
> +{
> +    struct checkout_opts *opts = cb;
> +
> +    if (!strcmp(var, "restore.defaultdestination")) {
> +        git_config_string(&checkout_default_index_worktree, var, value);

I'm not sure if this use of `git_config_string` is required, as `value` seems to
contain the desired value, but this seems to be how it's done elsewhere.

> > +    git restore one &&
> > +    test -z $(git status --porcelain --untracked-files=no) &&
>
> This (together with many other uses of "git status" in the new
> tests) will not catch a segfaulting "git status".

I replaced this with `test_must_be_empty`, I'm not sure if that's what you
meant. I wasn't able to find a version that doesn't need to write to a file.

I'm aware I'm not supposed to send patches as an attachment, but I spent at least 3 hours
today and 3-4 more the day I sent the first patch, and it just doesn't work, so I hope this works



>From ec878a84db4dc7aabbc5cddf0897b717ac772494 Mon Sep 17 00:00:00 2001
From: Hugo Sales <hugo@xxxxxxx>
Date: Sat, 11 Mar 2023 12:43:34 +0000
Subject: [PATCH v2] restore: add `restore.defaultDestination` to configure what
 gets updated

`git restore` takes `--worktree` and/or `--staged` options to specify
which one of the working tree files and/or the index entries are
updated. With neither option, the command, by default, updates the
working tree files.

If a user attempts to reset the index entries from HEAD, they may, by
mistake, run `git restore` without the `--staged` option. When such a
mistake happens, the work made in the working tree files that are not
yet added to the index will be forever lost. This patch is intended to
mitigate this. This is a trade-off between lost worktree changes,
which may not be present anywhere else, and lost index modifications,
which can be recreated.

Introduce the `restore.defaultDestination` configuration variable,
which can be set to one of "both", "index", or "worktree", useful for
users who want to set it to "index" to avoid touching the working tree
files by mistake. They now force themselves to use the `--worktree`
option explicitly when they want to restore the working tree files.

Signed-off-by: Hugo Sales <hugo@xxxxxxx>
---
I think I addressed your comments in the patch below.

> I think a better solution would be to teach
> these users to use "git reset -- <path>" when they want to reset the
> index entries, and not run the "git restore" command.

I read _somewhere_ that `git restore` was the Modern Replacement for `git
checkout` and `git reset`, along with `git switch`. I do find it more intuite,
too. Friends I talked with liked this change, as they've also read similar
things and lost work due to a missing `--staged`, so they stopped using it. I
find `git reset` somewhat confusing, as I use it for `git reset --soft HEAD~`
and `git reset --hard <commit-ish>`, so I don't really associate it with the
same usage as `git restore`.

> +static const char *checkout_default_index_worktree;
> +static int git_restore_config(const char *var, const char *value, void *cb)
> +{
> +    struct checkout_opts *opts = cb;
> +
> +    if (!strcmp(var, "restore.defaultdestination")) {
> +        git_config_string(&checkout_default_index_worktree, var, value);

I'm not sure if this use of `git_config_string` is required, as `value` seems to
contain the desired value, but this seems to be how it's done elsewhere.

> > +    git restore one &&
> > +    test -z $(git status --porcelain --untracked-files=no) &&
>
> This (together with many other uses of "git status" in the new
> tests) will not catch a segfaulting "git status".

I replaced this with `test_must_be_empty`, I'm not sure if that's what you
meant. I wasn't able to find a version that doesn't need to write to a file.

P.S. I hope the following reaches you correctly, as I'm still struggling to
connect to mailbox.org with send-email.

 Documentation/config.txt         |   2 +
 Documentation/config/restore.txt |  16 ++++
 Documentation/git-restore.txt    |  17 ++--
 builtin/checkout.c               |  25 ++++++
 t/t2070-restore.sh               | 143 +++++++++++++++++++++++++++++++
 5 files changed, 198 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/config/restore.txt

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 0e93aef862..4359c63794 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -501,6 +501,8 @@ include::config/repack.txt[]
 
 include::config/rerere.txt[]
 
+include::config/restore.txt[]
+
 include::config/revert.txt[]
 
 include::config/safe.txt[]
diff --git a/Documentation/config/restore.txt b/Documentation/config/restore.txt
new file mode 100644
index 0000000000..6a06310b4a
--- /dev/null
+++ b/Documentation/config/restore.txt
@@ -0,0 +1,16 @@
+restore.defaultDestination::
+    Valid values: "worktree", "staged" or "both". Controls the default
+    behavior of `git restore` without `--worktree` or `--staged`. If
+    "worktree", `git restore` without `--worktree` or `--staged` is
+    equivalent to `git restore --worktree`. If "staged", `git restore`
+    without `--worktree` or `--staged` is equivalent to `git restore
+    --staged`. If "both", `git restore` without `--worktree` or `--staged`
+    is equivalent to `git restore --worktree --staged`. Adding an option
+    overrides the default, such that if the configuration variable is set to
+    "staged", specifying `--worktree` will only affect the worktree, not
+    both. This variable can be set to "staged" to help prevent accidentally
+    losing modifications to the worktree, caused by running `git restore .`
+    when `git restore --staged .` was intended. In this case, modifications
+    to the index would be lost, which could also be a significant amount of
+    work, so care is highly recommended.
+    See linkgit:git-restore[1]
diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt
index 5964810caa..391c367d32 100644
--- a/Documentation/git-restore.txt
+++ b/Documentation/git-restore.txt
@@ -14,14 +14,18 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Restore specified paths in the working tree with some contents from a
+Restore specified paths in the working tree and/or index with some contents from a
 restore source. If a path is tracked but does not exist in the restore
 source, it will be removed to match the source.
 
-The command can also be used to restore the content in the index with
+The command can be used to restore the content in the index with
 `--staged`, or restore both the working tree and the index with
 `--staged --worktree`.
 
+The configuration vairable `restore.defaultDestination`, which accepts values
+"worktree", "staged" or "both", can be used to control the default behavior for
+which flag(s) apply if neither `--staged` nor `--worktree` is supplied.
+
 By default, if `--staged` is given, the contents are restored from `HEAD`,
 otherwise from the index. Use `--source` to restore from a different commit.
 
@@ -59,9 +63,12 @@ all modified paths.
 --worktree::
 -S::
 --staged::
-    Specify the restore location. If neither option is specified,
-    by default the working tree is restored. Specifying `--staged`
-    will only restore the index. Specifying both restores both.
+    Specify the restore destination. If neither option is specified, the default
+    depends on the `'restore.defaultDestination` configuration variable,
+    which can be "worktree" (the default), "staged" or "both", to control
+    which of the two flags is assumed if none are given. Specifying
+    `--worktree` will only restore the worktree. Specifying `--staged` will
+    only restore the index. Specifying both restores both.
 
 -q::
 --quiet::
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a5155cf55c..4a9131ec29 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1922,6 +1922,29 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
     return ret;
 }
 
+static const char *checkout_default_index_worktree;
+static int git_restore_config(const char *var, const char *value, void *cb)
+{
+    struct checkout_opts *opts = cb;
+
+    if (!strcmp(var, "restore.defaultdestination")) {
+        git_config_string(&checkout_default_index_worktree, var, value);
+        if (!strcmp(checkout_default_index_worktree, "both")) {
+            opts->checkout_index = -2;    /* default on */
+            opts->checkout_worktree = -2; /* default on */
+        } else if (!strcmp(checkout_default_index_worktree, "staged")) {
+            opts->checkout_index = -2;    /* default on */
+            opts->checkout_worktree = -1; /* default off */
+        } else {
+            opts->checkout_index = -1;    /* default off */
+            opts->checkout_worktree = -2; /* default on */
+        }
+        return 0;
+    }
+    return git_xmerge_config(var, value, NULL);
+}
+
+
 int cmd_restore(int argc, const char **argv, const char *prefix)
 {
     struct checkout_opts opts;
@@ -1950,6 +1973,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
     opts.checkout_worktree = -2; /* default on */
     opts.ignore_unmerged_opt = "--ignore-unmerged";
 
+    git_config(git_restore_config, &opts);
+
     options = parse_options_dup(restore_options);
     options = add_common_options(&opts, options);
     options = add_checkout_path_options(&opts, options);
diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh
index 7c43ddf1d9..c3a35dd878 100755
--- a/t/t2070-restore.sh
+++ b/t/t2070-restore.sh
@@ -137,4 +137,147 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '
     test_must_fail git rev-parse HEAD:new1
 '
 
+test_expect_success 'restore with restore.defaultDestination unset works as if --worktree given' '
+    test_when_finished git reset --hard HEAD^ &&
+    test_commit root-unset-restore.defaultDestination &&
+    test_commit unset-restore.defaultDestination one one &&
+    >one &&
+
+    git restore one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status &&
+
+    >one &&
+    git add one &&
+    git restore one &&
+    git status --porcelain --untracked-files=no | grep "^M " &&
+
+    >one &&
+    git add one &&
+    git restore --worktree one &&
+    git status --porcelain --untracked-files=no | grep "^M " &&
+
+    git restore --staged one &&
+    git status --porcelain --untracked-files=no | grep "^ M" &&
+
+    >one &&
+    git add one &&
+    git restore --worktree --staged one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status
+'
+
+test_expect_success 'restore with restore.defaultDestination set to worktree works as if --worktree given' '
+    test_when_finished git reset --hard HEAD^ &&
+    test_when_finished git config --unset restore.defaultDestination &&
+    test_commit root-worktree-restore.defaultDestination &&
+    test_commit worktree-restore.defaultDestination one one &&
+    git config restore.defaultDestination worktree &&
+    >one &&
+
+    git restore one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status &&
+
+    >one &&
+    git add one &&
+    git restore one &&
+    git status --porcelain --untracked-files=no | grep "^M " &&
+
+    >one &&
+    git add one &&
+    git restore --worktree one &&
+    git status --porcelain --untracked-files=no | grep "^M " &&
+
+    git restore --staged one &&
+    git status --porcelain --untracked-files=no | grep "^ M" &&
+
+    >one &&
+    git add one &&
+    git restore --worktree --staged one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status
+'
+
+test_expect_success 'restore with restore.defaultDestination set to staged works as if --staged given' '
+    test_when_finished git reset --hard HEAD^ &&
+    test_when_finished git config --unset restore.defaultDestination &&
+    test_commit root-staged-restore.defaultDestination &&
+    test_commit staged-restore.defaultDestination one one &&
+    git config restore.defaultDestination staged &&
+    >one &&
+
+    git restore one &&
+    git status --porcelain --untracked-files=no | grep "^ M" &&
+
+    git restore --staged one &&
+    git status --porcelain --untracked-files=no | grep "^ M" &&
+
+    git add one &&
+    git restore one &&
+    git status --porcelain --untracked-files=no | grep "^ M" &&
+
+    git add one &&
+    git restore --staged one &&
+    git status --porcelain --untracked-files=no | grep "^ M" &&
+
+    git restore --worktree one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status &&
+
+    >one &&
+    git add one &&
+    git restore --worktree --staged one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status
+'
+
+test_expect_success 'restore with restore.defaultDestination set to both works as if --worktree --staged given' '
+    test_when_finished git reset --hard HEAD^ &&
+    test_when_finished git config --unset restore.defaultDestination &&
+    test_commit root-both-restore.defaultDestination &&
+    test_commit both-restore.defaultDestination one one &&
+    git config restore.defaultDestination both &&
+    >one &&
+
+    git restore one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status &&
+
+    >one &&
+    git add one &&
+    git restore --staged one &&
+    git status --porcelain --untracked-files=no | grep "^ M"  &&
+
+    git add one &&
+    git restore one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status &&
+
+    >one &&
+    git add one &&
+    git restore --staged one &&
+    git status --porcelain --untracked-files=no | grep "^ M" &&
+
+    git restore --worktree one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status &&
+
+    >one &&
+    git add one &&
+    git restore --worktree --staged one &&
+    git status --porcelain --untracked-files=no >status &&
+    test_must_be_empty status &&
+    rm status
+'
+
 test_done
-- 
2.39.2



On 3/13/23 23:11, Junio C Hamano wrote:
> "Hugo Sales via GitGitGadget" <gitgitgadget@xxxxxxxxx> writes:
>
>> Subject: Re: [PATCH] Add `restore.defaultLocation` option
> As to the form, perhaps
>
> 	Subject: [PATCH] restore: make it configurable what gets updated
>
> cf. Documentation/SubmittingPatches[[describe-changes]]
>
> Also on substance,
>
>  * "option" usually refers to the --option given on the command
>    line; when you mean "configuration variable", it is a misleading
>    word to use.
>
>  * "restore" command deals with two quite conceptually different
>    locations.  Where it gets the contents from (i.e. the source
>    location), and where the contents are used to update (i.e. the
>    destination location).  The ".defaultLocation" is a poor name for
>    a configuration variable because it does not convey which one is
>    meant.
>
>> From: Hugo Sales <hugo@xxxxxxx>
>>
>> This options allows control over which of `--worktree` or `--staged` is
>> applied when `git restore` is invoked with neither
>>
>> This patch is intended to reduce lost work to accidental `git restore .`
>> when `git restore --staged .` was intended.
> We usually describe the status quo (what the current system does),
> what bad things can happen with the current system, and then what
> change is proposed to improve the situation, in this order.  So the
> above is backwards.  Perhaps like
>
>     "git restore" takes "--worktree" and/or "--staged" options to
>     specify which one of the working tree files and/or the index
>     entries are updated.  With neither options, the command by
>     default updates the working tree files.
>
>     A user who wanted to reset the index entries from HEAD may by
>     mistake run "git restore" without the "--staged" option.  When
>     such a mistake happens, the work made in the working tree files
>     that are not yet added to the index will be forever lost.
>
>     Introduce the restore.defaultDestination configuration variable,
>     which can be set to one of "both", "index", or "worktree", to
>     help those users who want to set it to "index" to avoid touching
>     the working tree files by mistake.  They now force themselves to
>     use the "--worktree" option explicitly when they want to restore
>     the working tree files.
>
> or something along that line would make it more like our log messages.
>
> Note that even though I wrote the above by guessing what your
> motivation behind this change is, I do not very much agree with the
> third paragraph myself.  I think a better solution would be to teach
> these users to use "git reset -- <path>" when they want to reset the
> index entries, and not run the "git restore" command.
>
>
>> +	This
>> +	option can be used to prevent accidentally losing work by running `git
>> +	restore .` when `git restore --staged .` was intended.
> This is better left out, as it cuts both ways.  With it set to
> 'index', this option will clobber the index entries the user
> carefully prepared so far with "git add -p" and friends, when the
> user wanted to update the working tree files (either from the index
> or from an existing commit) by running "git restore", which will
> lose work.
>
> If we wanted to advertise it as a feature, then the sentence should
> say something like
>
> 	This variable can be set to 'index' to prevent accidentally
> 	losing work ...
>
> "can be used" is too vague when you are talking about setting it to
> a particular value.  IOW, setting it to any other value would *NOT*
> prevent "git restore" from behaving differently from "git resetore --staged".
>
> Again, I do not think it is a good idea to sell it as a feature in
> the first place, as it cuts both ways.
>
>> diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt
>> index 5964810caa4..28165861f55 100644
>> --- a/Documentation/git-restore.txt
>> +++ b/Documentation/git-restore.txt
>> @@ -14,14 +14,18 @@ SYNOPSIS
>>  
>>  DESCRIPTION
>>  -----------
>> -Restore specified paths in the working tree with some contents from a
>> +Restore specified paths in the working tree or index with some contents from a
> Shouldn't it be "and/or"?
>
>> diff --git a/builtin/checkout.c b/builtin/checkout.c
>> index a5155cf55c1..5067753030b 100644
>> --- a/builtin/checkout.c
>> +++ b/builtin/checkout.c
>> @@ -1942,6 +1966,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
>>  	struct branch_info new_branch_info = { 0 };
>>  
>>  	memset(&opts, 0, sizeof(opts));
>> +
>>  	opts.accept_ref = 0;
>>  	opts.accept_pathspec = 1;
>>  	opts.empty_pathspec_ok = 0;
> Why?
>
>
>> diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh
>> index 7c43ddf1d99..6e9b06e0bf4 100755
>> --- a/t/t2070-restore.sh
>> +++ b/t/t2070-restore.sh
>> @@ -137,4 +137,128 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '
>>  	test_must_fail git rev-parse HEAD:new1
>>  '
>>  
>> +test_expect_success 'restore with restore.defaultLocation unset works as if --worktree given' '
>> +	test_when_finished git reset --hard HEAD^ &&
>> +	test_commit root-unset-restore.defaultLocation &&
>> +	test_commit unset-restore.defaultLocation one one &&
>> +	> one &&
> Lose SP between ">" and "one", its redirection target.
>
> cf. Documentation/CodingGuidelines, look for "Redirection operators
> should be written with space before, but no space after them." and
> study the entire paragraph.
>
>> +	git restore one &&
>> +	test -z $(git status --porcelain --untracked-files=no) &&
> This (together with many other uses of "git status" in the new
> tests) will not catch a segfaulting "git status".
>
> Thanks.
From ec878a84db4dc7aabbc5cddf0897b717ac772494 Mon Sep 17 00:00:00 2001
From: Hugo Sales <hugo@xxxxxxx>
Date: Sat, 11 Mar 2023 12:43:34 +0000
Subject: [PATCH v2] restore: add `restore.defaultDestination` to configure what
 gets updated

`git restore` takes `--worktree` and/or `--staged` options to specify
which one of the working tree files and/or the index entries are
updated. With neither option, the command, by default, updates the
working tree files.

If a user attempts to reset the index entries from HEAD, they may, by
mistake, run `git restore` without the `--staged` option. When such a
mistake happens, the work made in the working tree files that are not
yet added to the index will be forever lost. This patch is intended to
mitigate this. This is a trade-off between lost worktree changes,
which may not be present anywhere else, and lost index modifications,
which can be recreated.

Introduce the `restore.defaultDestination` configuration variable,
which can be set to one of "both", "index", or "worktree", useful for
users who want to set it to "index" to avoid touching the working tree
files by mistake. They now force themselves to use the `--worktree`
option explicitly when they want to restore the working tree files.

Signed-off-by: Hugo Sales <hugo@xxxxxxx>
---
I think I addressed your comments in the patch below.

> I think a better solution would be to teach
> these users to use "git reset -- <path>" when they want to reset the
> index entries, and not run the "git restore" command.

I read _somewhere_ that `git restore` was the Modern Replacement for `git
checkout` and `git reset`, along with `git switch`. I do find it more intuite,
too. Friends I talked with liked this change, as they've also read similar
things and lost work due to a missing `--staged`, so they stopped using it. I
find `git reset` somewhat confusing, as I use it for `git reset --soft HEAD~`
and `git reset --hard <commit-ish>`, so I don't really associate it with the
same usage as `git restore`.

> +static const char *checkout_default_index_worktree;
> +static int git_restore_config(const char *var, const char *value, void *cb)
> +{
> +	struct checkout_opts *opts = cb;
> +
> +	if (!strcmp(var, "restore.defaultdestination")) {
> +		git_config_string(&checkout_default_index_worktree, var, value);

I'm not sure if this use of `git_config_string` is required, as `value` seems to
contain the desired value, but this seems to be how it's done elsewhere.

> > +	git restore one &&
> > +	test -z $(git status --porcelain --untracked-files=no) &&
> 
> This (together with many other uses of "git status" in the new
> tests) will not catch a segfaulting "git status".

I replaced this with `test_must_be_empty`, I'm not sure if that's what you
meant. I wasn't able to find a version that doesn't need to write to a file.

P.S. I hope the following reaches you correctly, as I'm still struggling to
connect to mailbox.org with send-email.

 Documentation/config.txt         |   2 +
 Documentation/config/restore.txt |  16 ++++
 Documentation/git-restore.txt    |  17 ++--
 builtin/checkout.c               |  25 ++++++
 t/t2070-restore.sh               | 143 +++++++++++++++++++++++++++++++
 5 files changed, 198 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/config/restore.txt

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 0e93aef862..4359c63794 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -501,6 +501,8 @@ include::config/repack.txt[]
 
 include::config/rerere.txt[]
 
+include::config/restore.txt[]
+
 include::config/revert.txt[]
 
 include::config/safe.txt[]
diff --git a/Documentation/config/restore.txt b/Documentation/config/restore.txt
new file mode 100644
index 0000000000..6a06310b4a
--- /dev/null
+++ b/Documentation/config/restore.txt
@@ -0,0 +1,16 @@
+restore.defaultDestination::
+	Valid values: "worktree", "staged" or "both". Controls the default
+	behavior of `git restore` without `--worktree` or `--staged`. If
+	"worktree", `git restore` without `--worktree` or `--staged` is
+	equivalent to `git restore --worktree`. If "staged", `git restore`
+	without `--worktree` or `--staged` is equivalent to `git restore
+	--staged`. If "both", `git restore` without `--worktree` or `--staged`
+	is equivalent to `git restore --worktree --staged`. Adding an option
+	overrides the default, such that if the configuration variable is set to
+	"staged", specifying `--worktree` will only affect the worktree, not
+	both. This variable can be set to "staged" to help prevent accidentally
+	losing modifications to the worktree, caused by running `git restore .`
+	when `git restore --staged .` was intended. In this case, modifications
+	to the index would be lost, which could also be a significant amount of
+	work, so care is highly recommended.
+	See linkgit:git-restore[1]
diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt
index 5964810caa..391c367d32 100644
--- a/Documentation/git-restore.txt
+++ b/Documentation/git-restore.txt
@@ -14,14 +14,18 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Restore specified paths in the working tree with some contents from a
+Restore specified paths in the working tree and/or index with some contents from a
 restore source. If a path is tracked but does not exist in the restore
 source, it will be removed to match the source.
 
-The command can also be used to restore the content in the index with
+The command can be used to restore the content in the index with
 `--staged`, or restore both the working tree and the index with
 `--staged --worktree`.
 
+The configuration vairable `restore.defaultDestination`, which accepts values
+"worktree", "staged" or "both", can be used to control the default behavior for
+which flag(s) apply if neither `--staged` nor `--worktree` is supplied.
+
 By default, if `--staged` is given, the contents are restored from `HEAD`,
 otherwise from the index. Use `--source` to restore from a different commit.
 
@@ -59,9 +63,12 @@ all modified paths.
 --worktree::
 -S::
 --staged::
-	Specify the restore location. If neither option is specified,
-	by default the working tree is restored. Specifying `--staged`
-	will only restore the index. Specifying both restores both.
+	Specify the restore destination. If neither option is specified, the default
+	depends on the `'restore.defaultDestination` configuration variable,
+	which can be "worktree" (the default), "staged" or "both", to control
+	which of the two flags is assumed if none are given. Specifying
+	`--worktree` will only restore the worktree. Specifying `--staged` will
+	only restore the index. Specifying both restores both.
 
 -q::
 --quiet::
diff --git a/builtin/checkout.c b/builtin/checkout.c
index a5155cf55c..4a9131ec29 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1922,6 +1922,29 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
 	return ret;
 }
 
+static const char *checkout_default_index_worktree;
+static int git_restore_config(const char *var, const char *value, void *cb)
+{
+	struct checkout_opts *opts = cb;
+
+	if (!strcmp(var, "restore.defaultdestination")) {
+		git_config_string(&checkout_default_index_worktree, var, value);
+		if (!strcmp(checkout_default_index_worktree, "both")) {
+			opts->checkout_index = -2;    /* default on */
+			opts->checkout_worktree = -2; /* default on */
+		} else if (!strcmp(checkout_default_index_worktree, "staged")) {
+			opts->checkout_index = -2;    /* default on */
+			opts->checkout_worktree = -1; /* default off */
+		} else {
+			opts->checkout_index = -1;    /* default off */
+			opts->checkout_worktree = -2; /* default on */
+		}
+		return 0;
+	}
+	return git_xmerge_config(var, value, NULL);
+}
+
+
 int cmd_restore(int argc, const char **argv, const char *prefix)
 {
 	struct checkout_opts opts;
@@ -1950,6 +1973,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
 	opts.checkout_worktree = -2; /* default on */
 	opts.ignore_unmerged_opt = "--ignore-unmerged";
 
+	git_config(git_restore_config, &opts);
+
 	options = parse_options_dup(restore_options);
 	options = add_common_options(&opts, options);
 	options = add_checkout_path_options(&opts, options);
diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh
index 7c43ddf1d9..c3a35dd878 100755
--- a/t/t2070-restore.sh
+++ b/t/t2070-restore.sh
@@ -137,4 +137,147 @@ test_expect_success 'restore --staged invalidates cache tree for deletions' '
 	test_must_fail git rev-parse HEAD:new1
 '
 
+test_expect_success 'restore with restore.defaultDestination unset works as if --worktree given' '
+	test_when_finished git reset --hard HEAD^ &&
+	test_commit root-unset-restore.defaultDestination &&
+	test_commit unset-restore.defaultDestination one one &&
+	>one &&
+
+	git restore one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status &&
+
+	>one &&
+	git add one &&
+	git restore one &&
+	git status --porcelain --untracked-files=no | grep "^M " &&
+
+	>one &&
+	git add one &&
+	git restore --worktree one &&
+	git status --porcelain --untracked-files=no | grep "^M " &&
+
+	git restore --staged one &&
+	git status --porcelain --untracked-files=no | grep "^ M" &&
+
+	>one &&
+	git add one &&
+	git restore --worktree --staged one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status
+'
+
+test_expect_success 'restore with restore.defaultDestination set to worktree works as if --worktree given' '
+	test_when_finished git reset --hard HEAD^ &&
+	test_when_finished git config --unset restore.defaultDestination &&
+	test_commit root-worktree-restore.defaultDestination &&
+	test_commit worktree-restore.defaultDestination one one &&
+	git config restore.defaultDestination worktree &&
+	>one &&
+
+	git restore one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status &&
+
+	>one &&
+	git add one &&
+	git restore one &&
+	git status --porcelain --untracked-files=no | grep "^M " &&
+
+	>one &&
+	git add one &&
+	git restore --worktree one &&
+	git status --porcelain --untracked-files=no | grep "^M " &&
+
+	git restore --staged one &&
+	git status --porcelain --untracked-files=no | grep "^ M" &&
+
+	>one &&
+	git add one &&
+	git restore --worktree --staged one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status
+'
+
+test_expect_success 'restore with restore.defaultDestination set to staged works as if --staged given' '
+	test_when_finished git reset --hard HEAD^ &&
+	test_when_finished git config --unset restore.defaultDestination &&
+	test_commit root-staged-restore.defaultDestination &&
+	test_commit staged-restore.defaultDestination one one &&
+	git config restore.defaultDestination staged &&
+	>one &&
+
+	git restore one &&
+	git status --porcelain --untracked-files=no | grep "^ M" &&
+
+	git restore --staged one &&
+	git status --porcelain --untracked-files=no | grep "^ M" &&
+
+	git add one &&
+	git restore one &&
+	git status --porcelain --untracked-files=no | grep "^ M" &&
+
+	git add one &&
+	git restore --staged one &&
+	git status --porcelain --untracked-files=no | grep "^ M" &&
+
+	git restore --worktree one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status &&
+
+	>one &&
+	git add one &&
+	git restore --worktree --staged one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status
+'
+
+test_expect_success 'restore with restore.defaultDestination set to both works as if --worktree --staged given' '
+	test_when_finished git reset --hard HEAD^ &&
+	test_when_finished git config --unset restore.defaultDestination &&
+	test_commit root-both-restore.defaultDestination &&
+	test_commit both-restore.defaultDestination one one &&
+	git config restore.defaultDestination both &&
+	>one &&
+
+	git restore one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status &&
+
+	>one &&
+	git add one &&
+	git restore --staged one &&
+	git status --porcelain --untracked-files=no | grep "^ M"  &&
+
+	git add one &&
+	git restore one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status &&
+
+	>one &&
+	git add one &&
+	git restore --staged one &&
+	git status --porcelain --untracked-files=no | grep "^ M" &&
+
+	git restore --worktree one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status &&
+
+	>one &&
+	git add one &&
+	git restore --worktree --staged one &&
+	git status --porcelain --untracked-files=no >status &&
+	test_must_be_empty status &&
+	rm status
+'
+
 test_done
-- 
2.39.2


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux