[PATCH v2 0/2] push: fix --force-if-includes consulting wrong ref
From: Tyler Cipriani <hidden>
Date: 2026-09-08 22:21:35
Changes since v1:
- Clarify in log message 1/2 that --force-if-includes will reject a
detached HEAD today (when the same-named local branch lacks the remote
tip). And note that this change makes it explicit to always reject
the detached HEAD case.
--force-if-includes has been checking the reflog of the local branch named
after the destination branch regardless of what's being pushed. This can cause
false rejections or unintended data loss.
False rejection has been reported twice that I could find:
- 2023-07-26 - Stefan Haller reported local branch with a different name
false rejection[0]
- 2025-05-08 - D. Ben Knoble reported detached HEAD false rejection[1]
The same root cause can result in data loss: when a same-name local branch
contains the remote tip but you --force-if-includes push an unrelated branch,
clobbering the remote repo. PoCs are in t/t5533-push-cas.sh -- new test cases
fail against maint, but pass with patches applied.
Existing tests covered refspecs with different names for --force-with-lease,
but missed --force-if-includes. New patches cover:
- allow forced-update using refspec with different-named local branch
- allow same as above, but with HEAD
- reject force-update using refspec with different-named local branch lacking
branch tip
- reject same as above using HEAD
- reject detached HEAD
Resolved question: the detached HEAD case; HEAD's reflog was considered
and rejected as too broad for purpose in the original review. cf. [2]
[0]: <https://lore.kernel.org/git/f51c73ed-eb03-83ca-fb31-d3e2645c9a63@haller-berlin.de (local)>
[1]: <https://lore.kernel.org/git/CALnO6CCk0SgwObQRnpd5Pt_DvCKF8dBmyVHivU6Nr_O-GusGLA@mail.gmail.com (local)>
[2]: <https://lore.kernel.org/git/CAHLx=O=tVhtiZpaRP9TpfiBfOMS2xPe3c3=mC3VNEdBrLOioFg@mail.gmail.com (local)>
Tyler Cipriani (2):
push: check pushed ref for --force-if-includes
push: fix --force-if-includes detached HEAD advice
Documentation/config/advice.adoc | 4 ++
advice.c | 1 +
advice.h | 1 +
builtin/push.c | 15 +++++++
builtin/send-pack.c | 5 +++
remote.c | 27 +++++++++++-
remote.h | 10 +++--
send-pack.c | 1 +
t/t5533-push-cas.sh | 70 +++++++++++++++++++++++++++++++-
transport-helper.c | 5 +++
transport.c | 8 ++++
transport.h | 1 +
12 files changed, 143 insertions(+), 5 deletions(-)
Range-diff against v1:
1: 5e866b883e ! 1: da27c421ed push: check pushed ref for --force-if-includes
@@ Commit message
the remote main.
Find local reflog using ref->peer_ref. When using a refspec like
- HEAD:refs/heads/main, we resolve HEAD to a branch and use that reflog.
- In a detached HEAD state, the reflog cannot tell us if the history
- being pushed includes the tip of the remote, so the push is rejected.
+ HEAD:refs/heads/main, we resolve HEAD. If HEAD is a branch, use that
+ branch's reflog.
+
+ But if HEAD does not resolve to a branch (i.e. a detached HEAD), then we
+ reject the push. HEAD's reflog is too broad to tell us if the history
+ being pushed includes the tip of the remote. Rejecting a detached HEAD
+ already happens today (if the same-named local branch lacks the remote
+ tip); now the detached HEAD state is explicitly rejected.
Skip deletions:
2: 4ae40db7fe = 2: e07d16d53e push: fix --force-if-includes detached HEAD advice
base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc
--
2.47.3