]> git.ipfire.org Git - thirdparty/git.git/commitdiff
pull: add pull.autoStash config option
authorLidong Yan <yldhome2d2@gmail.com>
Sun, 20 Jul 2025 12:43:34 +0000 (20:43 +0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 21 Jul 2025 22:01:21 +0000 (15:01 -0700)
Git uses `rebase.autostash` or `merge.autostash` to determine whether a
dirty worktree is allowed during pull. However, this behavior is not
clearly documented, making it difficult for users to discover how to
enable autostash, or causing them to unknowingly enable it. Add new
config option `pull.autostash` along with its documentation and test
cases.

`pull.autostash` provides the same functionality as `rebase.autostash`
and `merge.autostash`, but overrides them when set. If `pull.autostash`
is not set, it falls back to `rebase.autostash` or `merge.autostash`,
depending on the value of `pull.rebase`.

Signed-off-by: Lidong Yan <yldhome2d2@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/config/pull.adoc
builtin/pull.c
t/t5520-pull.sh

index 9349e09261b25ce0d80b2543810fd4e8a3e08bf6..125c930f72c8db63f31308ea5bd8fd9188e15d5e 100644 (file)
@@ -29,5 +29,21 @@ pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
 
+pull.autoStash::
+       When set to true, automatically create a temporary stash entry
+       to record the local changes before the operation begins, and
+       restore them after the operation completes.  When your "git
+       pull" rebases (instead of merges), this may be convenient, since
+       unlike merging pull that tolerates local changes that do not
+       interfere with the merge, rebasing pull refuses to work with any
+       local changes.
++
+If `pull.autostash` is set (either to true or false),
+`merge.autostash` and `rebase.autostash` are ignored.  If
+`pull.autostash` is not set at all, depending on the value of
+`pull.rebase`, `merge.autostash` or `rebase.autostash` is used
+instead.  Can be overridden by the `--[no-]autostash` command line
+option.
+
 pull.twohead::
        The default merge strategy to use when pulling a single branch.
index a1ebc6ad3328e074b105246f6bf5c41b063c17c9..1b2bacc255eed61e3a1b846976140c39b54566b5 100644 (file)
@@ -90,7 +90,8 @@ static char *opt_ff;
 static const char *opt_verify_signatures;
 static const char *opt_verify;
 static int opt_autostash = -1;
-static int config_autostash;
+static int config_rebase_autostash;
+static int config_pull_autostash = -1;
 static int check_trust_level = 1;
 static struct strvec opt_strategies = STRVEC_INIT;
 static struct strvec opt_strategy_opts = STRVEC_INIT;
@@ -364,7 +365,18 @@ static int git_pull_config(const char *var, const char *value,
                           const struct config_context *ctx, void *cb)
 {
        if (!strcmp(var, "rebase.autostash")) {
-               config_autostash = git_config_bool(var, value);
+               /*
+                * run_rebase() also reads this option. The reason we handle it here is
+                * that when pull.rebase is true, a fast-forward may occur without
+                * invoking run_rebase(). We need to ensure that autostash is set even
+                * in the fast-forward case.
+                *
+                * run_merge() handles merge.autostash, so we don't handle it here.
+                */
+               config_rebase_autostash = git_config_bool(var, value);
+               return 0;
+       } else if (!strcmp(var, "pull.autostash")) {
+               config_pull_autostash = git_config_bool(var, value);
                return 0;
        } else if (!strcmp(var, "submodule.recurse")) {
                recurse_submodules = git_config_bool(var, value) ?
@@ -1003,6 +1015,8 @@ int cmd_pull(int argc,
        }
 
        argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
+       if (opt_autostash == -1)
+               opt_autostash = config_pull_autostash;
 
        if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT)
                recurse_submodules = recurse_submodules_cli;
@@ -1049,7 +1063,7 @@ int cmd_pull(int argc,
 
        if (opt_rebase) {
                if (opt_autostash == -1)
-                       opt_autostash = config_autostash;
+                       opt_autostash = config_rebase_autostash;
 
                if (is_null_oid(&orig_head) && !is_index_unborn(the_repository->index))
                        die(_("Updating an unborn branch with changes added to the index."));
index 63c9a8f04b1cb9873c60fe5ccdf86705b4f0623b..0e0019347e85e9949fe6c2c63b9f86f6c4ce4519 100755 (executable)
@@ -472,6 +472,66 @@ test_expect_success 'pull --no-autostash & merge.autostash unset' '
        test_pull_autostash_fail --no-autostash --no-rebase
 '
 
+test_expect_success 'pull succeeds with dirty working directory and pull.autostash=true' '
+       test_config pull.autostash true &&
+       test_pull_autostash 1 --rebase &&
+       test_pull_autostash 2 --no-rebase &&
+       test_pull_autostash 1 --autostash --rebase &&
+       test_pull_autostash 2 --autostash --no-rebase
+'
+
+test_expect_success 'pull fails with dirty working directory and pull.autostash=false' '
+       test_config pull.autostash false &&
+       test_pull_autostash_fail --rebase &&
+       test_pull_autostash_fail --no-rebase &&
+       test_pull_autostash_fail --no-autostash --rebase &&
+       test_pull_autostash_fail --no-autostash --no-rebase
+'
+
+test_expect_success 'pull --autostash overrides pull.autostash=false' '
+       test_config pull.autostash false &&
+       test_pull_autostash 1 --autostash --rebase &&
+       test_pull_autostash 2 --autostash --no-rebase
+'
+
+test_expect_success 'pull --no-autostash overrides pull.autostash=true' '
+       test_config pull.autostash true &&
+       test_pull_autostash_fail --no-autostash --rebase &&
+       test_pull_autostash_fail --no-autostash --no-rebase
+'
+
+test_expect_success 'pull.autostash=true overrides rebase.autostash' '
+       test_config pull.autostash true &&
+       test_config rebase.autostash true &&
+       test_pull_autostash 1 --rebase &&
+       test_config rebase.autostash false &&
+       test_pull_autostash 1 --rebase
+'
+
+test_expect_success 'pull.autostash=false overrides rebase.autostash' '
+       test_config pull.autostash false &&
+       test_config rebase.autostash true &&
+       test_pull_autostash_fail --rebase &&
+       test_config rebase.autostash false &&
+       test_pull_autostash_fail --rebase
+'
+
+test_expect_success 'pull.autostash=true overrides merge.autostash' '
+       test_config pull.autostash true &&
+       test_config merge.autostash true &&
+       test_pull_autostash 2 --no-rebase &&
+       test_config merge.autostash false &&
+       test_pull_autostash 2 --no-rebase
+'
+
+test_expect_success 'pull.autostash=false overrides merge.autostash' '
+       test_config pull.autostash false &&
+       test_config merge.autostash true &&
+       test_pull_autostash_fail --no-rebase &&
+       test_config merge.autostash false &&
+       test_pull_autostash_fail --no-rebase
+'
+
 test_expect_success 'pull.rebase' '
        git reset --hard before-rebase &&
        test_config pull.rebase true &&