{"thread":{"id":"66260","subject":"[PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","startedAt":"2026-09-03T01:05:51Z","lastAt":"2026-09-29T18:05:18Z","messageCount":27,"participants":["Aleksei Sviridkin","Junio C Hamano","Kristoffer Haugsbakk","Thomas Bachem","Weijie Yuan","Tyler Cipriani"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"551816","messageId":"20260903010547.85469-1-f@lex.la","threadId":"66260","inReplyTo":null,"subject":"[PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-03T01:05:47Z","receivedAt":"2026-09-03T01:05:51Z","isPatch":true,"body":"Since 99a1f9ae10 (push: add reflog check for \"--force-if-includes\",\n2020-10-03), is_reachable_in_reflog() stops walking the reflog of the\nlocal branch at entries older than the newest reflog entry of the\nremote-tracking ref. That timestamp is read by a callback of\nrefs_for_each_reflog_ent_reverse() into a variable that is never\ninitialized, so when the remote-tracking ref has no reflog the walk\nis cut off at whatever happens to be on the stack.\n\nWith the files backend a remote-tracking ref created by \"git clone\"\nhas no reflog and does not get one until it moves. On my machine the\nleftover value exceeds any real timestamp: the walk stops at the very\nfirst entry, never reaches the \"Created from\" entry that \"checkout\n--track\" wrote, and the push is rejected with \"remote ref updated\nsince checkout\" although nothing on the remote has changed.\n\nInitialize the timestamp to zero, so that a remote-tracking ref\nwithout reflog makes the walk cover the whole reflog of the local\nbranch, as documented.\n\nSigned-off-by: Aleksei Sviridkin <f@lex.la>\nAssisted-by: LLM\n---\nThe new test fails without the fix on my machine (macOS, arm64). As\nthe value read is uninitialized, other platforms may pass it by luck.\n\n remote.c            |  2 +-\n t/t5533-push-cas.sh | 18 ++++++++++++++++++\n 2 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex 00723b3..6d30169 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2751,7 +2751,7 @@ static int check_and_collect_until(const char *refname UNUSED,\n  */\n static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n {\n-\ttimestamp_t date;\n+\ttimestamp_t date = 0;\n \tstruct commit *commit;\n \tstruct commit **chunk;\n \tstruct check_and_collect_until_cb_data cb;\ndiff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh\nindex cba26a8..77f46f3 100755\n--- a/t/t5533-push-cas.sh\n+++ b/t/t5533-push-cas.sh\n@@ -396,4 +396,22 @@ test_expect_success '\"--force-if-includes\" should allow deletes' '\n \t)\n '\n \n+test_expect_success '\"--force-if-includes\" should allow forced update when remote-tracking ref has no reflog' '\n+\trm -fr dst src &&\n+\tgit init --bare dst &&\n+\tgit push dst main main:branch &&\n+\tgit clone --no-local dst src &&\n+\ttest_when_finished \"rm -fr dst src\" &&\n+\t(\n+\t\tcd src &&\n+\t\t# a clone leaves the remote-tracking refs without reflog\n+\t\t# entries with the files backend, but not with reftable\n+\t\tgit reflog expire --all --expire=all &&\n+\t\tgit switch -c branch --track origin/branch &&\n+\t\tgit reset --hard HEAD^ &&\n+\t\ttest_commit D &&\n+\t\tgit push --force-if-includes --force-with-lease=\"branch\"\n+\t)\n+'\n+\n test_done\n\nbase-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc\n-- \n2.55.0\n\n"},{"id":"551883","messageId":"xmqq5x0mfgyh.fsf@gitster.g","threadId":"66260","inReplyTo":"20260903010547.85469-1-f@lex.la","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-03T16:16:38Z","receivedAt":"2026-09-03T16:16:42Z","isPatch":true,"body":"Aleksei Sviridkin <f@lex.la> writes:\n\n> Since 99a1f9ae10 (push: add reflog check for \"--force-if-includes\",\n> 2020-10-03), is_reachable_in_reflog() stops walking the reflog of the\n> local branch at entries older than the newest reflog entry of the\n> remote-tracking ref. That timestamp is read by a callback of\n> refs_for_each_reflog_ent_reverse() into a variable that is never\n> initialized, so when the remote-tracking ref has no reflog the walk\n> is cut off at whatever happens to be on the stack.\n>\n> With the files backend a remote-tracking ref created by \"git clone\"\n> has no reflog and does not get one until it moves. On my machine the\n> leftover value exceeds any real timestamp: the walk stops at the very\n> first entry, never reaches the \"Created from\" entry that \"checkout\n> --track\" wrote, and the push is rejected with \"remote ref updated\n> since checkout\" although nothing on the remote has changed.\n>\n> Initialize the timestamp to zero, so that a remote-tracking ref\n> without reflog makes the walk cover the whole reflog of the local\n> branch, as documented.\n>\n> Signed-off-by: Aleksei Sviridkin <f@lex.la>\n> Assisted-by: LLM\n\nThe last line adds no useful information, though.  Besides, you are\nfully responsible for whatever LLM emitted and contributed into this\npatch, so your sign-off must be the last line in the trailers.\n\n> ---\n> The new test fails without the fix on my machine (macOS, arm64). As\n> the value read is uninitialized, other platforms may pass it by luck.\n\nThe code change looks good.\n\nIt is a bit surprising to see the fallout from a change 6 years ago\nto be addressed now, and makes me wonder what else changed recently.\nCertainly year 2026 is not the first year in which macOS on arm64\nstarted becoming widely used, or you are not the only user of Git on\nthat platform.\n\n>  remote.c            |  2 +-\n>  t/t5533-push-cas.sh | 18 ++++++++++++++++++\n>  2 files changed, 19 insertions(+), 1 deletion(-)\n>\n> diff --git a/remote.c b/remote.c\n> index 00723b3..6d30169 100644\n> --- a/remote.c\n> +++ b/remote.c\n> @@ -2751,7 +2751,7 @@ static int check_and_collect_until(const char *refname UNUSED,\n>   */\n>  static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n>  {\n> -\ttimestamp_t date;\n> +\ttimestamp_t date = 0;\n>  \tstruct commit *commit;\n>  \tstruct commit **chunk;\n>  \tstruct check_and_collect_until_cb_data cb;\n> diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh\n> index cba26a8..77f46f3 100755\n> --- a/t/t5533-push-cas.sh\n> +++ b/t/t5533-push-cas.sh\n> @@ -396,4 +396,22 @@ test_expect_success '\"--force-if-includes\" should allow deletes' '\n>  \t)\n>  '\n>  \n> +test_expect_success '\"--force-if-includes\" should allow forced update when remote-tracking ref has no reflog' '\n> +\trm -fr dst src &&\n> +\tgit init --bare dst &&\n> +\tgit push dst main main:branch &&\n> +\tgit clone --no-local dst src &&\n> +\ttest_when_finished \"rm -fr dst src\" &&\n\nYou'd want to move \"test_when_finished\" immediately before \"git init\n--bare dst\", no?  That way, you can clean things up after any or the\n\"init\", \"push\", \"clone\" fails (as well as the main part of the test\nthat is done in the subdirectory).\n\n> +\t(\n> +\t\tcd src &&\n> +\t\t# a clone leaves the remote-tracking refs without reflog\n> +\t\t# entries with the files backend, but not with reftable\n> +\t\tgit reflog expire --all --expire=all &&\n> +\t\tgit switch -c branch --track origin/branch &&\n> +\t\tgit reset --hard HEAD^ &&\n> +\t\ttest_commit D &&\n> +\t\tgit push --force-if-includes --force-with-lease=\"branch\"\n> +\t)\n> +'\n> +\n>  test_done\n>\n> base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc\n"},{"id":"551907","messageId":"20260903200015.36849-1-f@lex.la","threadId":"66260","inReplyTo":"xmqq5x0mfgyh.fsf@gitster.g","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-03T20:00:15Z","receivedAt":"2026-09-03T20:00:21Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n> your sign-off must be the last line in the trailers.\n\nI took Assisted-by from the kernel, which asks for it. I could not find\nanything either way in git's guidelines, so I followed the kernel. I will\nput the sign-off last in v2, and drop Assisted-by if you would rather not\nhave it.\n\n> It is a bit surprising to see the fallout from a change 6 years ago\n> to be addressed now, and makes me wonder what else changed recently.\n\nThe date is read from uninitialized stack, so it only bites when the\nleftover happens to be larger than a real timestamp. That is build- and\nlayout-dependent, not really macOS/arm64 specific. I hit a layout where\nit triggers every time, so the test is reliable here, but on another\nbuild it can pass by luck, as you saw.\n"},{"id":"551909","messageId":"xmqqo6ee9jtx.fsf@gitster.g","threadId":"66260","inReplyTo":"20260903200015.36849-1-f@lex.la","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-03T20:11:06Z","receivedAt":"2026-09-03T20:11:12Z","isPatch":true,"body":"Aleksei Sviridkin <f@lex.la> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>> your sign-off must be the last line in the trailers.\n>\n> I took Assisted-by from the kernel, which asks for it. I could not find\n> anything either way in git's guidelines, so I followed the kernel. I will\n> put the sign-off last in v2, and drop Assisted-by if you would rather not\n> have it.\n\nI didn't mean to say that Assisted-by was useless.  What I meant was\nthat \"LLM\" on that trailer has no information contennts.  Which LLM?\n\nWe have the following in Documentation/SubmittingPatches by the way.\n\n[[ai]]\n=== Use of Artificial Intelligence (AI)\n\nThe Developer's Certificate of Origin requires contributors to certify\nthat they know the origin of their contributions to the project and\nthat they have the right to submit it under the project's license.\nIt's not yet clear that this can be legally satisfied when submitting\nsignificant amount of content that has been generated by AI tools.\n\nAnother issue with AI generated content is that AIs still often\nhallucinate or just produce bad code, commit messages, documentation\nor output, even when you point out their mistakes.\n\nTo avoid these issues, we will reject anything that looks AI\ngenerated, that sounds overly formal or bloated, that looks like AI\nslop, that looks good on the surface but makes no sense, or that\nsenders don’t understand or cannot explain.\n\nWe strongly recommend using AI tools carefully and responsibly.\n\nContributors would often benefit more from AI by using it to guide and\nhelp them step by step towards producing a solution by themselves\nrather than by asking for a full solution that they would then mostly\ncopy-paste. They can also use AI to help with debugging, or with\nchecking for obvious mistakes, things that can be improved, things\nthat don’t match our style, guidelines or our feedback, before sending\nit to us.\n\n\n"},{"id":"551917","messageId":"20260903214551.53918-1-f@lex.la","threadId":"66260","inReplyTo":"xmqqo6ee9jtx.fsf@gitster.g","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-03T21:45:51Z","receivedAt":"2026-09-03T21:45:55Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n> I didn't mean to say that Assisted-by was useless.  What I meant was\n> that \"LLM\" on that trailer has no information contennts.  Which LLM?\n\nOn \"which LLM\": I took the trailer from the kernel, and they dropped the\nmodel name on purpose. Christian Brauner's 816d9992d9ed\n(coding-assistants: simplify attribution) turned\nAssisted-by: AGENT_NAME:MODEL_VERSION into a bare Assisted-by: LLM,\nbecause naming the model \"provides free advertising to proprietary\nsoftware companies while adding little or no useful information\". It\nfits my case anyway: this went through a router mixing models from three\nvendors, so there is no single model to name, and git's guidelines don't\nask for one either. In case you are curious: k3, sol-5.6, fable 5.1,\nopus 5 and sonnet 5. If you would still rather I drop the trailer, I\nwill.\n\nI read the AI section. These are small fixes, I went through the whole\nchange myself and can explain any part of it. I'll keep what I send\nlean.\n\nv2 with the sign-off last and the test_when_finished fix goes out once\n24 hours have passed since v1.\n"},{"id":"551921","messageId":"642a1c7c-e159-41db-a3ae-19ea8ed300b7@app.fastmail.com","threadId":"66260","inReplyTo":"20260903200015.36849-1-f@lex.la","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-09-04T01:03:03Z","receivedAt":"2026-09-04T01:03:28Z","isPatch":true,"body":"On Thu, Sep 3, 2026, at 22:00, Aleksei Sviridkin wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>> your sign-off must be the last line in the trailers.\n>\n> I took Assisted-by from the kernel, which asks for it. I could not find\n> anything either way in git's guidelines, so I followed the kernel. \n\nIf there is no mention of it in the guidelines\nthe fallback guideline becomes the one for\nan operating system kernel? ’,:|\n\nMaybe you saw the few ones that have landed\nin the last months in this project. But those don't\nfollow the recent Linux workflow change to just\n“LLM”.\n\n> I will\n> put the sign-off last in v2, and drop Assisted-by if you would rather not\n> have it.\n"},{"id":"551954","messageId":"20260904124433.12840-1-f@lex.la","threadId":"66260","inReplyTo":"20260903010547.85469-1-f@lex.la","subject":"[PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-04T12:44:33Z","receivedAt":"2026-09-04T12:44:37Z","isPatch":true,"body":"Since 99a1f9ae10 (push: add reflog check for \"--force-if-includes\",\n2020-10-03), is_reachable_in_reflog() stops walking the reflog of the\nlocal branch at entries older than the newest reflog entry of the\nremote-tracking ref. That timestamp is read by a callback of\nrefs_for_each_reflog_ent_reverse() into a variable that is never\ninitialized, so when the remote-tracking ref has no reflog the walk\nis cut off at whatever happens to be on the stack.\n\nWith the files backend a remote-tracking ref created by \"git clone\"\nhas no reflog and does not get one until it moves. On my machine the\nleftover value exceeds any real timestamp: the walk stops at the very\nfirst entry, never reaches the \"Created from\" entry that \"checkout\n--track\" wrote, and the push is rejected with \"remote ref updated\nsince checkout\" although nothing on the remote has changed.\n\nInitialize the timestamp to zero, so that a remote-tracking ref\nwithout reflog makes the walk cover the whole reflog of the local\nbranch, as documented.\n\nAssisted-by: LLM\nSigned-off-by: Aleksei Sviridkin <f@lex.la>\n---\nChanges since v1:\n  - sign-off is now the last trailer\n  - test_when_finished moved ahead of the setup so a failed init, push\n    or clone still cleans up\n\n remote.c            |  2 +-\n t/t5533-push-cas.sh | 18 ++++++++++++++++++\n 2 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..6d301698ca 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2751,7 +2751,7 @@ static int check_and_collect_until(const char *refname UNUSED,\n  */\n static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n {\n-\ttimestamp_t date;\n+\ttimestamp_t date = 0;\n \tstruct commit *commit;\n \tstruct commit **chunk;\n \tstruct check_and_collect_until_cb_data cb;\ndiff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh\nindex cba26a872d..bb8878c593 100755\n--- a/t/t5533-push-cas.sh\n+++ b/t/t5533-push-cas.sh\n@@ -396,4 +396,22 @@ test_expect_success '\"--force-if-includes\" should allow deletes' '\n \t)\n '\n \n+test_expect_success '\"--force-if-includes\" should allow forced update when remote-tracking ref has no reflog' '\n+\trm -fr dst src &&\n+\ttest_when_finished \"rm -fr dst src\" &&\n+\tgit init --bare dst &&\n+\tgit push dst main main:branch &&\n+\tgit clone --no-local dst src &&\n+\t(\n+\t\tcd src &&\n+\t\t# a clone leaves the remote-tracking refs without reflog\n+\t\t# entries with the files backend, but not with reftable\n+\t\tgit reflog expire --all --expire=all &&\n+\t\tgit switch -c branch --track origin/branch &&\n+\t\tgit reset --hard HEAD^ &&\n+\t\ttest_commit D &&\n+\t\tgit push --force-if-includes --force-with-lease=\"branch\"\n+\t)\n+'\n+\n test_done\n\nbase-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc\n-- \n2.55.0\n\n"},{"id":"551973","messageId":"xmqqzexx58hc.fsf@gitster.g","threadId":"66260","inReplyTo":"20260904124433.12840-1-f@lex.la","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-04T15:42:07Z","receivedAt":"2026-09-04T15:42:11Z","isPatch":true,"body":"Aleksei Sviridkin <f@lex.la> writes:\n\n>  static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n>  {\n> -\ttimestamp_t date;\n> +\ttimestamp_t date = 0;\n>  \tstruct commit *commit;\n>  \tstruct commit **chunk;\n>  \tstruct check_and_collect_until_cb_data cb;\n\nThis gives a known value to the \"date\" variable, solving the issue\nof using an uninitialized variable.  But how do we know if \"0\" a\nreasonable fall-back value?  Why is it better than \"now\" or perhaps\n\"2 weeks ago\"?\n\nWe pretend that the latest entry of the remote-tracking ref was from\nyear 1970.  And then that timestamp is used as a cut-off time for\ncheck_and_collect_until().  What's the ramification of that?\n\n> Since 99a1f9ae10 (push: add reflog check for \"--force-if-includes\",\n> 2020-10-03), is_reachable_in_reflog() stops walking the reflog of the\n> local branch at entries older than the newest reflog entry of the\n> remote-tracking ref. That timestamp is read by a callback of\n> refs_for_each_reflog_ent_reverse() into a variable that is never\n> initialized, so when the remote-tracking ref has no reflog the walk\n> is cut off at whatever happens to be on the stack.\n\nThis is almost good as-is.  I'd end the above with \"... has no reflog,\nthe variable that holds the timestamp stays uninitialized\".\n\n> With the files backend a remote-tracking ref created by \"git clone\"\n> has no reflog and does not get one until it moves. On my machine the\n> leftover value exceeds any real timestamp: the walk stops at the very\n> first entry, never reaches the \"Created from\" entry that \"checkout\n> --track\" wrote, and the push is rejected with \"remote ref updated\n> since checkout\" although nothing on the remote has changed.\n\nThat describes what happens (eh, rather, what does not happen) when\nthat uninitialized timestamp is more recent than the current time.\n\nIt does not explain why it is sensible to set it to year 1970, which\nwould force everything to be inspected.\n"},{"id":"551987","messageId":"xmqqpkyt3qul.fsf@gitster.g","threadId":"66260","inReplyTo":"20260903200015.36849-1-f@lex.la","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-04T16:48:18Z","receivedAt":"2026-09-04T16:48:21Z","isPatch":true,"body":"Aleksei Sviridkin <f@lex.la> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>> your sign-off must be the last line in the trailers.\n>\n> I took Assisted-by from the kernel, which asks for it. I could not find\n> anything either way in git's guidelines, so I followed the kernel. I will\n> put the sign-off last in v2, and drop Assisted-by if you would rather not\n> have it.\n\nWe are not the kernel ;-)\n\nQuite honestly, I would rather not have patches filled with AI slop\nthat is often walls of text filled with \"eh, that may not be wrong\nper-se, but is it relevant?\" descriptions and we can never tell what\nwas used as the original material to copy from.  I prefer patches\nwith human-readable explanations and known origin.\n\nThanks.\n"},{"id":"552039","messageId":"20260905171330.34646-1-f@lex.la","threadId":"66260","inReplyTo":"20260903010547.85469-1-f@lex.la","subject":"[PATCH v3] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-05T17:13:30Z","receivedAt":"2026-09-05T17:13:34Z","isPatch":true,"body":"Since 99a1f9ae10 (push: add reflog check for \"--force-if-includes\",\n2020-10-03), is_reachable_in_reflog() stops walking the reflog of the\nlocal branch at entries older than the newest reflog entry of the\nremote-tracking ref. That timestamp is read by a callback of\nrefs_for_each_reflog_ent_reverse(), so when the remote-tracking ref\nhas no reflog, the variable that holds the timestamp stays\nuninitialized.\n\nWith the files backend a remote-tracking ref created by \"git clone\"\nhas no reflog and does not get one until it moves. On my machine the\nleftover value exceeds any real timestamp: the walk stops at the very\nfirst entry, never reaches the \"Created from\" entry that \"checkout\n--track\" wrote, and the push is rejected with \"remote ref updated\nsince checkout\" although nothing on the remote has changed.\n\nThe cut-off is an optimization that rests on an assumption: an entry\nolder than the moment the remote-tracking ref last moved is not\nexpected to be the one being looked for. Without a reflog there is\nno such moment, hence no cut-off to apply. Initialize the timestamp\nto zero to say exactly that: timestamp_t is unsigned, so no entry\ncompares older than zero and the comparison never fires. Using\n\"now\", or any fixed age, would instead cut the walk off at the first\nentry older than that bound, which is how the failure happens in\nthe first place. The price is paid only when no matching entry is\nfound: the walk then reaches the oldest entry and falls back to the\nmerge-base check over what it collected, where the cut-off would\nhave stopped it earlier.\n\nSigned-off-by: Aleksei Sviridkin <f@lex.la>\n---\nChanges since v2:\n  - reworded the first paragraph as you suggested\n  - explain why zero is the fallback rather than \"now\" or a fixed age\n  - dropped the Assisted-by trailer\n\n remote.c            |  2 +-\n t/t5533-push-cas.sh | 18 ++++++++++++++++++\n 2 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..6d301698ca 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2751,7 +2751,7 @@ static int check_and_collect_until(const char *refname UNUSED,\n  */\n static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n {\n-\ttimestamp_t date;\n+\ttimestamp_t date = 0;\n \tstruct commit *commit;\n \tstruct commit **chunk;\n \tstruct check_and_collect_until_cb_data cb;\ndiff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh\nindex cba26a872d..bb8878c593 100755\n--- a/t/t5533-push-cas.sh\n+++ b/t/t5533-push-cas.sh\n@@ -396,4 +396,22 @@ test_expect_success '\"--force-if-includes\" should allow deletes' '\n \t)\n '\n \n+test_expect_success '\"--force-if-includes\" should allow forced update when remote-tracking ref has no reflog' '\n+\trm -fr dst src &&\n+\ttest_when_finished \"rm -fr dst src\" &&\n+\tgit init --bare dst &&\n+\tgit push dst main main:branch &&\n+\tgit clone --no-local dst src &&\n+\t(\n+\t\tcd src &&\n+\t\t# a clone leaves the remote-tracking refs without reflog\n+\t\t# entries with the files backend, but not with reftable\n+\t\tgit reflog expire --all --expire=all &&\n+\t\tgit switch -c branch --track origin/branch &&\n+\t\tgit reset --hard HEAD^ &&\n+\t\ttest_commit D &&\n+\t\tgit push --force-if-includes --force-with-lease=\"branch\"\n+\t)\n+'\n+\n test_done\n\nbase-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc\n-- \n2.55.0\n\n"},{"id":"552043","messageId":"20260905171343.34722-1-f@lex.la","threadId":"66260","inReplyTo":"xmqqpkyt3qul.fsf@gitster.g","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-05T17:13:43Z","receivedAt":"2026-09-05T17:13:47Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n> I prefer patches with human-readable explanations and known origin.\n\nDropped the trailer. You will not see it again.\n\nOn origin: this comes out of git's own tree, not an outside corpus, and\nthat is why every claim in the message names a file or a commit you can\ncheck. The change here is one line, timestamp_t date; becoming\ntimestamp_t date = 0;. I read the whole thing and can explain any line\nof it, and the sign-off is what carries that.\n\nv3 uses your wording for the first paragraph and explains why zero is\nthe right fallback rather than \"now\" or a fixed age.\n"},{"id":"552056","messageId":"xmqq33vn5hsq.fsf@gitster.g","threadId":"66260","inReplyTo":"xmqqzexx58hc.fsf@gitster.g","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-06T00:45:25Z","receivedAt":"2026-09-06T00:45:27Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Aleksei Sviridkin <f@lex.la> writes:\n>\n>>  static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n>>  {\n>> -\ttimestamp_t date;\n>> +\ttimestamp_t date = 0;\n>>  \tstruct commit *commit;\n>>  \tstruct commit **chunk;\n>>  \tstruct check_and_collect_until_cb_data cb;\n>\n> This gives a known value to the \"date\" variable, solving the issue\n> of using an uninitialized variable.  But how do we know if \"0\" a\n> reasonable fall-back value?  Why is it better than \"now\" or perhaps\n> \"2 weeks ago\"?\n\nThinking about it a bit more, let's imagine that we had reflog\nenabled and did not have to suffer from this \"uninitialized\nvariable\" problem.  Even if the reflog for the remote-tracking\nbranch were enabled long ago and had plenty of entries, it wouldn't\nhave any entry older than 90 days, or the value gc.reflogExpire is\nset.  Which suggests to me that gc.reflogExpire or 90 days ago would\nbe a lot more reasonable than year 1970 to use as a fallback cutoff\ndate.\n\nThanks.\n\n"},{"id":"552067","messageId":"fdf8fa9c-1e6a-4f7c-bbe3-a0b41cdaabd4@app.fastmail.com","threadId":"66260","inReplyTo":"20260905171343.34722-1-f@lex.la","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-09-06T09:39:29Z","receivedAt":"2026-09-06T09:41:33Z","isPatch":true,"body":"On Sat, Sep 5, 2026, at 19:13, Aleksei Sviridkin wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>> I prefer patches with human-readable explanations and known origin.\n\nThe following are just drive by comments on this point.\n\nOn Sat, Sep 5, 2026, at 19:13, Aleksei Sviridkin wrote:\n> Dropped the trailer. You will not see it again.\n>\n> On origin: this comes out of git's own tree, not an outside corpus, and\n\nCool that it is only trained on Git’s corpus.\n\n> that is why every claim in the message names a file or a commit you can\n> check. The change here is one line, timestamp_t date; becoming\n> timestamp_t date = 0;. I read the whole thing and can explain any line\n> of it, [...]\n\nIf one can explain any and all of it, then having an AI write it is not\nnecessary. Referring to the “human-readable explanation” point.\n\n> and the sign-off is what carries that.\n\nWeird LLM-like phrases like this one seems to be used a lot on these\n`Assisted-by` submissions (by you and Thomas Bachem recently).\nInanimate nouns gain subject-agency in sentences instead of saying\nsomething plainly like, I have signed off on this and I mean it. And\nthis is on patch discussions, not in the commit messages (which are\nalready marked as being potentially AI written).\n\nI feel like I should take two courses in linguistics in order to\narticulate this uncanny valley feeling.\n\nBy commit message volume, I would have expected the commit messages (if\nthey are LLM-assisted) to read more like Jeff King log messages given\nthe corpus training.\n\n>[snip]\n"},{"id":"552077","messageId":"20260906165052.21780-1-f@lex.la","threadId":"66260","inReplyTo":"xmqq33vn5hsq.fsf@gitster.g","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-06T16:50:52Z","receivedAt":"2026-09-06T16:50:58Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n> Which suggests to me that gc.reflogExpire or 90 days ago would be a\n> lot more reasonable than year 1970 to use as a fallback cutoff date.\n\nEntries older than 90 days do survive. The reflog expires when gc or\n\"git reflog expire\" runs, not on its own, so I could build a branch\nwhose matching reflog entry is 200 days old and still sitting there.\n\nA/B on one scenario with only the fallback differing: with now minus 90\ndays the push is rejected, with zero it goes through as a forced update.\nThe branch was created at the remote tip 200 days ago, that entry being\nthe matching one, rewound below the tip 150 days ago, one recent commit\non top, so the tip is not an ancestor of anything newer.\n\nIt takes two crossings of the bound to bite, which is why my first two\nattempts to reproduce it failed. The match is tested before the\ncut-off, so the entry sitting at the bound is still inspected, and the\nmerge-base fallback still covers the case where the tip is reachable\nfrom something collected. You need a non-matching entry past the bound\nand the tip unreachable from what was collected.\n\nv3 went out a few hours before your mail; its third paragraph argues\nzero over \"now\" or a fixed age. Your call.\n"},{"id":"552081","messageId":"xmqq8q5e480p.fsf@gitster.g","threadId":"66260","inReplyTo":"fdf8fa9c-1e6a-4f7c-bbe3-a0b41cdaabd4@app.fastmail.com","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-06T17:14:14Z","receivedAt":"2026-09-06T17:14:17Z","isPatch":true,"body":"\"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n\n> By commit message volume, I would have expected the commit messages (if\n> they are LLM-assisted) to read more like Jeff King log messages given\n> the corpus training.\n\n;-)\n"},{"id":"552087","messageId":"CAA0xjtou7HwaKS8arPsBavkOREBHJ5ARN52KWukDntTJG6DUyw@mail.gmail.com","threadId":"66260","inReplyTo":"xmqq8q5e480p.fsf@gitster.g","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Thomas Bachem","fromEmail":"mail@thomasbachem.com","sentAt":"2026-09-07T04:54:28Z","receivedAt":"2026-09-07T04:54:39Z","isPatch":true,"body":"On 06/09/2026 19:14, Junio C Hamano wrote:\n> \"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n>\n>> By commit message volume, I would have expected the commit messages (if\n>> they are LLM-assisted) to read more like Jeff King log messages given\n>> the corpus training.\n>\n> ;-)\nI can ask for that from the next reroll on, if it helps ;-)\n"},{"id":"552093","messageId":"ap5YTGRduklkI1Li@wyuan.org","threadId":"66260","inReplyTo":"CAA0xjtou7HwaKS8arPsBavkOREBHJ5ARN52KWukDntTJG6DUyw@mail.gmail.com","subject":"Re: [PATCH] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Weijie Yuan","fromEmail":"wy@wyuan.org","sentAt":"2026-09-07T06:23:08Z","receivedAt":"2026-09-07T06:23:25Z","isPatch":true,"body":"On Mon, Sep 07, 2026 at 06:54:28AM +0200, Thomas Bachem wrote:\n> On 06/09/2026 19:14, Junio C Hamano wrote:\n> > \"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n> >\n> >> By commit message volume, I would have expected the commit messages (if\n> >> they are LLM-assisted) to read more like Jeff King log messages given\n> >> the corpus training.\n> >\n> > ;-)\n> I can ask for that from the next reroll on, if it helps ;-)\n\nI've already had an LLM do this for a while in my toy projects,\nfeeding it a few representative patch series from Peff.\n\nThanks, Peff! ;-)\n"},{"id":"552179","messageId":"xmqqjyowz9oq.fsf@gitster.g","threadId":"66260","inReplyTo":"20260906165052.21780-1-f@lex.la","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-08T03:47:01Z","receivedAt":"2026-09-08T03:47:05Z","isPatch":true,"body":"Aleksei Sviridkin <f@lex.la> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>> Which suggests to me that gc.reflogExpire or 90 days ago would be a\n>> lot more reasonable than year 1970 to use as a fallback cutoff date.\n>\n> Entries older than 90 days do survive. The reflog expires when gc or\n> \"git reflog expire\" runs, not on its own, so I could build a branch\n> whose matching reflog entry is 200 days old and still sitting there.\n\nIt is only true for those who conciously disable the gc, isn't it?\n\nIt all depends on how hard it is to recover from such a failure, and\nit may not even matter in practice what value we set, as it will\nbecome a non-issue once they pull from there or push into there even\nonce.\n\nBut in the context of discussing what the fallback default ought to\nbe, I somehow sounds more like a poor excuse rather than a sensible\nargument.  Doesn't it force a behaviour that would happen only to\nthose people who deliberately choose to ignore cutoff and who are\nwilling to spend cycles to go back to the beginning of history, to\nall users, including those who do not make such customization, no?\n\n"},{"id":"552295","messageId":"20260909065639.47316-1-f@lex.la","threadId":"66260","inReplyTo":"xmqqjyowz9oq.fsf@gitster.g","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-09T06:56:39Z","receivedAt":"2026-09-09T06:56:46Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n> It is only true for those who conciously disable the gc, isn't it?\n\nNo. The fallback is not about expiry, it is reached when the\nremote-tracking ref has no reflog, and a plain clone leaves it that\nway: after \"git clone --no-local\" on the files backend, \"git reflog\nexists refs/remotes/origin/main\" returns 1, with core.logAllRefUpdates\nat its default and gc untouched. The same source cloned with\n--ref-format=reftable gets one entry.\n\nNothing expires on a calendar either. Entries go when \"git reflog\nexpire\" runs, and \"git gc --auto\" decides by loose object count\n(gc.auto, 6700), so a quiet repository expires nothing.\n\n> Doesn't it force a behaviour that would happen only to those people\n> who deliberately choose to ignore cutoff and who are willing to spend\n> cycles to go back to the beginning of history, to all users,\n> including those who do not make such customization, no?\n\nMeasured that. One repository, 20000 entries in the local branch's\nreflog, remote-tracking ref without a reflog, both values reject the\npush so only the work differs. Median of 7 runs:\n\n  entries spread over 200 days      zero 0.325s   90 days 0.069s\n  after \"git reflog expire --all\"   zero 0.070s   90 days 0.069s\n  20000 entries inside 90 days      zero 0.319s   90 days 0.321s\n\nThe expire run left 5 of the 20000, since all but five are 100 to 200\ndays old here. So the cost lands only on a repository that still holds\nentries older than 90 days, which is the one where gc has not run.\n\nIn that repository, when the matching entry is one of the old ones, the\nsame walk is what decides: zero accepts in 0.322s, 90 days rejects in\n0.086s.\n\nWith no match it is 0.26s of extra work for the same answer.\n"},{"id":"552388","messageId":"xmqqv78dordu.fsf@gitster.g","threadId":"66260","inReplyTo":"20260909065639.47316-1-f@lex.la","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-10T00:57:01Z","receivedAt":"2026-09-10T00:57:03Z","isPatch":true,"body":"Aleksei Sviridkin <f@lex.la> writes:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>> It is only true for those who conciously disable the gc, isn't it?\n>\n> No. The fallback is not about expiry, it is reached when the\n> remote-tracking ref has no reflog, and a plain clone leaves it that\n> way: after \"git clone --no-local\" on the files backend, \"git reflog\n> exists refs/remotes/origin/main\" returns 1, with core.logAllRefUpdates\n> at its default and gc untouched. The same source cloned with\n> --ref-format=reftable gets one entry.\n>\n> Nothing expires on a calendar either. Entries go when \"git reflog\n> expire\" runs, and \"git gc --auto\" decides by loose object count\n> (gc.auto, 6700), so a quiet repository expires nothing.\n\nSorry but I am confused.  Your sample below is with 20000 local\nreflog worth of activities, which is hardly a \"quiet repository\".\nBesides, we are talking about \"push\" so optimizing for a quiet\nrepository does not sound like a useful mentail exercise to do.\n\n>> Doesn't it force a behaviour that would happen only to those people\n>> who deliberately choose to ignore cutoff and who are willing to spend\n>> cycles to go back to the beginning of history, to all users,\n>> including those who do not make such customization, no?\n>\n> Measured that. One repository, 20000 entries in the local branch's\n> reflog, remote-tracking ref without a reflog, both values reject the\n> push so only the work differs. Median of 7 runs:\n>\n>   entries spread over 200 days      zero 0.325s   90 days 0.069s\n>   after \"git reflog expire --all\"   zero 0.070s   90 days 0.069s\n>   20000 entries inside 90 days      zero 0.319s   90 days 0.321s\n>\n> The expire run left 5 of the 20000, since all but five are 100 to 200\n> days old here. So the cost lands only on a repository that still holds\n> entries older than 90 days, which is the one where gc has not run.\n>\n> In that repository, when the matching entry is one of the old ones, the\n> same walk is what decides: zero accepts in 0.322s, 90 days rejects in\n> 0.086s.\n>\n> With no match it is 0.26s of extra work for the same answer.\n\nThanks.\n\nDoesn't that mean it is more logical to use the default gc\nexpiration timeout than year 1970 and in any cases using the usual\ngc expiration would not waste more time than using 1970, right?\n"},{"id":"552420","messageId":"20260910083106.88960-1-f@lex.la","threadId":"66260","inReplyTo":"xmqqv78dordu.fsf@gitster.g","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-10T08:31:06Z","receivedAt":"2026-09-10T08:31:11Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n> Sorry but I am confused.  Your sample below is with 20000 local\n> reflog worth of activities, which is hardly a \"quiet repository\".\n\nTwo things got joined there. The 20000 entries are the worst case\nfor measuring the walk's cost. The repositories that keep entries\nolder than 90 days are ordinary ones where \"git gc --auto\" never\ncrossed 6700 loose objects, and that needs no configuration.\n\n> Doesn't that mean it is more logical to use the default gc\n> expiration timeout than year 1970 and in any cases using the usual\n> gc expiration would not waste more time than using 1970, right?\n\nOn time, yes. The cutoff never takes longer than zero. But it saves\ntime only by ending the search early, and ending the search early is\nwhat rejects a valid push. Same repository, matching entry 200 days\nold: the cutoff rejects in 0.086s, zero accepts in 0.322s. Where the\ncutoff cannot change the verdict, both take the same time: 0.070s vs\n0.069s after expiry, 0.319s vs 0.321s with everything inside 90 days.\n\nThe cutoff is faster than zero only where it gives the wrong answer.\nIf that trade is acceptable, gc.reflogExpire is a one-line change,\nand the commit message should then say the fallback can still reject\na correct push when the matching entry is older than the cutoff.\nYour call.\n"},{"id":"553325","messageId":"arbfQ7xF1NgDeilU@localhost.localdomain","threadId":"66260","inReplyTo":"xmqqv78dordu.fsf@gitster.g","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Tyler Cipriani","fromEmail":"tyler@tylercipriani.com","sentAt":"2026-09-25T20:53:23Z","receivedAt":"2026-09-25T20:53:27Z","isPatch":true,"body":"On 26-09-09 17:57:01, Junio C Hamano wrote:\n\n<snip>\n\n>Doesn't that mean it is more logical to use the default gc\n>expiration timeout than year 1970 and in any cases using the usual\n>gc expiration would not waste more time than using 1970, right?\n\nI like date=0 (i.e., 1970).\n\nTested locally, in _most_ cases both give the right answer. But\ndate=<cutoff> can give the wrong answer in a subset of cases, and date=0\ncan give a slower answer in a subset of cases.\n\nI think the wrong answer is worse, and I think the case where\ndate=<cutoff> provides a wrong answer is common for me (with default gc\nsettings and lots of old git clones).\n\nThe important bits of is_reachable_in_reflog:\n\n- date: initial: either gc.reflogExpire (default: 90 days) or 0. Later:\n   maybe set by a walk of remote reflog.\n- remote: e.g., remotes/origin/<x>\n- local: e.g., refs/heads/<x>\n- remote->old_oid: advertised oid for remote ref\n\nWe need to find remote->old_oid in the local reflog. We can't build on a\ncommit we've never fetched, so we set date to the last time remote's\nreflog moved to bound our walk of local.\n\nBut remote's reflog can expire or be empty, so it needs an initial\nvalue.\n\nWith date=0 (and remote gone: older than 90 days + gc, removed, or fresh\nclone), we walk local until we find the remote->old_oid or we run out of\nreflog to walk. But local is also subject to gc, so by default that's 90\ndays without having to bound anything. Since both reflogs are gc'd, the\ntime difference should be minimal.\n\nWith date=<cutoff> is only faster where we have no remote reflog, we\ndon't have remote->old_oid in our local, and our local reflog has\nentries older than the typical gc cutoff; viz. I'm rebuilding history\nwithout the remote tip and: (a) expired my remote reflog manually (b)\nhave my remote reflog gc configured differently than my local or (c) I\nhave gc turned off.\n\nBut in one case, date=0 gives the right answer and the cutoff date gives\nthe wrong answer:\n\n     git clone ...            # 1. files backend, no remote reflog\n     git reset --hard HEAD^   # 2. start a rewrite\n     ...                      # 3. do nothing for gc.reflogExpire amount\n     ...                      #    of time.\n     ...                      #    Remote never moves/we never fetch.\n     git commit ...           # 4. Finish rewrite and push\n     git push --force-if-includes --force-with-lease origin main\n\nPush fails with date=gc.reflogExpire (wrong). Push succeeds with date=0\n(right).\n\nAnd nothing about gc config need be tweaked from the defaults for this\nto happen---git gc can even happen (provided it runs between the initial\nclone and the reset, since the reflogUnreachable prune is 30 days by\ndefault). But in small repos, gc may not have been triggered at all.\n\nSo date=0 is always correct and should have equivalent in runtime in\nmost cases. And it neatly side-steps what cut off should we use?\ngc.reflogExpire vs.  gc.<remote>.reflogExpire vs.\ngc.<local>.reflogExpire vs. flat 90 days vs. do we respect\ngc.reflogExpire=never.\n\nThanks.\n"},{"id":"553335","messageId":"xmqqfqyxt2ll.fsf@gitster.g","threadId":"66260","inReplyTo":"arbfQ7xF1NgDeilU@localhost.localdomain","subject":"Re: [PATCH v2] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-25T21:58:46Z","receivedAt":"2026-09-25T21:58:49Z","isPatch":true,"body":"Tyler Cipriani <tyler@tylercipriani.com> writes:\n\n> So date=0 is always correct and should have equivalent in runtime in\n> most cases. And it neatly side-steps what cut off should we use?\n> gc.reflogExpire vs.  gc.<remote>.reflogExpire vs.\n> gc.<local>.reflogExpire vs. flat 90 days vs. do we respect\n> gc.reflogExpire=never.\n\nWith a reflog that has never been expired, using all the available\ninformation will always work with better information than with\ncutoff, so that is not surprising at all ;-).\n\n"},{"id":"553532","messageId":"arsP8IE6LuAKzYE6@localhost.localdomain","threadId":"66260","inReplyTo":"20260905171330.34646-1-f@lex.la","subject":"Re: [PATCH v3] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Tyler Cipriani","fromEmail":"tyler@tylercipriani.com","sentAt":"2026-09-29T01:10:08Z","receivedAt":"2026-09-29T01:10:11Z","isPatch":true,"body":"On 26-09-05 20:13:30, Aleksei Sviridkin wrote:\n\nCode looks right to me with the date=0 fallback.\n\npush.useForceIfIncludes is meant to tighten --force-with-lease's checks.\nGetting a wrong answer with advice telling me to pull would push me (pun\nintended) to drop the config and ditch the feature. For\n--force-if-includes, being wrong here is worse than being slow here.\n\nRe: being slow. I tried to recreate some numbers from this thread with\nmy own test case using linux.git and 2k reflog entries. In the cases I\ntried:\n\n- With a commit-graph (which gc should write), walking + merge-base\n   checks on 2k entries took 15ms. So a few microseconds per reflog entry\n   roughly jibes with numbers from this thread.\n- Without a commit-graph, each batched call to\n   repo_in_merge_bases_many() walks the commit history from scratch:\n   rejection took two minutes for 2k entries.\n\nBut my tests were artificial worst-case scenarios. And today, on my\nbuild, \"date\" already happens to be a low number. For folks like me,\nsetting date=0 is a non-change and I've been unable to find any\ncomplaints of slowness on the mailing list (or by searching the web).\n\nFor folks where date happens to be a high number: this gets the feature\nworking correctly. Bonus: doubling batch size after each call to\nrepo_in_merge_bases_many took my 2min down to 10s, locally; a viable\nspeed up if needed (but separate from this change).\n\n>Since 99a1f9ae10 (push: add reflog check for \"--force-if-includes\",\n>2020-10-03), is_reachable_in_reflog() stops walking the reflog of the\n>local branch at entries older than the newest reflog entry of the\n>remote-tracking ref. That timestamp is read by a callback of\n>refs_for_each_reflog_ent_reverse(), so when the remote-tracking ref\n>has no reflog, the variable that holds the timestamp stays\n>uninitialized.\n>\n>With the files backend a remote-tracking ref created by \"git clone\"\n>has no reflog and does not get one until it moves. On my machine the\n>leftover value exceeds any real timestamp: the walk stops at the very\n>first entry, never reaches the \"Created from\" entry that \"checkout\n>--track\" wrote, and the push is rejected with \"remote ref updated\n>since checkout\" although nothing on the remote has changed.\n>\n>The cut-off is an optimization that rests on an assumption: an entry\n>older than the moment the remote-tracking ref last moved is not\n>expected to be the one being looked for. Without a reflog there is\n>no such moment, hence no cut-off to apply. Initialize the timestamp\n>to zero to say exactly that: timestamp_t is unsigned, so no entry\n>compares older than zero and the comparison never fires. Using\n>\"now\", or any fixed age, would instead cut the walk off at the first\n>entry older than that bound, which is how the failure happens in\n>the first place. The price is paid only when no matching entry is\n>found: the walk then reaches the oldest entry and falls back to the\n>merge-base check over what it collected, where the cut-off would\n>have stopped it earlier.\n\nThe last paragraph of this log message is hard to read for me; I think\npeople could come away from reading it with the wrong information.\n\nNits:\n\n- The final paragraph of the log message starts with \"The cut-off\", but\n   it's the first time you've used \"cut-off.\" What cut-off?\n- \"an entry older than [...] is not expected to be the one being looked\n   for\" - passive voice, stacked verb phrases (\"is not expected/to be\"),\n   and a subject separated from its verb by 9 words made this hard to\n   follow. And it leaves questions: Why is <who or what> not looking at\n   <what> entry?\n- \"Without a reflog\" - which reflog? remote-tracking or local?\n- Unclear referents:\n     - \"exactly that\"\n     - \"that bound\"\n\nProblems (with more nits :)):\n\n- \"there is no such moment\"\n   - Readability: referring back to \"moment\" that came 23 words before\n     this \"moment\" made me re-read this a few times.\n   - Inaccuracy: there may have been a moment when the remote-tracking\n     ref last moved, but there is no reliable record of it because there\n     is no remote-tracking reflog. That is, someone may have removed the\n     reflog, or the reflog could have been GC'd (neither case is\n     mentioned in your message).\n- Most importantly, since the way I parse it is technically incorrect:\n   \"the walk then reaches the oldest entry and falls back to the\n   merge-base check...where the cut-off would have stopped it earlier.\" -\n   Stopped what earlier? I read this sentence split on \"where\" (i.e., Y\n   does this, whereas X does that).\n\n   Read that way, the final sentence reads as:\n\n   Walk without a cut-off:\n\n   (a) \"reaches the oldest entry\"\n   (b) \"falls back to the merge-base check\"\n\n   vs.\n\n   Walk with a cut-off: stops earlier and therefore does neither.\n\n   But a walk with a cut-off falls back to a merge-base check, too. The\n   difference is that without a cut-off you reach the oldest entry and\n   therefore pass more local reflog entries to the merge-base check;\n   i.e., potentially more calls to repo_in_merge_bases_many()\n\n>\n>Signed-off-by: Aleksei Sviridkin <f@lex.la>\n>---\n>Changes since v2:\n>  - reworded the first paragraph as you suggested\n>  - explain why zero is the fallback rather than \"now\" or a fixed age\n>  - dropped the Assisted-by trailer\n>\n> remote.c            |  2 +-\n> t/t5533-push-cas.sh | 18 ++++++++++++++++++\n> 2 files changed, 19 insertions(+), 1 deletion(-)\n>\n>diff --git a/remote.c b/remote.c\n>index 00723b385e..6d301698ca 100644\n>--- a/remote.c\n>+++ b/remote.c\n>@@ -2751,7 +2751,7 @@ static int check_and_collect_until(const char *refname UNUSED,\n>  */\n> static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n> {\n>-\ttimestamp_t date;\n>+\ttimestamp_t date = 0;\n> \tstruct commit *commit;\n> \tstruct commit **chunk;\n> \tstruct check_and_collect_until_cb_data cb;\n>diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh\n>index cba26a872d..bb8878c593 100755\n>--- a/t/t5533-push-cas.sh\n>+++ b/t/t5533-push-cas.sh\n>@@ -396,4 +396,22 @@ test_expect_success '\"--force-if-includes\" should allow deletes' '\n> \t)\n> '\n>\n>+test_expect_success '\"--force-if-includes\" should allow forced update when remote-tracking ref has no reflog' '\n>+\trm -fr dst src &&\n>+\ttest_when_finished \"rm -fr dst src\" &&\n>+\tgit init --bare dst &&\n>+\tgit push dst main main:branch &&\n>+\tgit clone --no-local dst src &&\n>+\t(\n>+\t\tcd src &&\n>+\t\t# a clone leaves the remote-tracking refs without reflog\n>+\t\t# entries with the files backend, but not with reftable\n>+\t\tgit reflog expire --all --expire=all &&\n>+\t\tgit switch -c branch --track origin/branch &&\n>+\t\tgit reset --hard HEAD^ &&\n>+\t\ttest_commit D &&\n>+\t\tgit push --force-if-includes --force-with-lease=\"branch\"\n>+\t)\n>+'\n>+\n> test_done\n\nTested: passes with the fix.\n\nWithout the fix it also passes on my machine. gdb says that the value of\ndate is 2 for me (Linux x86_64, gcc (Debian 14.2.0-19) 14.2.0, on\nTrixie). To get the test to fail reliably, had to build with:\n\n     make CFLAGS_APPEND=-ftrivial-auto-var-init=pattern\n\nSo, CI probably would miss date becoming uninitialized again. It also\nfails with a date set to a timestamp 90 days ago due, since test dates\nare 2005.\n\nMinor nit: surrounding tests in t/t5533-push-cas.sh use\nsetup_src_dup_dst, which would simplify the test setup.\n\nI'd be happy to give a Reviewed-by once the log message is clearer.\n\nThanks.\n"},{"id":"553562","messageId":"20260929091319.86392-1-f@lex.la","threadId":"66260","inReplyTo":"20260905171330.34646-1-f@lex.la","subject":"[PATCH v4] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Aleksei Sviridkin","fromEmail":"f@lex.la","sentAt":"2026-09-29T09:13:18Z","receivedAt":"2026-09-29T09:13:26Z","isPatch":true,"body":"Since 99a1f9ae10 (push: add reflog check for \"--force-if-includes\",\n2020-10-03), is_reachable_in_reflog() looks for the remote tip in the\nlocal branch's reflog and stops at entries older than the newest entry\nof the remote-tracking ref's reflog. That timestamp comes from a\ncallback of refs_for_each_reflog_ent_reverse(), which never runs when\nthe remote-tracking ref has no reflog, so the variable stays\nuninitialized.\n\nWith the files backend a remote-tracking ref that \"git clone\" created\nhas no reflog until it moves. On my machine the leftover value exceeded\nany real timestamp, so the walk stopped at the first entry and the push\nwas rejected with \"remote ref updated since checkout\" though nothing on\nthe remote had changed.\n\nThat stopping point assumes an entry older than the last recorded move\nof the remote-tracking ref cannot be the one we want. The record itself\ncan be missing: never written, deleted, or expired by gc. Initialize\nthe timestamp to zero for a missing record. timestamp_t is unsigned, so\nnothing compares older and the walk stops only at the remote tip or at\nthe end of the local reflog. \"Now\" brings the bug straight back. A\nfixed age narrows it: the push is rejected when the remote tip is\nrecorded only past the first entry older than that age and nothing\ncollected reaches it.\n\nWhen the remote tip is not in the local reflog at all, a stopped walk\nand a full one fall back to the same merge-base check, and the full one\nhands it more entries.\n\nSigned-off-by: Aleksei Sviridkin <f@lex.la>\n---\nChanges since v3:\n\n- log message rewritten. Two things in it were wrong, not just\n  unclear: it read as if a walk that stops at the cut-off skips the\n  merge-base check, and it said there is \"no such moment\" when what is\n  missing is the record of it.\n- test uses setup_src_dup_dst and expires only the remote-tracking\n  reflog.\n\nt5533 passes 24/24 with the fix on files and on reftable, and the new\ntest fails on both without it. That failure is only reliable when built\nwith\n\n\tmake CFLAGS_APPEND=-ftrivial-auto-var-init=pattern\n\notherwise the stack may hold a small number, as on your machine. So CI\nwould not catch this going uninitialized again.\n\nOne detail the expire hides: on files it leaves no reflog at all, on\nreftable an empty one. The callback does not run either way.\n\nThe batch size growth looks worth its own patch. Not touched here.\n\n remote.c            |  2 +-\n t/t5533-push-cas.sh | 16 ++++++++++++++++\n 2 files changed, 17 insertions(+), 1 deletion(-)\n\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..6d301698ca 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2751,7 +2751,7 @@ static int check_and_collect_until(const char *refname UNUSED,\n  */\n static int is_reachable_in_reflog(const char *local, const struct ref *remote)\n {\n-\ttimestamp_t date;\n+\ttimestamp_t date = 0;\n \tstruct commit *commit;\n \tstruct commit **chunk;\n \tstruct check_and_collect_until_cb_data cb;\ndiff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh\nindex cba26a872d..c9aaeec8d1 100755\n--- a/t/t5533-push-cas.sh\n+++ b/t/t5533-push-cas.sh\n@@ -396,4 +396,20 @@ test_expect_success '\"--force-if-includes\" should allow deletes' '\n \t)\n '\n \n+test_expect_success '\"--force-if-includes\" should allow forced update when remote-tracking ref has no reflog' '\n+\tsetup_src_dup_dst &&\n+\ttest_when_finished \"rm -fr dst src dup\" &&\n+\t(\n+\t\tcd src &&\n+\t\tgit switch branch &&\n+\t\tgit pull --rebase origin branch &&\n+\t\t# the bug needs a remote-tracking ref with no reflog, and\n+\t\t# the fetch above wrote one\n+\t\tgit reflog expire --expire=all refs/remotes/origin/branch &&\n+\t\tgit reset --hard HEAD^ &&\n+\t\ttest_commit I &&\n+\t\tgit push --force-if-includes --force-with-lease=\"branch\"\n+\t)\n+'\n+\n test_done\n-- \n2.55.0\n\n"},{"id":"553617","messageId":"xmqq8q4khuax.fsf@gitster.g","threadId":"66260","inReplyTo":"20260929091319.86392-1-f@lex.la","subject":"Re: [PATCH v4] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-29T16:54:46Z","receivedAt":"2026-09-29T16:54:49Z","isPatch":true,"body":"Aleksei Sviridkin <f@lex.la> writes:\n\n> Changes since v3:\n>\n> - log message rewritten. Two things in it were wrong, not just\n>   unclear: it read as if a walk that stops at the cut-off skips the\n>   merge-base check, and it said there is \"no such moment\" when what is\n>   missing is the record of it.\n> - test uses setup_src_dup_dst and expires only the remote-tracking\n>   reflog.\n\nBoth changes look sensible.  Thanks, Aleksei and Tyler, for writing\nand reviewing.\n\nWill replace and mark it for 'next'.\n\n> +test_expect_success '\"--force-if-includes\" should allow forced update when remote-tracking ref has no reflog' '\n> +\tsetup_src_dup_dst &&\n> +\ttest_when_finished \"rm -fr dst src dup\" &&\n> +\t(\n> +\t\tcd src &&\n> +\t\tgit switch branch &&\n> +\t\tgit pull --rebase origin branch &&\n> +\t\t# the bug needs a remote-tracking ref with no reflog, and\n> +\t\t# the fetch above wrote one\n> +\t\tgit reflog expire --expire=all refs/remotes/origin/branch &&\n> +\t\tgit reset --hard HEAD^ &&\n> +\t\ttest_commit I &&\n> +\t\tgit push --force-if-includes --force-with-lease=\"branch\"\n> +\t)\n> +'\n> +\n>  test_done\n"},{"id":"553622","messageId":"xmqqo6dggch0.fsf@gitster.g","threadId":"66260","inReplyTo":"arsP8IE6LuAKzYE6@localhost.localdomain","subject":"Re: [PATCH v3] push: fix --force-if-includes when remote-tracking ref has no reflog","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-09-29T18:05:15Z","receivedAt":"2026-09-29T18:05:18Z","isPatch":true,"body":"Tyler Cipriani <tyler@tylercipriani.com> writes:\n\n> Minor nit: surrounding tests in t/t5533-push-cas.sh use\n> setup_src_dup_dst, which would simplify the test setup.\n>\n> I'd be happy to give a Reviewed-by once the log message is clearer.\n\nThanks.  Just FYI, the patch has textual conflicts in t5533 with\nyour tc/push-force-if-includes-fixes topic, but the resolution was\ntrivial.\n"}]}