{"thread":{"id":"66188","subject":"[PATCH] pull: add --hard mode","startedAt":"2026-08-18T11:34:36Z","lastAt":"2026-08-24T14:11:44Z","messageCount":9,"participants":["Artur Bieniek via GitGitGadget","Junio C Hamano","Phillip Wood","Artur Bieniek","Kristoffer Haugsbakk"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"550744","messageId":"pull.2384.git.git.1787052873141.gitgitgadget@gmail.com","threadId":"66188","inReplyTo":null,"subject":"[PATCH] pull: add --hard mode","fromName":"Artur Bieniek via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-18T11:34:33Z","receivedAt":"2026-08-18T11:34:36Z","isPatch":true,"body":"From: Artur Bieniek <ar2rekb@gmail.com>\n\nAdd --hard as an explicit alternative to merge and rebase. After\nfetching, require a single integration candidate and reset the current\nbranch, index, and working tree to it.\n\nPreserve quiet and submodule recursion behavior, and reject options\nthat cannot be honored by a hard reset. Document the destructive\nsemantics and cover them in tests.\n\nSigned-off-by: Artur Bieniek <ar2rekb@gmail.com>\n---\n    [RFC] Add git pull --hard mode\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2384%2FArturBieniek4%2Fpull-hard-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2384/ArturBieniek4/pull-hard-v1\nPull-Request: https://github.com/git/git/pull/2384\n\n Documentation/git-pull.adoc | 10 +++++-\n builtin/pull.c              | 68 ++++++++++++++++++++++++++++++++-----\n t/t5521-pull-options.sh     | 31 +++++++++++++++++\n t/t5572-pull-submodule.sh   | 18 ++++++++++\n 4 files changed, 118 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-pull.adoc b/Documentation/git-pull.adoc\nindex 88f4fd3926..3a103a1630 100644\n--- a/Documentation/git-pull.adoc\n+++ b/Documentation/git-pull.adoc\n@@ -24,7 +24,7 @@ with no arguments this defaults to the <<UPSTREAM-BRANCHES,upstream>>\n for the current branch.\n Then it integrates that branch into the current branch.\n \n-There are 4 main options for integrating the remote branch:\n+There are 5 main options for integrating the remote branch:\n \n 1. `git pull --ff-only` will only do \"fast-forward\" updates: it\n    fails if your local branch has diverged from the remote branch.\n@@ -32,6 +32,7 @@ There are 4 main options for integrating the remote branch:\n 2. `git pull --rebase` runs `git rebase`\n 3. `git pull --no-rebase` runs `git merge`.\n 4. `git pull --squash` runs `git merge --squash`\n+5. `git pull --hard` runs `git reset --hard` to the fetched branch.\n \n You can also set the configuration options `pull.rebase`, `pull.squash`,\n or `pull.ff` with your preferred behaviour.\n@@ -119,6 +120,13 @@ unless you have read linkgit:git-rebase[1] carefully.\n `--no-rebase`::\n \tThis is shorthand for `--rebase=false`.\n \n+`--hard`::\n+\tReset the current branch, index, and working tree to the fetched branch.\n+\tThe pull must select exactly one branch. Local commits and changes to\n+\ttracked files are discarded. Untracked files or directories in the way\n+\tof writing tracked files may also be deleted. This option cannot be\n+\tcombined with merge or rebase options, or with `--append`.\n+\n Options related to fetching\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex db3ee0aab3..8324f06084 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -91,6 +91,7 @@ static char *opt_ff;\n static const char *opt_verify_signatures;\n static const char *opt_verify;\n static int opt_autostash = -1;\n+static int opt_hard;\n static int config_rebase_autostash;\n static int config_pull_autostash = -1;\n static int check_trust_level = 1;\n@@ -318,7 +319,9 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs\n \tconst char *remote = curr_branch ? curr_branch->remote_name : NULL;\n \n \tif (*refspecs) {\n-\t\tif (opt_rebase)\n+\t\tif (opt_hard)\n+\t\t\tfprintf_ln(stderr, _(\"There is no candidate for resetting to among the refs that you just fetched.\"));\n+\t\telse if (opt_rebase)\n \t\t\tfprintf_ln(stderr, _(\"There is no candidate for rebasing against among the refs that you just fetched.\"));\n \t\telse\n \t\t\tfprintf_ln(stderr, _(\"There are no candidates for merging among the refs that you just fetched.\"));\n@@ -331,7 +334,9 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs\n \t\t\trepo);\n \t} else if (!curr_branch) {\n \t\tfprintf_ln(stderr, _(\"You are not currently on a branch.\"));\n-\t\tif (opt_rebase)\n+\t\tif (opt_hard)\n+\t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to reset to.\"));\n+\t\telse if (opt_rebase)\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to rebase against.\"));\n \t\telse\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to merge with.\"));\n@@ -346,7 +351,9 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs\n \t\t\tremote_name = _(\"<remote>\");\n \n \t\tfprintf_ln(stderr, _(\"There is no tracking information for the current branch.\"));\n-\t\tif (opt_rebase)\n+\t\tif (opt_hard)\n+\t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to reset to.\"));\n+\t\telse if (opt_rebase)\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to rebase against.\"));\n \t\telse\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to merge with.\"));\n@@ -358,7 +365,11 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs\n \t\tfprintf(stderr, \"\\n\");\n \t\tfprintf_ln(stderr, \"    git branch --set-upstream-to=%s/%s %s\\n\",\n \t\t\t\tremote_name, _(\"<branch>\"), curr_branch->name);\n-\t} else\n+\t} else if (opt_hard)\n+\t\tfprintf_ln(stderr, _(\"Your configuration specifies to reset to the ref '%s'\\n\"\n+\t\t\t\"from the remote, but no such ref was fetched.\"),\n+\t\t\tcurr_branch->merge[0]->src);\n+\telse\n \t\tfprintf_ln(stderr, _(\"Your configuration specifies to merge with the ref '%s'\\n\"\n \t\t\t\"from the remote, but no such ref was fetched.\"),\n \t\t\tcurr_branch->merge[0]->src);\n@@ -570,6 +581,23 @@ static int run_merge(void)\n \treturn run_command(&cmd);\n }\n \n+static int run_reset(const struct object_id *oid)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\n+\tstrvec_pushl(&cmd.args, \"reset\", \"--hard\", NULL);\n+\tif (opt_verbosity < 0)\n+\t\tstrvec_push(&cmd.args, \"--quiet\");\n+\tif (recurse_submodules == RECURSE_SUBMODULES_ON ||\n+\t    recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)\n+\t\tstrvec_push(&cmd.args, \"--recurse-submodules\");\n+\telse if (recurse_submodules == RECURSE_SUBMODULES_OFF)\n+\t\tstrvec_push(&cmd.args, \"--no-recurse-submodules\");\n+\tstrvec_push(&cmd.args, oid_to_hex(oid));\n+\tcmd.git_cmd = 1;\n+\treturn run_command(&cmd);\n+}\n+\n /**\n  * Returns remote's upstream branch for the current branch. If remote is NULL,\n  * the current branch's configured default remote is used. Returns NULL if\n@@ -925,6 +953,8 @@ int cmd_pull(int argc,\n \t\t\tPARSE_OPT_NOARG),\n \t\tOPT_BOOL(0, \"autostash\", &opt_autostash,\n \t\t\tN_(\"automatically stash/stash pop before and after\")),\n+\t\tOPT_BOOL(0, \"hard\", &opt_hard,\n+\t\t\tN_(\"reset hard to the fetched branch\")),\n \t\tOPT_PASSTHRU_ARGV('s', \"strategy\", &opt_strategies, N_(\"strategy\"),\n \t\t\tN_(\"merge strategy to use\"),\n \t\t\t0),\n@@ -1022,6 +1052,16 @@ int cmd_pull(int argc,\n \t}\n \n \targc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);\n+\tif (opt_hard &&\n+\t    (opt_rebase >= 0 || opt_diffstat || opt_log || opt_signoff ||\n+\t     opt_squash || opt_commit || opt_edit || cleanup_arg || opt_ff ||\n+\t     opt_verify_signatures || opt_verify || opt_autostash >= 0 ||\n+\t     opt_strategies.nr || opt_strategy_opts.nr || opt_gpg_sign ||\n+\t     opt_allow_unrelated_histories))\n+\t\tdie(_(\"--hard cannot be combined with merge or rebase options\"));\n+\tdie_for_incompatible_opt2(opt_hard, \"--hard\",\n+\t\t\t\t  opt_append && !strcmp(opt_append, \"--append\"),\n+\t\t\t\t  \"--append\");\n \tif (opt_autostash == -1)\n \t\topt_autostash = config_pull_autostash;\n \n@@ -1037,7 +1077,7 @@ int cmd_pull(int argc,\n \n \tparse_repo_refspecs(argc, argv, &repo, &refspecs);\n \n-\tif (!opt_ff) {\n+\tif (!opt_hard && !opt_ff) {\n \t\topt_ff = xstrdup_or_null(config_get_ff());\n \t\t/*\n \t\t * A subtle point: opt_ff was set on the line above via\n@@ -1056,13 +1096,15 @@ int cmd_pull(int argc,\n \t\t}\n \t}\n \n-\tif (opt_rebase < 0)\n+\tif (opt_hard)\n+\t\topt_rebase = REBASE_FALSE;\n+\telse if (opt_rebase < 0)\n \t\topt_rebase = config_get_rebase(&rebase_unspecified);\n \n-\tif (repo_read_index_unmerged(the_repository))\n+\tif (!opt_hard && repo_read_index_unmerged(the_repository))\n \t\tdie_resolve_conflict(\"pull\");\n \n-\tif (file_exists(git_path_merge_head(the_repository)))\n+\tif (!opt_hard && file_exists(git_path_merge_head(the_repository)))\n \t\tdie_conclude_merge();\n \n \tif (repo_get_oid(the_repository, \"HEAD\", &orig_head))\n@@ -1090,6 +1132,16 @@ int cmd_pull(int argc,\n \tif (opt_dry_run)\n \t\treturn 0;\n \n+\tif (opt_hard) {\n+\t\tget_merge_heads(&merge_heads);\n+\t\tif (!merge_heads.nr)\n+\t\t\tdie_no_merge_candidates(repo, refspecs);\n+\t\tif (merge_heads.nr > 1)\n+\t\t\tdie(_(\"Cannot hard reset to multiple branches.\"));\n+\t\tret = run_reset(merge_heads.oid);\n+\t\tgoto cleanup;\n+\t}\n+\n \tif (repo_get_oid(the_repository, \"HEAD\", &curr_head))\n \t\toidclr(&curr_head, the_repository->hash_algo);\n \ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 5e420c208c..31bb76b465 100755\n--- a/t/t5521-pull-options.sh\n+++ b/t/t5521-pull-options.sh\n@@ -117,6 +117,37 @@ test_expect_success 'git pull --force' '\n \t)\n '\n \n+test_expect_success 'git pull --hard' '\n+\ttest_when_finished \"rm -rf hard-parent hard\" &&\n+\tgit init hard-parent &&\n+\ttest_commit -C hard-parent base &&\n+\tgit clone hard-parent hard &&\n+\ttest_commit -C hard local &&\n+\ttest_commit -C hard-parent upstream obstruct upstream &&\n+\tgit -C hard-parent branch side &&\n+\t(\n+\t\tcd hard &&\n+\t\techo dirty >base.t &&\n+\t\tmkdir obstruct &&\n+\t\techo untracked >obstruct/file &&\n+\t\ttest_must_fail git pull --hard --ff-only 2>err &&\n+\t\ttest_grep \"cannot be combined\" err &&\n+\t\ttest_must_fail git pull --hard -a 2>err &&\n+\t\ttest_grep \"options .*--hard.* and .*--append.*\" err &&\n+\t\ttest_must_fail git pull --hard origin main side 2>err &&\n+\t\ttest_grep \"Cannot hard reset to multiple branches\" err &&\n+\t\tgit pull --hard &&\n+\t\ttest_cmp_rev HEAD origin/main &&\n+\t\ttest_path_is_missing local.t &&\n+\t\ttest_path_is_file obstruct &&\n+\t\tgit diff --quiet &&\n+\t\tgit diff --cached --quiet &&\n+\t\tgit pull -q --hard >out 2>quiet-err &&\n+\t\ttest_must_be_empty out &&\n+\t\ttest_must_be_empty quiet-err\n+\t)\n+'\n+\n test_expect_success 'git pull --all' '\n \tmkdir clonedmulti &&\n \t(cd clonedmulti && git init &&\ndiff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh\nindex 42d14328b6..f2df277b79 100755\n--- a/t/t5572-pull-submodule.sh\n+++ b/t/t5572-pull-submodule.sh\n@@ -106,6 +106,24 @@ test_expect_success \" --[no-]recurse-submodule and submodule.recurse\" '\n \ttest_path_is_file super/sub/merge_strategy_4.t\n '\n \n+test_expect_success 'pull --hard honors submodule recursion' '\n+\ttest_commit -C child hard_recurse &&\n+\tgit -C parent submodule update --remote &&\n+\tgit -C parent add sub &&\n+\tgit -C parent commit -m \"update submodule\" &&\n+\n+\tgit -C super pull --hard --recurse-submodules &&\n+\ttest_path_is_file super/sub/hard_recurse.t &&\n+\n+\ttest_commit -C child hard_no_recurse &&\n+\tgit -C parent submodule update --remote &&\n+\tgit -C parent add sub &&\n+\tgit -C parent commit -m \"update submodule\" &&\n+\n+\tgit -C super -c submodule.recurse=true pull --hard --no-recurse-submodules &&\n+\ttest_path_is_missing super/sub/hard_no_recurse.t\n+'\n+\n test_expect_success \"fetch.recurseSubmodules option triggers recursive fetch (but not recursive update)\" '\n \ttest_commit -C child merge_strategy_5 &&\n \t# Omit the parent commit, otherwise this passes with the\n\nbase-commit: 745601a9a94110d74769ab605ccd4f61339758d2\n-- \ngitgitgadget\n"},{"id":"550757","messageId":"xmqqwltn1o4e.fsf@gitster.g","threadId":"66188","inReplyTo":"pull.2384.git.git.1787052873141.gitgitgadget@gmail.com","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-18T14:48:01Z","receivedAt":"2026-08-18T14:48:04Z","isPatch":true,"body":"\"Artur Bieniek via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Artur Bieniek <ar2rekb@gmail.com>\n>\n> Add --hard as an explicit alternative to merge and rebase. After\n> fetching, require a single integration candidate and reset the current\n> branch, index, and working tree to it.\n\nThere may be a population of users who *never* make changes to their\nhistory or working tree, and always want to \"hard reset to the\nupdated upstream\".  Doing so would be safe for them because they\ncreate nothing in their tree whose loss matters.\n\nGiving them a convenient and safe way to do so might be worth\nconsidering, but the behavior is already safely and explicitly\nachieved by running 'git fetch' followed by 'git reset --hard @{u}',\nso I am not sure whether it is worth adding another way to do so.\n\nMore importantly, throwing it into 'git pull' feels very wrong.\n\nThe core purpose of 'git pull' is history integration.  The command\nis designed to help those who make their own changes and advance\nhistory.  Adding a destructive option to the command makes it easier\nfor them to trigger it by accident, and unlike the main target of\nthis new feature, they have things in their tree that they cannot\nafford to lose to accidents or mistakes.\n\nSo, I am mildly against adding anything of this sort to 'git pull'.\nFor that matter, I am generally against making it convenient to\ndiscard or destroy history.  I prefer to keep these destructive\noperations explicit, e.g., \"fetch + reset --hard\".\n\nThanks.\n"},{"id":"550884","messageId":"0c2607e2-16da-4efd-879f-82ef2c2aa127@gmail.com","threadId":"66188","inReplyTo":"xmqqwltn1o4e.fsf@gitster.g","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-08-20T09:58:56Z","receivedAt":"2026-08-20T09:58:59Z","isPatch":true,"body":"On 18/08/2026 15:48, Junio C Hamano wrote:\n> \"Artur Bieniek via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Artur Bieniek <ar2rekb@gmail.com>\n>>\n>> Add --hard as an explicit alternative to merge and rebase. After\n>> fetching, require a single integration candidate and reset the current\n>> branch, index, and working tree to it.\n> \n> There may be a population of users who *never* make changes to their\n> history or working tree, and always want to \"hard reset to the\n> updated upstream\".  Doing so would be safe for them because they\n> create nothing in their tree whose loss matters.\n> \n> Giving them a convenient and safe way to do so might be worth\n> considering, but the behavior is already safely and explicitly\n> achieved by running 'git fetch' followed by 'git reset --hard @{u}',\n> so I am not sure whether it is worth adding another way to do so.\n\nI think if the design was slightly different so that it errored out by \ndefault if there were uncommitted changes then that would make it worth \nwhile as it is safer than \"git fetch; git reset --hard @{u}\" and would \nallow the user to carry over those changes with \"--autostash\". So to me \nsomething like\n\n\tgit pull --reset [--discard-changes | --autostash]\n\nwould be a more convincing design.\n\n> More importantly, throwing it into 'git pull' feels very wrong.\n> \n> The core purpose of 'git pull' is history integration.  The command\n> is designed to help those who make their own changes and advance\n> history.  Adding a destructive option to the command makes it easier\n> for them to trigger it by accident, and unlike the main target of\n> this new feature, they have things in their tree that they cannot\n> afford to lose to accidents or mistakes.\n\nIf it refused to reset by default when there were uncommitted changes \nwould that be safe enough? Uncommitted changes would be protected and \nany local commits that become unreachable after the reset can still be \nretrieved from the reflog. It's not quite the same as integrating remote \nand local changes, but more like updating the working copy.\n\nThanks\n\nPhillip\n\n> So, I am mildly against adding anything of this sort to 'git pull'.\n> For that matter, I am generally against making it convenient to\n> discard or destroy history.  I prefer to keep these destructive\n> operations explicit, e.g., \"fetch + reset --hard\".\n> \n> Thanks.\n> \n\n"},{"id":"550917","messageId":"xmqqo6ewsrzd.fsf@gitster.g","threadId":"66188","inReplyTo":"0c2607e2-16da-4efd-879f-82ef2c2aa127@gmail.com","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-20T15:59:18Z","receivedAt":"2026-08-20T15:59:22Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> I think if the design was slightly different so that it errored out by \n> default if there were uncommitted changes then that would make it worth \n> while as it is safer than \"git fetch; git reset --hard @{u}\" and would \n> allow the user to carry over those changes with \"--autostash\". So to me \n> something like\n>\n> \tgit pull --reset [--discard-changes | --autostash]\n>\n> would be a more convincing design.\n> ...\n> If it refused to reset by default when there were uncommitted changes \n> would that be safe enough? Uncommitted changes would be protected and \n> any local commits that become unreachable after the reset can still be \n> retrieved from the reflog. It's not quite the same as integrating remote \n> and local changes, but more like updating the working copy.\n\nYup, but git pull --ff-only serves the \"No development is done in\nthis repository; it is merely to keep the latest sources here\"\naudience just fine.\n\nWhat you are suggesting may be *useful* for those who agree with\nthis statement:\n\n    I do value my local changes because I haven't committed them,\n    but I am willing to discard these changes and replace them with\n    whatever the upstream did.\n\nbut I am not sure of the use case for a repository/working tree\nthat is managed in such a way.\n"},{"id":"550919","messageId":"2b9cc581-7c8e-4cb3-9524-2b466209ac7e@antmicro.com","threadId":"66188","inReplyTo":"xmqqo6ewsrzd.fsf@gitster.g","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Artur Bieniek","fromEmail":"abieniek@antmicro.com","sentAt":"2026-08-20T16:43:08Z","receivedAt":"2026-08-20T16:43:11Z","isPatch":true,"body":"One case where --ff-only does not seem to cover that audience is when \nthe upstream branch itself is rewritten.\n\nFor example, a checkout may contain no local development at all and only \nbe used to track the latest state of an upstream branch, but if that \nbranch is rebased or otherwise force-updated, git pull --ff-only will \nrefuse to update it because the histories have diverged.\n\nThat seems like a reasonably natural use case for the behavior Phillip \ndescribed: git pull --reset on a clean working tree would mean \"make \nthis checkout match the fetched upstream\", while still refusing by \ndefault to discard uncommitted changes.\n\nI also like that distinction better than my original --hard proposal, \nsince the destructive working-tree behavior would no longer be implicit \nin the primary option.\n\nThanks,\nArtur\n\nOn 8/20/26 5:59 PM, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> I think if the design was slightly different so that it errored out by\n>> default if there were uncommitted changes then that would make it worth\n>> while as it is safer than \"git fetch; git reset --hard @{u}\" and would\n>> allow the user to carry over those changes with \"--autostash\". So to me\n>> something like\n>>\n>> \tgit pull --reset [--discard-changes | --autostash]\n>>\n>> would be a more convincing design.\n>> ...\n>> If it refused to reset by default when there were uncommitted changes\n>> would that be safe enough? Uncommitted changes would be protected and\n>> any local commits that become unreachable after the reset can still be\n>> retrieved from the reflog. It's not quite the same as integrating remote\n>> and local changes, but more like updating the working copy.\n> \n> Yup, but git pull --ff-only serves the \"No development is done in\n> this repository; it is merely to keep the latest sources here\"\n> audience just fine.\n> \n> What you are suggesting may be *useful* for those who agree with\n> this statement:\n> \n>      I do value my local changes because I haven't committed them,\n>      but I am willing to discard these changes and replace them with\n>      whatever the upstream did.\n> \n> but I am not sure of the use case for a repository/working tree\n> that is managed in such a way.\n\n"},{"id":"550921","messageId":"fa62cf90-61ae-4352-b823-455ccffe403a@app.fastmail.com","threadId":"66188","inReplyTo":"2b9cc581-7c8e-4cb3-9524-2b466209ac7e@antmicro.com","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-08-20T17:29:08Z","receivedAt":"2026-08-20T17:29:34Z","isPatch":true,"body":"On Thu, Aug 20, 2026, at 18:43, Artur Bieniek wrote:\n> One case where --ff-only does not seem to cover that audience is when\n> the upstream branch itself is rewritten.\n>\n> For example, a checkout may contain no local development at all and only\n> be used to track the latest state of an upstream branch, but if that\n> branch is rebased or otherwise force-updated, git pull --ff-only will\n> refuse to update it because the histories have diverged.\n\nI don’t understand what you need a branch for in that case. I just use\nthe remote-tracking branch in that case (`origin/main` e.g.).\n\nPS: Bottom posting is strongly preferred on this list.\n\n> ...\n"},{"id":"550968","messageId":"xmqqa4qgth35.fsf@gitster.g","threadId":"66188","inReplyTo":"2b9cc581-7c8e-4cb3-9524-2b466209ac7e@antmicro.com","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-21T01:09:18Z","receivedAt":"2026-08-21T01:09:21Z","isPatch":true,"body":"Artur Bieniek <abieniek@antmicro.com> writes:\n\n> One case where --ff-only does not seem to cover that audience is when \n> the upstream branch itself is rewritten.\n\nIt does, doesn't it?  \n\nIt is not like you want to always blindly follow them.  Rewound\nupstream is w warning-worthy event that the user should be notified\nrather loudly, I would expect.\n\n"},{"id":"551126","messageId":"248fc3ca-7907-4720-ab71-1cf5926b82a0@antmicro.com","threadId":"66188","inReplyTo":"fa62cf90-61ae-4352-b823-455ccffe403a@app.fastmail.com","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Artur Bieniek","fromEmail":"abieniek@antmicro.com","sentAt":"2026-08-24T10:55:08Z","receivedAt":"2026-08-24T10:55:10Z","isPatch":true,"body":"On 8/20/26 7:29 PM, Kristoffer Haugsbakk wrote:\n> On Thu, Aug 20, 2026, at 18:43, Artur Bieniek wrote:\n>> One case where --ff-only does not seem to cover that audience is when\n>> the upstream branch itself is rewritten.\n>>\n>> For example, a checkout may contain no local development at all and only\n>> be used to track the latest state of an upstream branch, but if that\n>> branch is rebased or otherwise force-updated, git pull --ff-only will\n>> refuse to update it because the histories have diverged.\n> \n> I don’t understand what you need a branch for in that case. I just use\n> the remote-tracking branch in that case (`origin/main` e.g.).\n> \n> PS: Bottom posting is strongly preferred on this list.\n> \n>> ...\n\n\nThe branch itself is not essential; the point is to update the \nchecked-out working tree as well. git fetch updates origin/main, but \nleaves the checkout at the old commit.\n\nIf resetting does not belong under pull, would the inverse design be \nmore appropriate, e.g. git reset --pull --hard, meaning “fetch the \nconfigured upstream and reset to it”? Or would making reset perform \nnetwork I/O be undesirable for the same reason?\n\nThanks,\nArtur\n"},{"id":"551130","messageId":"xmqq1pbntxpe.fsf@gitster.g","threadId":"66188","inReplyTo":"248fc3ca-7907-4720-ab71-1cf5926b82a0@antmicro.com","subject":"Re: [PATCH] pull: add --hard mode","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-24T14:11:41Z","receivedAt":"2026-08-24T14:11:44Z","isPatch":true,"body":"Artur Bieniek <abieniek@antmicro.com> writes:\n\n> If resetting does not belong under pull, would the inverse design be \n> more appropriate, e.g. git reset --pull --hard, meaning “fetch the \n> configured upstream and reset to it”? Or would making reset perform \n> network I/O be undesirable for the same reason?\n\nVery true.  I wonder if the feature of git you should be looking at\nfor doing things like this is not \"pull\", \"fetch\", or \"reset\" but is\n\"alias\"?\n\n\n\n"}]}