Rewrite of stable protection with the 'no-ff' strategy.
authorStephen Haberman <stephen@exigencecorp.com>
Wed, 9 Jul 2008 08:28:21 +0000 (03:28 -0500)
committerStephen Haberman <stephen@exigencecorp.com>
Wed, 9 Jul 2008 08:28:21 +0000 (03:28 -0500)
server/update-stable
tests/t2100-server-update-stable.sh

index 416495c..64f7369 100644 (file)
@@ -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
 
index 32dd039..9143008 100644 (file)
@@ -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