Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add restore.defaultLocation option #1467

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

someonewithpc
Copy link

@someonewithpc someonewithpc commented Mar 11, 2023

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.

CC: Ævar Arnfjörð Bjarmason avarab@gmail.com, Jeff King peff@peff.net,
Victoria Dye vdye@github.com


I tried to send with git send-email, but I'm having problems. My mail provider is mailbox.org and I'm getting Command unknown: 'AUTH' at /usr/lib/git-core/git-send-email line 1691. Apologies if it actually went through.

@gitgitgadget-git
Copy link

Welcome to GitGitGadget

Hi @someonewithpc, and welcome to GitGitGadget, the GitHub App to send patch series to the Git mailing list from GitHub Pull Requests.

Please make sure that your Pull Request has a good description, as it will be used as cover letter. You can CC potential reviewers by adding a footer to the PR description with the following syntax:

CC: Revi Ewer <revi.ewer@example.com>, Ill Takalook <ill.takalook@example.net>

Also, it is a good idea to review the commit messages one last time, as the Git project expects them in a quite specific form:

  • the lines should not exceed 76 columns,
  • the first line should be like a header and typically start with a prefix like "tests:" or "revisions:" to state which subsystem the change is about, and
  • the commit messages' body should be describing the "why?" of the change.
  • Finally, the commit messages should end in a Signed-off-by: line matching the commits' author.

It is in general a good idea to await the automated test ("Checks") in this Pull Request before contributing the patches, e.g. to avoid trivial issues such as unportable code.

Contributing the patches

Before you can contribute the patches, your GitHub username needs to be added to the list of permitted users. Any already-permitted user can do that, by adding a comment to your PR of the form /allow. A good way to find other contributors is to locate recent pull requests where someone has been /allowed:

Both the person who commented /allow and the PR author are able to /allow you.

An alternative is the channel #git-devel on the Libera Chat IRC network:

<newcontributor> I've just created my first PR, could someone please /allow me? https://github.com/gitgitgadget/git/pull/12345
<veteran> newcontributor: it is done
<newcontributor> thanks!

Once on the list of permitted usernames, you can contribute the patches to the Git mailing list by adding a PR comment /submit.

If you want to see what email(s) would be sent for a /submit request, add a PR comment /preview to have the email(s) sent to you. You must have a public GitHub email address for this. Note that any reviewers CC'd via the list in the PR description will not actually be sent emails.

After you submit, GitGitGadget will respond with another comment that contains the link to the cover letter mail in the Git mailing list archive. Please make sure to monitor the discussion in that thread and to address comments and suggestions (while the comments and suggestions will be mirrored into the PR by GitGitGadget, you will still want to reply via mail).

If you do not want to subscribe to the Git mailing list just to be able to respond to a mail, you can download the mbox from the Git mailing list archive (click the (raw) link), then import it into your mail program. If you use GMail, you can do this via:

curl -g --user "<EMailAddress>:<Password>" \
    --url "imaps://imap.gmail.com/INBOX" -T /path/to/raw.txt

To iterate on your change, i.e. send a revised patch or patch series, you will first want to (force-)push to the same branch. You probably also want to modify your Pull Request description (or title). It is a good idea to summarize the revision by adding something like this to the cover letter (read: by editing the first comment on the PR, i.e. the PR description):

Changes since v1:
- Fixed a typo in the commit message (found by ...)
- Added a code comment to ... as suggested by ...
...

To send a new iteration, just add another PR comment with the contents: /submit.

Need help?

New contributors who want advice are encouraged to join git-mentoring@googlegroups.com, where volunteers who regularly contribute to Git are willing to answer newbie questions, give advice, or otherwise provide mentoring to interested contributors. You must join in order to post or view messages, but anyone can join.

You may also be able to find help in real time in the developer IRC channel, #git-devel on Libera Chat. Remember that IRC does not support offline messaging, so if you send someone a private message and log out, they cannot respond to you. The scrollback of #git-devel is archived, though.

@Labnann
Copy link
Contributor

Labnann commented Mar 11, 2023

/allow

@gitgitgadget-git
Copy link

User someonewithpc is now allowed to use GitGitGadget.

@someonewithpc
Copy link
Author

/preview

@gitgitgadget-git
Copy link

Preview email sent as pull.1467.git.git.1678577673306.gitgitgadget@gmail.com

@someonewithpc
Copy link
Author

/submit

@gitgitgadget-git
Copy link

Submitted as pull.1467.git.git.1678578153640.gitgitgadget@gmail.com

To fetch this version into FETCH_HEAD:

git fetch https://github.com/gitgitgadget/git/ pr-git-1467/someonewithpc/master-v1

To fetch this version to local tag pr-git-1467/someonewithpc/master-v1:

git fetch --no-tags https://github.com/gitgitgadget/git/ tag pr-git-1467/someonewithpc/master-v1

@gitgitgadget-git
Copy link

On the Git mailing list, Junio C Hamano wrote (reply to this):

"Hugo Sales via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Hugo Sales <hugo@hsal.es>
>
> This options allows control over which of `--worktree` or `--staged` is
> applied when `git restore` is invoked with neither

We do not want to do this.  Tutorials and documents will be written
assuming the official default, and those users who set it to use a
different default, only because they are told to use it before they
know better, would waste a lot of time wondering why the procedures
they read from tutorials and documents would not work for them.
Even an expert who is asked to help a novice, who has this variable
set, would end up getting confused becaue "git restore" without the
explict options does not work as they expect.

Individual users are welcome to use "[alias] ri = restore --staged"
and this will hurt nobody.

@gitgitgadget-git
Copy link

On the Git mailing list, Junio C Hamano wrote (reply to this):

Junio C Hamano <gitster@pobox.com> writes:

> "Hugo Sales via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
>> From: Hugo Sales <hugo@hsal.es>
>>
>> This options allows control over which of `--worktree` or `--staged` is
>> applied when `git restore` is invoked with neither
>
> We do not want to do this.  Tutorials and documents will be written
> assuming the official default, ...

Well, I think this change is no different from any other
configuration option and it may be OK.

My initial reaction primarily came from the fact that the choice
between the index and the working tree is so fundamental aspect of
the behaviour of the command that people who wrote their script
would be relying on it not to change.  But given that the command is
still marked as experimental, as long as the new behaviour is
clearly documented to warn those who care *not* to rely on the
default behaviour and tell them to instead always give explicitly
these "--worktree" and/or "--staged" options, it would be OK.

This actually is a more important tangent, but if you think the
command invites mistakes from users who forget to give "--staged",
it may indicate that the command is too overloaded, and the UI might
be improved by removing the feature from this command and instead
encouraging people to use "git reset" to futz with the contents of
the index.  I dunno.

@gitgitgadget-git
Copy link

On the Git mailing list, Junio C Hamano wrote (reply to this):

"Hugo Sales via GitGitGadget" <gitgitgadget@gmail.com> 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@hsal.es>
>
> 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.

`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@hsal.es>
@gitgitgadget-git
Copy link

On the Git mailing list, Hugo Sales wrote (reply to this):

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@hsal.es>
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@hsal.es>
---
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@gmail.com> 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@hsal.es>
>>
>> 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.

@gitgitgadget-git
Copy link

On the Git mailing list, Hugo Sales wrote (reply to this):

Wanted to add that since `git status` suggests using `git restore --staged` to
reset index entries, I'm sure it has happened many times that a user ran `git
restore` and lost work

@Erdaza

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
3 participants