#!/bin/sh test_description='compare & swap push force/delete safety' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh setup_srcdst_basic () { rm -fr src dst && git clone --no-local . src && git clone --no-local src dst && ( cd src && git checkout HEAD^0 ) } # For tests with "--force-if-includes". setup_src_dup_dst () { rm -fr src dup dst && git init --bare dst && git clone --no-local dst src && git clone --no-local dst dup ( cd src && test_commit A && test_commit B && test_commit C && git push origin ) && ( cd dup && git fetch && git merge origin/main && git switch -c branch main~2 && test_commit D && test_commit E && git push origin --all ) && ( cd src && git switch main && git fetch --all && git branch branch --track origin/branch && git rebase origin/main ) && ( cd dup && git switch main && test_commit F && test_commit G && git switch branch && test_commit H && git push origin --all ) } test_expect_success setup ' # create template repository test_commit A && test_commit B && test_commit C ' test_expect_success 'push to update (protected)' ' setup_srcdst_basic && ( cd dst && test_commit D && test_must_fail git push --force-with-lease=main:main origin main 2>err && grep "stale info" err ) && git ls-remote . refs/heads/main >expect && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to update (protected, forced)' ' setup_srcdst_basic && ( cd dst && test_commit D && git push --force --force-with-lease=main:main origin main 2>err && grep "forced update" err ) && git ls-remote dst refs/heads/main >expect && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to update (protected, tracking)' ' setup_srcdst_basic && ( cd src && git checkout main && test_commit D && git checkout HEAD^0 ) && git ls-remote src refs/heads/main >expect && ( cd dst && test_commit E && git ls-remote . refs/remotes/origin/main >expect && test_must_fail git push --force-with-lease=main origin main && git ls-remote . refs/remotes/origin/main >actual && test_cmp expect actual ) && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to update (protected, tracking, forced)' ' setup_srcdst_basic && ( cd src && git checkout main && test_commit D && git checkout HEAD^0 ) && ( cd dst && test_commit E && git ls-remote . refs/remotes/origin/main >expect && git push --force --force-with-lease=main origin main ) && git ls-remote dst refs/heads/main >expect && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to update (allowed)' ' setup_srcdst_basic && ( cd dst && test_commit D && git push --force-with-lease=main:main^ origin main ) && git ls-remote dst refs/heads/main >expect && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to update (allowed, tracking)' ' setup_srcdst_basic && ( cd dst && test_commit D && git push --force-with-lease=main origin main 2>err && ! grep "forced update" err ) && git ls-remote dst refs/heads/main >expect && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to update (allowed even though no-ff)' ' setup_srcdst_basic && ( cd dst && git reset --hard HEAD^ && test_commit D && git push --force-with-lease=main origin main 2>err && grep "forced update" err ) && git ls-remote dst refs/heads/main >expect && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to delete (protected)' ' setup_srcdst_basic && git ls-remote src refs/heads/main >expect && ( cd dst && test_must_fail git push --force-with-lease=main:main^ origin :main ) && git ls-remote src refs/heads/main >actual && test_cmp expect actual ' test_expect_success 'push to delete (protected, forced)' ' setup_srcdst_basic && ( cd dst && git push --force --force-with-lease=main:main^ origin :main ) && git ls-remote src refs/heads/main >actual && test_must_be_empty actual ' test_expect_success 'push to delete (allowed)' ' setup_srcdst_basic && ( cd dst && git push --force-with-lease=main origin :main 2>err && grep deleted err ) && git ls-remote src refs/heads/main >actual && test_must_be_empty actual ' test_expect_success 'cover everything with default force-with-lease (protected)' ' setup_srcdst_basic && ( cd src && git branch nain main^ ) && git ls-remote src refs/heads/\* >expect && ( cd dst && test_must_fail git push --force-with-lease origin main main:nain ) && git ls-remote src refs/heads/\* >actual && test_cmp expect actual ' test_expect_success 'cover everything with default force-with-lease (allowed)' ' setup_srcdst_basic && ( cd src && git branch nain main^ ) && ( cd dst && git fetch && git push --force-with-lease origin main main:nain ) && git ls-remote dst refs/heads/main | sed -e "s/main/nain/" >expect && git ls-remote src refs/heads/nain >actual && test_cmp expect actual ' test_expect_success 'new branch covered by force-with-lease' ' setup_srcdst_basic && ( cd dst && git branch branch main && git push --force-with-lease=branch origin branch ) && git ls-remote dst refs/heads/branch >expect && git ls-remote src refs/heads/branch >actual && test_cmp expect actual ' test_expect_success 'new branch covered by force-with-lease (explicit)' ' setup_srcdst_basic && ( cd dst && git branch branch main && git push --force-with-lease=branch: origin branch ) && git ls-remote dst refs/heads/branch >expect && git ls-remote src refs/heads/branch >actual && test_cmp expect actual ' test_expect_success 'new branch already exists' ' setup_srcdst_basic && ( cd src && git checkout -b branch main && test_commit F ) && ( cd dst && git branch branch main && test_must_fail git push --force-with-lease=branch: origin branch ) ' test_expect_success 'background updates of REMOTE can be mitigated with a non-updated REMOTE-push' ' rm -rf src dst && git init --bare src.bare && test_when_finished "rm -rf src.bare" && git clone --no-local src.bare dst && test_when_finished "rm -rf dst" && ( cd dst && test_commit G && git remote add origin-push ../src.bare && git push origin-push main:main ) && git clone --no-local src.bare dst2 && test_when_finished "rm -rf dst2" && ( cd dst2 && test_commit H && git push ) && ( cd dst && test_commit I && git fetch origin && test_must_fail git push --force-with-lease origin-push && git fetch origin-push && git push --force-with-lease origin-push ) ' test_expect_success 'background updates to remote can be mitigated with "--force-if-includes"' ' setup_src_dup_dst && test_when_finished "rm -fr dst src dup" && git ls-remote dst refs/heads/main >expect.main && git ls-remote dst refs/heads/branch >expect.branch && ( cd src && git switch branch && test_commit I && git switch main && test_commit J && git fetch --all && test_must_fail git push --force-with-lease --force-if-includes --all ) && git ls-remote dst refs/heads/main >actual.main && git ls-remote dst refs/heads/branch >actual.branch && test_cmp expect.main actual.main && test_cmp expect.branch actual.branch ' test_expect_success 'background updates to remote can be mitigated with "push.useForceIfIncludes"' ' setup_src_dup_dst && test_when_finished "rm -fr dst src dup" && git ls-remote dst refs/heads/main >expect.main && ( cd src && git switch branch && test_commit I && git switch main && test_commit J && git fetch --all && git config --local push.useForceIfIncludes true && test_must_fail git push --force-with-lease=main origin main ) && git ls-remote dst refs/heads/main >actual.main && test_cmp expect.main actual.main ' test_expect_success '"--force-if-includes" should be disabled for --force-with-lease=":"' ' setup_src_dup_dst && test_when_finished "rm -fr dst src dup" && git ls-remote dst refs/heads/main >expect.main && ( cd src && git switch branch && test_commit I && git switch main && test_commit J && remote_head="$(git rev-parse refs/remotes/origin/main)" && git fetch --all && test_must_fail git push --force-if-includes --force-with-lease="main:$remote_head" 2>err && grep "stale info" err ) && git ls-remote dst refs/heads/main >actual.main && test_cmp expect.main actual.main ' test_expect_success '"--force-if-includes" should allow forced update after a rebase ("pull --rebase")' ' setup_src_dup_dst && test_when_finished "rm -fr dst src dup" && ( cd src && git switch branch && test_commit I && git switch main && test_commit J && git pull --rebase origin main && git push --force-if-includes --force-with-lease="main" ) ' test_expect_success '"--force-if-includes" should allow forced update after a rebase ("pull --rebase", local rebase)' ' setup_src_dup_dst && test_when_finished "rm -fr dst src dup" && ( cd src && git switch branch && test_commit I && git switch main && test_commit J && git pull --rebase origin main && git rebase --onto HEAD~4 HEAD~1 && git push --force-if-includes --force-with-lease="main" ) ' test_expect_success '"--force-if-includes" should allow deletes' ' setup_src_dup_dst && test_when_finished "rm -fr dst src dup" && ( cd src && git switch branch && git pull --rebase origin branch && git push --force-if-includes --force-with-lease="branch" origin :branch ) ' test_done