Thread (12 messages) flat view 12 messages, 3 authors, 5h ago
HOTtoday

[PATCH v3] push: fix --force-if-includes when remote-tracking ref has no reflog

From: Aleksei Sviridkin <hidden>
Date: 2026-09-05 17:13:34
Subsystem: the rest · Maintainer: Linus Torvalds

Since 99a1f9ae10 (push: add reflog check for "--force-if-includes",
2020-10-03), is_reachable_in_reflog() stops walking the reflog of the
local branch at entries older than the newest reflog entry of the
remote-tracking ref. That timestamp is read by a callback of
refs_for_each_reflog_ent_reverse(), so when the remote-tracking ref
has no reflog, the variable that holds the timestamp stays
uninitialized.

With the files backend a remote-tracking ref created by "git clone"
has no reflog and does not get one until it moves. On my machine the
leftover value exceeds any real timestamp: the walk stops at the very
first entry, never reaches the "Created from" entry that "checkout
--track" wrote, and the push is rejected with "remote ref updated
since checkout" although nothing on the remote has changed.

The cut-off is an optimization that rests on an assumption: an entry
older than the moment the remote-tracking ref last moved is not
expected to be the one being looked for. Without a reflog there is
no such moment, hence no cut-off to apply. Initialize the timestamp
to zero to say exactly that: timestamp_t is unsigned, so no entry
compares older than zero and the comparison never fires. Using
"now", or any fixed age, would instead cut the walk off at the first
entry older than that bound, which is how the failure happens in
the first place. The price is paid only when no matching entry is
found: the walk then reaches the oldest entry and falls back to the
merge-base check over what it collected, where the cut-off would
have stopped it earlier.

Signed-off-by: Aleksei Sviridkin <redacted>
---
Changes since v2:
  - reworded the first paragraph as you suggested
  - explain why zero is the fallback rather than "now" or a fixed age
  - dropped the Assisted-by trailer

 remote.c            |  2 +-
 t/t5533-push-cas.sh | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/remote.c b/remote.c
index 00723b385e..6d301698ca 100644
--- a/remote.c
+++ b/remote.c
@@ -2751,7 +2751,7 @@ static int check_and_collect_until(const char *refname UNUSED,
  */
 static int is_reachable_in_reflog(const char *local, const struct ref *remote)
 {
-	timestamp_t date;
+	timestamp_t date = 0;
 	struct commit *commit;
 	struct commit **chunk;
 	struct check_and_collect_until_cb_data cb;
diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh
index cba26a872d..bb8878c593 100755
--- a/t/t5533-push-cas.sh
+++ b/t/t5533-push-cas.sh
@@ -396,4 +396,22 @@ test_expect_success '"--force-if-includes" should allow deletes' '
 	)
 '
 
+test_expect_success '"--force-if-includes" should allow forced update when remote-tracking ref has no reflog' '
+	rm -fr dst src &&
+	test_when_finished "rm -fr dst src" &&
+	git init --bare dst &&
+	git push dst main main:branch &&
+	git clone --no-local dst src &&
+	(
+		cd src &&
+		# a clone leaves the remote-tracking refs without reflog
+		# entries with the files backend, but not with reftable
+		git reflog expire --all --expire=all &&
+		git switch -c branch --track origin/branch &&
+		git reset --hard HEAD^ &&
+		test_commit D &&
+		git push --force-if-includes --force-with-lease="branch"
+	)
+'
+
 test_done
base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc
-- 
2.55.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help