diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b30fdcf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +# This action is run from sequoia-pgp/fast-forward. It needs to +# access the sequoia-pgp/fast-forward-unit-test repository. We can't +# use a GitHub Token: its permissions are limited to the repository in +# which the action is run. Instead, we can use a deploy key: +# +# https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys#deploy-keys +# +# Deploy keys with write access can perform the same actions as an +# organization member with admin access, or a collaborator on a +# personal repository. For more information, see "Repository roles +# for an organization" and "Permission levels for a personal account +# repository." +# +# To add a deply key to fast-forward-unit-tests, go to: +# +# https://github.com/sequoia-pgp/fast-forward-unit-tests +# +# Then go to settings, deploy keys, add deploy key. +# +# Generate a new ssh key pair in fast-forward/secrets (don't add it to +# the repository). +# +# $ ssh-keygen -t ed25519 +# +# In the fast-forward repository, navigate to Settings, Secrets and +# variables, Actions, New repository secret. Name the environment +# variable `FAST_FORWARD_UNIT_TESTS_SSH_KEY`. This is automatically +# used by the fast-forward script. + +name: ci +on: + push: + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + compile: + name: Fast forward + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Check that /fast-forward fast forwards. + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FAST_FORWARD_UNIT_TESTS_SSH_KEY: ${{ secrets.FAST_FORWARD_UNIT_TESTS_SSH_KEY }} + run: tests/fast-forward.sh diff --git a/.gitignore b/.gitignore index b25c15b..327b91f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *~ +tests/run.sh +secrets diff --git a/tests/fast-forward.sh b/tests/fast-forward.sh new file mode 100755 index 0000000..de80994 --- /dev/null +++ b/tests/fast-forward.sh @@ -0,0 +1,286 @@ +#! /bin/bash + +# Go to: +# - Top-Right Menu +# - Settings +# - Developer Settings +# - Personal access tokens +# - Tokens (classic) +# - Generate a personal access token +# +# Create a token with "repo" permission. +if test x$GITHUB_TOKEN = x +then + echo "GITHUB_TOKEN environment variable is not set, but is required." + exit 1 +fi + +if test x$GITHUB_ACTOR = x +then + echo "GITHUB_ACTOR environment variable is not set, but is required." + exit 1 +fi + +if test "x$FAST_FORWARD_UNIT_TESTS_SSH_KEY" != x +then + echo "Adding deploy key for fast-forward-unit-tests repository." + # XXX: It would be better to use an ssh-agent. + SSH_ID=$(mktemp) + echo "$FAST_FORWARD_UNIT_TESTS_SSH_KEY" > "$SSH_ID" +else + echo "No deploy key for target repository. Hoping for the best." +fi +echo SSH_ID: $SSH_ID + +# Where to run the test. +OWNER=${OWNER:-sequoia-pgp} +REPO=${REPO:-fast-forward-unit-tests} + +TEMPFILES=$(mktemp) +echo -n "$TEMPFILES" >> "$TEMPFILES" +function maketemp { + F=$(mktemp $*) + echo -n " $F" >> $TEMPFILES + echo "$F" +} +function maketemp_exit { + TEMPFILES=$(cat $TEMPFILES) + if test x"$TEMPFILES" != x + then + echo -e "Cleanup by running:\n $ rm -rf $TEMPFILES" + fi +} +trap maketemp_exit EXIT + +set -ex + +# Files from the fast-forward repository that we copy over. +FILES=".github/workflows/fast-forward.yml + .github/workflows/pull_request.yml" + +FAST_FORWARD_REPO=$(git rev-parse --show-toplevel) +for f in $FILES +do + if ! test -e "$FAST_FORWARD_REPO/$f" + then + echo "Missing \"$f\". Are you really in the fast-forward repo?" + exit 1 + fi +done + +echo "::group::Initializing scratch repository" + +D=$(maketemp -d) +echo "Scratch directory: $D" +cd $D + +git init --initial-branch main . +git config user.name "Fast Forward Unit Test" +git config user.email "neal@sequoia-pgp.org" +if test "x$SSH_ID" != x +then + git config core.sshCommand "ssh -i $SSH_ID" +fi + +git remote add origin "https://$GITHUB_ACTOR@github.com/$OWNER/$REPO.git" + +echo "::endgroup::" + +echo "::group::Add commit #1" + +# Add the workflow files. +for f in $FILES +do + mkdir -p $(dirname $f) + cp "$FAST_FORWARD_REPO/$f" "$f" + git add "$f" +done + +git commit -m 'Initial commit' --no-gpg-sign + +BASE=fast-forward-test-0$RANDOM +git push origin main:$BASE + +echo "::endgroup::" + +# Create a new commit, push it to a different branch. +echo "::group::Add commit #2" + +echo $RANDOM > hello +git add hello +git commit -m 'Hello' --no-gpg-sign + +PR=$BASE-pr +git push origin main:$PR + +echo "::endgroup::" + +echo "::group::Open pull request" + +# Create a pull request. +OPEN_PR_RESULT=$(maketemp) +curl --silent --show-error --output $OPEN_PR_RESULT -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/$OWNER/$REPO/pulls \ + -d '{ + "title":"Test", + "body":"This is a test, only a test!", + "head":"'"$PR"'", + "base":"'"$BASE"'" + }' + +PR_URL=$(jq -r ".url" < $OPEN_PR_RESULT) +if test "x$PR_URL" = xnull +then + echo "Couldn't get PR's URL" + exit 1 +fi +PR_NUMBER=$(jq -r ".number" < $OPEN_PR_RESULT) +if test "x$PR_NUMBER" = xnull +then + echo "Couldn't get PR's URL" + exit 1 +fi + +echo "::endgroup::" + +# Wait for the check-fast-forward job to finish and check the results. +echo "::group::Check that the check-fast-forward action ran, and said yes" + +COMMENTS_RESULT=$(maketemp) +for i in $(seq 1 10) +do + if test $i -eq 10 + then + echo "Timeout waiting for check-fast-forward job" + cat "$COMMENTS_RESULT" + exit 1 + fi + sleep 3 + + curl --silent --show-error --output "$COMMENTS_RESULT" -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/$OWNER/$REPO/issues/$PR_NUMBER/comments + + COMMENT=$(jq -r .[0].body <"$COMMENTS_RESULT") + if test "x$COMMENT" = xnull + then + # The job hasn't completed yet. + continue + else + if echo $COMMENT | grep -q 'you can add a comment with `/fast-forward` to fast forward' + then + echo check-fast-forward worked. + else + echo "Unexpected comment in response to push, did check-fast-forward change?" + cat $COMMENTS_RESULT + exit 1 + fi + fi + + break +done + +echo "::endgroup::" + +echo "::group::Post a /fast-forward comment to fast forward the pull request" + +curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/$OWNER/$REPO/issues/$PR_NUMBER/comments \ + -d '{ "body":"We should /fast-forward this..." }' + +# Wait for the fast-forward job to finish and then check the results. +for i in $(seq 1 10) +do + if test $i -eq 10 + then + echo "Timeout waiting for fast-forward job" + cat "$COMMENTS_RESULT" + exit 1 + fi + sleep 3 + + curl --silent --show-error --output "$COMMENTS_RESULT" -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/$OWNER/$REPO/issues/$PR_NUMBER/comments + + # Comment 0 is from check-fast-forward, 1 is our /fast-forward, + # and 2 will be from fast-forward. + COMMENT=$(jq -r .[2].body <"$COMMENTS_RESULT") + if test "x$COMMENT" = xnull + then + # The job hasn't completed yet. + continue + else + if echo $COMMENT | grep -q 'Fast forwarding `'"$BASE"'`' + then + echo fast-forward worked. + else + echo "Unexpected comment in response to /fast-forward, did fast-forward change?" + cat "$COMMENTS_RESULT" + exit 1 + fi + fi + + break +done + +echo "::endgroup::" + +# Make sure the base was fast forwarded by checking that it's sha is +# now identical to HEAD. +echo "::group::Check that the remote branch was fast forwarded" + +git fetch -a origin $BASE + +BASE_SHA=$(git rev-parse origin/$BASE) +if test x$BASE_SHA = x +then + echo "Base branch disappeared?!?" + exit 1 +fi +HEAD_SHA=$(git rev-parse HEAD) +if test "x$BASE_SHA" != "x$HEAD_SHA" +then + echo "Base was not fast forwarded to HEAD: $BASE_SHA != $HEAD_SHA!" + exit 1 +fi + +echo "Pull request was fast forwarded!" + +echo "::endgroup::" + +# Make sure the base was fast forwarded by checking that it's sha is +# now identical to HEAD. +echo "::group::Check that the PR is closed" + +MERGED_PR_RESULT=$(maketemp) +curl --silent --show-error --output $MERGED_PR_RESULT -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/$OWNER/$REPO/issues/$PR_NUMBER +cat $MERGED_PR_RESULT + +STATE=$(jq -r .state <"$MERGED_PR_RESULT") +if test "x$STATE" != xclosed +then + echo "PR was not closed (state: '$STATE')" + exit 1 +fi + +echo "::endgroup::" + +# Clean up on success. +rm -rf $D