diff --git a/Documentation/config.txt b/Documentation/config.txt index 0e93aef86264db..4359c63794e6af 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 00000000000000..6a06310b4aa584 --- /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 5964810caa4153..72acf5ee4ad6a9 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 config options `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 location. If neither option is specified, the + default depends on the `'restore.defaultDestination` config option, 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 a5155cf55c1e51..9ab7fe9671468f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1922,6 +1922,31 @@ 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")) { + fprintf(stderr, value); + 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 +1975,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 7c43ddf1d99714..7ad49bf357292e 100755 --- a/t/t2070-restore.sh +++ b/t/t2070-restore.sh @@ -137,4 +137,148 @@ 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