#!/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"
# - 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
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 &&
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