| #!/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="<refname>:<expect>"' ' |
| 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 |