Merge pull request 'Add support for multiple git-check-assertions blocks in one commit message' (#16) from multiple-blocks into main

Reviewed-on: https://codeberg.org/svenvanheugten/git-check-assertions/pulls/16
This commit is contained in:
Sven van Heugten 2026-03-11 23:04:29 +01:00
commit ac6000d1ee
5 changed files with 77 additions and 11 deletions

View file

@ -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): 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. 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 ## 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. 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.

View file

@ -27,9 +27,12 @@ set -euo pipefail
# helper functions inspired by bats/bats-assert # helper functions inspired by bats/bats-assert
run() { run() {
set +e set +e
output="$("$@" 2>&1)" tmp_output="$(mktemp)"
status=$? "$@" 2>&1 | tee "$tmp_output"
status=${PIPESTATUS[0]}
set -e set -e
output="$(cat "$tmp_output")"
rm -f "$tmp_output"
printf '%s\n' "$output" printf '%s\n' "$output"
return 0 return 0
} }
@ -106,13 +109,15 @@ for commit_hash in "${commits[@]}"; do
echo "Checking out $commit_hash" echo "Checking out $commit_hash"
git -c advice.detachedHead=false checkout -q "$commit_hash" git -c advice.detachedHead=false checkout -q "$commit_hash"
commit_msg="$(git log -1 --format=%B "$commit_hash")" commit_msg="$(git log -1 --format=%B "$commit_hash")"
# shellcheck disable=SC2016 mapfile -d '' -t blocks < <(
block="$(printf '%s\n' "$commit_msg" | printf '%s\n' "$commit_msg" |
sed -n '/^```git-check-assertions[[:space:]]*$/,/^```[[:space:]]*$/p' | perl -0777 -ne 'while (/^```git-check-assertions\s*\n(.*?)^```\s*$/msg){ print $1, "\0" }'
sed '1d;$d')" )
if [ -n "$block" ]; then block_num=0
echo "git-check-assertions block in $commit_hash:" for block in "${blocks[@]}"; do
printf '%s\n' "$block" | sed 's/^/> /' block_num=$((block_num + 1))
echo "git-check-assertions block $block_num:"
printf '%s' "$block" | sed 's/^/> /'
if ! bash -euo pipefail -c "$block"; then if ! bash -euo pipefail -c "$block"; then
echo "git-check-assertions block failed in $commit_hash" >&2 echo "git-check-assertions block failed in $commit_hash" >&2
restore restore
@ -121,7 +126,8 @@ for commit_hash in "${commits[@]}"; do
exit 1 exit 1
fi fi
restore restore
fi git -c advice.detachedHead=false checkout -q "$commit_hash"
done
printf '%s\n' "$commit_hash" >>.git-check-assertions-cache printf '%s\n' "$commit_hash" >>.git-check-assertions-cache
echo echo
done done

View file

@ -9,6 +9,8 @@
gitMinimal, gitMinimal,
resholve, resholve,
shfmt, shfmt,
perl,
coreutils,
}: }:
let let
@ -40,6 +42,8 @@ resholve.mkDerivation {
])) ]))
gitMinimal gitMinimal
shfmt shfmt
perl
coreutils
]; ];
checkPhase = '' checkPhase = ''
@ -66,6 +70,8 @@ resholve.mkDerivation {
gitMinimal gitMinimal
gnused gnused
bash bash
perl
coreutils
]; ];
execer = [ execer = [
# Not true at all, but ¯\_(ツ)_/¯ # Not true at all, but ¯\_(ツ)_/¯

View file

@ -22,7 +22,9 @@
p.bats-support p.bats-support
p.bats-file p.bats-file
])) ]))
pkgs.perl
pkgs.shfmt pkgs.shfmt
pkgs.coreutils
]; ];
}; };
} }

View file

@ -124,6 +124,54 @@ commit_with_assertion() {
assert_output "feature" 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" { @test "should stop at the first failing assertion block and return to the original branch" {
git checkout -b feature git checkout -b feature
commit_with_assertion "touch ../test1 && exit 3" commit_with_assertion "touch ../test1 && exit 3"