diff --git a/README.md b/README.md index 2611a30..b3d4095 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ dotnet test --no-build ``` ~~~ -This script will be run with `set -euo pipefail`, and the commit will be considered correct if the script exits successfully. +This script will be run with `set -euo pipefail` on the version of the code that is in the commit, and the commit will be considered correct if the script exits successfully. You can technically assert that a command fails by writing `! ... || exit 1`, or write assertions about a command's output by piping it to `grep`, but doing so won't lead to very useful error messages when things go wrong. To make those things easier, there are some helper functions included, which are inspired by [bats](https://github.com/bats-core/bats-core) and [bats-assert](https://github.com/bats-core/bats-assert): @@ -77,6 +77,10 @@ You can technically assert that a command fails by writing `! ... || exit 1`, or I'm considering taking `bats-assert` as a dependency, but for now, this very minimal set of functions with a similar interface should get you on your way. +## Multiple blocks in one commit message + +You can include multiple `git-check-assertions` blocks in a single commit message. When you do, each individual block will run with a 'fresh' version of the commit, in which all changes that you made in the previous block have been undone. + ## Cache of successful commits After a commit's assertions run successfully, `git-check-assertions` appends the commit hash to `.git-check-assertions-cache` in the repo root. On the next run, any commit listed there is skipped. diff --git a/bin/git-check-assertions b/bin/git-check-assertions index 24bbfdf..1195884 100755 --- a/bin/git-check-assertions +++ b/bin/git-check-assertions @@ -27,9 +27,12 @@ set -euo pipefail # helper functions inspired by bats/bats-assert run() { set +e - output="$("$@" 2>&1)" - status=$? + tmp_output="$(mktemp)" + "$@" 2>&1 | tee "$tmp_output" + status=${PIPESTATUS[0]} set -e + output="$(cat "$tmp_output")" + rm -f "$tmp_output" printf '%s\n' "$output" return 0 } @@ -106,13 +109,15 @@ for commit_hash in "${commits[@]}"; do echo "Checking out $commit_hash" git -c advice.detachedHead=false checkout -q "$commit_hash" commit_msg="$(git log -1 --format=%B "$commit_hash")" - # shellcheck disable=SC2016 - block="$(printf '%s\n' "$commit_msg" | - sed -n '/^```git-check-assertions[[:space:]]*$/,/^```[[:space:]]*$/p' | - sed '1d;$d')" - if [ -n "$block" ]; then - echo "git-check-assertions block in $commit_hash:" - printf '%s\n' "$block" | sed 's/^/> /' + mapfile -d '' -t blocks < <( + printf '%s\n' "$commit_msg" | + perl -0777 -ne 'while (/^```git-check-assertions\s*\n(.*?)^```\s*$/msg){ print $1, "\0" }' + ) + block_num=0 + for block in "${blocks[@]}"; do + block_num=$((block_num + 1)) + echo "git-check-assertions block $block_num:" + printf '%s' "$block" | sed 's/^/> /' if ! bash -euo pipefail -c "$block"; then echo "git-check-assertions block failed in $commit_hash" >&2 restore @@ -121,7 +126,8 @@ for commit_hash in "${commits[@]}"; do exit 1 fi restore - fi + git -c advice.detachedHead=false checkout -q "$commit_hash" + done printf '%s\n' "$commit_hash" >>.git-check-assertions-cache echo done diff --git a/default.nix b/default.nix index 12dcadf..c260e4c 100644 --- a/default.nix +++ b/default.nix @@ -9,6 +9,8 @@ gitMinimal, resholve, shfmt, + perl, + coreutils, }: let @@ -40,6 +42,8 @@ resholve.mkDerivation { ])) gitMinimal shfmt + perl + coreutils ]; checkPhase = '' @@ -66,6 +70,8 @@ resholve.mkDerivation { gitMinimal gnused bash + perl + coreutils ]; execer = [ # Not true at all, but ¯\_(ツ)_/¯ diff --git a/flake.nix b/flake.nix index e82af69..1fabe89 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,9 @@ p.bats-support p.bats-file ])) + pkgs.perl pkgs.shfmt + pkgs.coreutils ]; }; } diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats index e706f41..be3b275 100755 --- a/test/git-check-assertions.bats +++ b/test/git-check-assertions.bats @@ -124,6 +124,54 @@ commit_with_assertion() { assert_output "feature" } +@test "multiple git-check-assertions blocks in one commit message run in order" { + git checkout -b feature + git commit --allow-empty -F - <<-EOF + test + + \`\`\`git-check-assertions + touch ../test1 + \`\`\` + + \`\`\`git-check-assertions + touch ../test2 + \`\`\` + EOF + + run git-check-assertions + + assert_success + assert_output --partial "git-check-assertions block 1:" + assert_output --partial "git-check-assertions block 2:" + assert_file_exists ../test1 + assert_file_exists ../test2 +} + +@test "should restore the commit between assertion blocks in one commit message" { + git checkout -b feature + echo old >readme + git add readme + git commit -m "old" + + echo new >readme + git add readme + git commit -F - <<-EOF + update + + \`\`\`git-check-assertions + git checkout HEAD~ + \`\`\` + + \`\`\`git-check-assertions + grep new readme + \`\`\` + EOF + + run git-check-assertions + + assert_success +} + @test "should stop at the first failing assertion block and return to the original branch" { git checkout -b feature commit_with_assertion "touch ../test1 && exit 3"