diff --git a/remote.c b/remote.c index 00723b385e..887c7ec00c 100644 --- a/remote.c +++ b/remote.c @@ -2806,10 +2806,32 @@ cleanup_return: */ static void check_if_includes_upstream(struct ref *remote) { - struct ref *local = get_local_ref(remote->name); - if (!local) + struct ref *local; + const char *name; + + /* ref without peer_ref will not be pushed */ + if (!remote->peer_ref) return; + /* A deletion has no local history to check against. */ + if (is_null_oid(&remote->peer_ref->new_oid)) + return; + + name = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), + remote->peer_ref->name, + RESOLVE_REF_READING, NULL, NULL); + + /* + * if we resolve the ref to anything other than a branch, + * then there is no reliable reflog to check + */ + if (!name || !starts_with(name, "refs/heads/")) { + remote->unreachable = 1; + return; + } + + local = get_local_ref(name); + if (is_reachable_in_reflog(local->name, remote) <= 0) remote->unreachable = 1; free_one_ref(local); diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh index cba26a872d..265be6a84c 100755 --- a/t/t5533-push-cas.sh +++ b/t/t5533-push-cas.sh @@ -396,4 +396,85 @@ test_expect_success '"--force-if-includes" should allow deletes' ' ) ' +test_expect_success '"--force-if-includes" should allow forced update when using differently named branches' ' + setup_src_dup_dst && + test_when_finished "rm -fr dst src dup" && + ( + cd src && + git fetch && + git switch -c newbranch origin/main && + git rebase HEAD --onto HEAD^ && + git push --force-if-includes --force-with-lease origin newbranch:main + ) +' + +test_expect_success '"--force-if-includes" should allow forced update from HEAD' ' + setup_src_dup_dst && + test_when_finished "rm -fr dst src dup" && + ( + cd src && + git fetch && + git switch -c newbranch origin/main && + git rebase HEAD --onto HEAD^ && + git push --force-if-includes --force-with-lease origin HEAD:main + ) +' + +test_expect_success '"--force-if-includes" should reject forced update from differently named branches when local lacks remote ref' ' + setup_src_dup_dst && + test_when_finished "rm -fr dst src dup" && + ( + cd src && + git fetch && + git switch main && + git reset --hard origin/main && + git switch --orphan orphan && + test_commit I && + test_must_fail git push --force-with-lease --force-if-includes origin orphan:main + ) +' + +test_expect_success '"--force-if-includes" should reject forced update from HEAD when it lacks remote ref' ' + setup_src_dup_dst && + test_when_finished "rm -fr dst src dup" && + ( + cd src && + git fetch && + git switch main && + git reset --hard origin/main && + git switch --orphan orphan && + test_commit I && + test_must_fail git push --force-with-lease --force-if-includes origin HEAD:main + ) +' + +test_expect_success '"--force-if-includes" should reject forced update from detached HEAD' ' + setup_src_dup_dst && + test_when_finished "rm -fr dst src dup" && + ( + cd src && + git fetch && + git switch main && + git reset --hard origin/main && + git switch -c newbranch origin/main && + git checkout HEAD^ && + test_must_fail git push --force-if-includes --force-with-lease origin HEAD:main + ) +' + +test_expect_success '"--force-if-includes" should reject forced update from tag' ' + setup_src_dup_dst && + test_when_finished "rm -fr dst src dup" && + ( + cd src && + git fetch && + git switch main && + git reset --hard origin/main && + git switch -c newbranch origin/main && + git checkout HEAD^ && + git tag stable && + test_must_fail git push --force-if-includes --force-with-lease origin stable:main + ) +' + test_done