From: Stephen Haberman Date: Wed, 9 Jul 2008 08:28:21 +0000 (-0500) Subject: Rewrite of stable protection with the 'no-ff' strategy. X-Git-Url: http://git.droids-corp.org/?a=commitdiff_plain;h=0a3e94c93e3635cc3c88460e185a673c89c85ede;p=git-central.git Rewrite of stable protection with the 'no-ff' strategy. --- diff --git a/server/update-stable b/server/update-stable index 416495c..64f7369 100644 --- a/server/update-stable +++ b/server/update-stable @@ -1,5 +1,41 @@ #!/bin/sh +# +# This enforces stable only moving in the approved way, which +# is via empty (no change) merge commits. The rationale is that +# in the DAG we want a simple, one-commit move from each release +# to the next. +# +# We started out with: +# +# * -- A stable +# \ \ +# \ * -- * -- B topic1 +# \ / +# * -- * -- * topic2 +# +# And then publishing stable was a matter of fast-forwarding +# from A to B. +# +# In a complicated (non-rebased) DAG, this becomes hard to follow, +# so want we want instead is: +# +# * -- A ----------- C stable +# \ \ / +# \ * -- * -- B topic1 +# \ / +# * -- * -- * topic2 +# +# Where commit C lists as it's first parent the prior stable +# commit and as it's second parent the release candidate. No +# other parents are allowed (e.g. no octopus merges here, which +# would insinuate qa didn't happen on the merged result). +# +# Also, we want to enforce that C does not actually introduce +# any diffs to the files between B and C--otherwise this changes +# would not have appeared in QA. +# + # Command line refname="$1" oldrev="$2" @@ -18,14 +54,43 @@ fi # - unless they were already pointed to by a branch # = all new commits on stable count=$(git rev-parse --not --branches | git rev-list --stdin $oldrev..$newrev | wc -l) -if [ "$count" -ne "0" ] ; then - newname=$(git rev-parse "$newrev") +if [ "$count" -ne "1" ] ; then echo "----------------------------------------------------" echo - echo "Moving stable to $newname includes a new commit" + echo "Moving stable must entail a single commit" echo echo "----------------------------------------------------" exit 1 fi +number_of_parents=$(git rev-list --no-walk --parents $newrev | sed 's/ /\n/g' | grep -v $newrev | wc -l) +if [ "$number_of_parents" -ne "2" ] ; then + echo "----------------------------------------------------" + echo + echo "Moving stable must entail a merge commit" + echo + echo "----------------------------------------------------" + exit 1 +fi + +first_parent=$(git rev-list --no-walk --parents $newrev | sed 's/ /\n/g' | grep -v $newrev | head --lines=1) +if [ "$first_parent" != "$oldrev" ] ; then + echo "----------------------------------------------------" + echo + echo "Moving stable must have the previous stable as the first parent" + echo + echo "----------------------------------------------------" + exit 1 +fi + +second_parent=$(git rev-list --no-walk --parents $newrev | sed 's/ /\n/g' | grep -v $newrev | tail --lines=1) +changed_lines=$(git diff $second_parent..$newrev | wc -l) +if [ "$changed_lines" -ne "0" ] ; then + echo "----------------------------------------------------" + echo + echo "Moving stable must not result in any changes" + echo + echo "----------------------------------------------------" + exit 1 +fi diff --git a/tests/t2100-server-update-stable.sh b/tests/t2100-server-update-stable.sh index 32dd039..9143008 100644 --- a/tests/t2100-server-update-stable.sh +++ b/tests/t2100-server-update-stable.sh @@ -25,11 +25,11 @@ test_expect_success 'reject commit directly to stable' ' git commit -a -m "$test_name going onto stable" && head=$(git rev-parse HEAD) && ! git push 2>push.err && - cat push.err | grep "Moving stable to $head includes a new commit" && + cat push.err | grep "Moving stable must entail a merge commit" && git reset --hard HEAD^ ' -test_expect_success 'reject aged topic branch' ' +test_expect_success 'reject fast-forward to candidate branch' ' # make one topic branch git checkout -b topic1 stable && echo $test_name >topic1 && @@ -37,55 +37,71 @@ test_expect_success 'reject aged topic branch' ' git commit -m "$test_name topic1" && git push origin topic1 && - # now make another topic - git checkout -b topic2 stable - echo $test_name >topic2 && - git add topic2 && - git commit -m "$test_name topic2" && - git push origin topic2 && - - # merge in topic2 git checkout stable && - git merge topic2 && - git push && - - # merge in topic1 fails - git merge topic1 && - head=$(git rev-parse HEAD) && + git merge topic1 >merge.out && + cat merge.out | grep "Fast forward" && ! git push 2>push.err && - cat push.err | grep "Moving stable to $head includes a new commit" && + cat push.err | grep "Moving stable must entail a single commit" && git reset --hard ORIG_HEAD ' -test_expect_success 'accept updated aged topic branch' ' +test_expect_success 'reject merge with wrong first-parent' ' # make one topic branch + git checkout -b topic2 stable && + echo $test_name >topic2 && + git add topic2 && + git commit -m "$test_name topic2" && + git push origin topic2 && + + # move ahead stable by topic3 git checkout -b topic3 stable && echo $test_name >topic3 && git add topic3 && git commit -m "$test_name topic3" && git push origin topic3 && + git checkout stable && + git merge --no-ff topic3 && + git push && - # now make another topic - git checkout -b topic4 stable + # back to topic2, merge in stable, and try to push it out as the new stable + git checkout topic2 && + git merge stable && + ! git push origin topic2:refs/heads/stable 2>push.err && + cat push.err | grep "Moving stable must have the previous stable as the first parent" && + + # Go ahead and push topic2 itself + git push && + + # but merging into stable should still work fine + git checkout stable && + git merge --no-ff topic2 && + git push +' + +test_expect_success 'reject merge with changes' ' + # make one topic branch + git checkout -b topic4 stable && echo $test_name >topic4 && git add topic4 && git commit -m "$test_name topic4" && git push origin topic4 && - # merge in topic4 + # move ahead stable by topic5 + git checkout -b topic5 stable && + echo $test_name >topic5 && + git add topic5 && + git commit -m "$test_name topic5" && + git push origin topic5 && git checkout stable && - git merge topic4 && - git push && - - # update topic3 first - git checkout topic3 && - git merge stable && + git merge --no-ff topic5 && git push && - # Now we can update stable + # try merging topic4 into stable, which will get a merge commit, but + # it should have changes involved and so get rejected git checkout stable && - git merge topic3 && - git push + git merge topic4 && + ! git push 2>push.err && + cat push.err | grep "Moving stable must not result in any changes" ' test_done