From 1a10b24b061275ff56407e598c3fce1449a2154f Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:22:34 +0100 Subject: [PATCH 1/8] Introduce assert_success --- bin/git-check-assertions | 17 +++++++++++++++++ test/git-check-assertions.bats | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/bin/git-check-assertions b/bin/git-check-assertions index f7fe936..60de125 100755 --- a/bin/git-check-assertions +++ b/bin/git-check-assertions @@ -32,6 +32,23 @@ assert_fails() { } export -f assert_fails +# helper functions inspired by bats/bats-assert +run() { + set +e + ("$@") + status=$? + set -e + return 0 +} +assert_success() { + if [ "$status" -ne 0 ]; then + echo "Expected command to succeed, but it failed." + exit 1 + fi +} +export -f run assert_success + +# main flow orig_ref="$(git symbolic-ref --quiet --short HEAD || git rev-parse HEAD)" base="$(git merge-base main HEAD)" mapfile -t commits < <(git rev-list --reverse "${base}..HEAD") diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats index f58c3ea..271d0d0 100755 --- a/test/git-check-assertions.bats +++ b/test/git-check-assertions.bats @@ -112,3 +112,22 @@ commit_with_assertion() { assert_success } + +@test "assert_success should succeed if the executed command succeeded" { + git checkout -b feature + commit_with_assertion $'run exit 0\nassert_success' + + run git-check-assertions + + assert_success +} + +@test "assert_success should fail if the executed command failed" { + git checkout -b feature + commit_with_assertion $'run exit 1\nassert_success' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected command to succeed, but it failed." +} From f9d66966871685cd88eb08642a981fb87fc20d5f Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:23:39 +0100 Subject: [PATCH 2/8] Introduce assert_failure --- bin/git-check-assertions | 8 +++++++- test/git-check-assertions.bats | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bin/git-check-assertions b/bin/git-check-assertions index 60de125..cdc14e8 100755 --- a/bin/git-check-assertions +++ b/bin/git-check-assertions @@ -46,7 +46,13 @@ assert_success() { exit 1 fi } -export -f run assert_success +assert_failure() { + if [ "$status" -eq 0 ]; then + echo "Expected command to fail, but it succeeded." + exit 1 + fi +} +export -f run assert_success assert_failure # main flow orig_ref="$(git symbolic-ref --quiet --short HEAD || git rev-parse HEAD)" diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats index 271d0d0..064341f 100755 --- a/test/git-check-assertions.bats +++ b/test/git-check-assertions.bats @@ -131,3 +131,22 @@ commit_with_assertion() { assert_failure assert_output --partial "Expected command to succeed, but it failed." } + +@test "assert_failure should succeed if the executed command failed" { + git checkout -b feature + commit_with_assertion $'run exit 1\nassert_failure' + + run git-check-assertions + + assert_success +} + +@test "assert_failure should fail if the executed command succeeded" { + git checkout -b feature + commit_with_assertion $'run exit 0\nassert_failure' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected command to fail, but it succeeded." +} From b57d527d6e68cee412f4f74ebb34092f350c9ae5 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:26:07 +0100 Subject: [PATCH 3/8] Introduce assert_output --- bin/git-check-assertions | 11 +++++++++-- test/git-check-assertions.bats | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/bin/git-check-assertions b/bin/git-check-assertions index cdc14e8..ea221fc 100755 --- a/bin/git-check-assertions +++ b/bin/git-check-assertions @@ -35,7 +35,7 @@ export -f assert_fails # helper functions inspired by bats/bats-assert run() { set +e - ("$@") + output="$("$@" 2>&1)" status=$? set -e return 0 @@ -52,7 +52,14 @@ assert_failure() { exit 1 fi } -export -f run assert_success assert_failure +assert_output() { + if [ "$output" != "$1" ]; then + echo "Expected output to equal: $1" + echo "Actual output: $output" + exit 1 + fi +} +export -f run assert_success assert_failure assert_output # main flow orig_ref="$(git symbolic-ref --quiet --short HEAD || git rev-parse HEAD)" diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats index 064341f..11f23e7 100755 --- a/test/git-check-assertions.bats +++ b/test/git-check-assertions.bats @@ -150,3 +150,23 @@ commit_with_assertion() { assert_failure assert_output --partial "Expected command to fail, but it succeeded." } + +@test "assert_output should succeed if the output matches the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output hello' + + run git-check-assertions + + assert_success +} + +@test "assert_output should fail if the output does not match the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output goodbye' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected output to equal: goodbye" + assert_output --partial "Actual output: hello" +} From 475b0c6fa15107d3eb7877130cf5013bdd45290b Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:28:17 +0100 Subject: [PATCH 4/8] Verify that stderr is taken into account --- test/git-check-assertions.bats | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats index 11f23e7..a6a2bba 100755 --- a/test/git-check-assertions.bats +++ b/test/git-check-assertions.bats @@ -170,3 +170,12 @@ commit_with_assertion() { assert_output --partial "Expected output to equal: goodbye" assert_output --partial "Actual output: hello" } + +@test "assert_output should also check stderr output" { + git checkout -b feature + commit_with_assertion $'run sh -c "echo err 1>&2"\nassert_output err' + + run git-check-assertions + + assert_success +} From 3036d6d37e378d7010c89f740373ce517246983e Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:29:39 +0100 Subject: [PATCH 5/8] Introduce --partial on assert_output --- bin/git-check-assertions | 9 +++++++++ test/git-check-assertions.bats | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/bin/git-check-assertions b/bin/git-check-assertions index ea221fc..9e85abf 100755 --- a/bin/git-check-assertions +++ b/bin/git-check-assertions @@ -53,6 +53,15 @@ assert_failure() { fi } assert_output() { + if [ "${1:-}" = "--partial" ]; then + local expected="$2" + if [[ "$output" != *"$expected"* ]]; then + echo "Expected output to contain: $expected" + echo "Actual output: $output" + exit 1 + fi + return 0 + fi if [ "$output" != "$1" ]; then echo "Expected output to equal: $1" echo "Actual output: $output" diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats index a6a2bba..697d47d 100755 --- a/test/git-check-assertions.bats +++ b/test/git-check-assertions.bats @@ -179,3 +179,23 @@ commit_with_assertion() { assert_success } + +@test "assert_output --partial should succeed if output contains the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output --partial ell' + + run git-check-assertions + + assert_success +} + +@test "assert_output --partial should fail if output does not contain the given string" { + git checkout -b feature + commit_with_assertion $'run echo hello\nassert_output --partial xyz' + + run git-check-assertions + + assert_failure + assert_output --partial "Expected output to contain: xyz" + assert_output --partial "Actual output: hello" +} From 523df74b72bddb7cca759ab39f75209a0971e1e3 Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:32:14 +0100 Subject: [PATCH 6/8] Use the new functions in the README examples --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 259c308..930e627 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,13 @@ dotnet test --no-build ``` ~~~ -Assert that a commit builds, but that the tests do not succeed (`assert_fails` is a helper function included in `git-check-assertions`): +Assert that a commit builds, but that the tests do not succeed: ~~~ ```git-check-assertions dotnet build -assert_fails dotnet test --no-build +run dotnet test --no-build +assert_failure ``` ~~~ @@ -83,7 +84,9 @@ Assert that a commit builds, and that the tests fail with exactly the error that ~~~ ```git-check-assertions dotnet build -(assert_fails dotnet test --no-build) | grep "Invalid URL" +run dotnet test --no-build +assert_failure +assert_output --partial "Invalid URL" ``` ~~~ @@ -94,6 +97,7 @@ Assert that a commit builds, and that a specific change breaks the tests (as dis dotnet test sed -i '/crucial code/d' Main.fs dotnet build -assert_fails dotnet test --no-build +run dotnet test --no-build +assert_failure ``` ~~~ From d244a706f3718515f45f41884a05852fa3ed2a3e Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:32:22 +0100 Subject: [PATCH 7/8] Remove old assert_fails --- bin/git-check-assertions | 8 -------- 1 file changed, 8 deletions(-) diff --git a/bin/git-check-assertions b/bin/git-check-assertions index 9e85abf..5501358 100755 --- a/bin/git-check-assertions +++ b/bin/git-check-assertions @@ -24,14 +24,6 @@ set -euo pipefail -assert_fails() { - ! "$@" || { - echo "Expected command to fail, but it succeeded." - exit 1 - } -} -export -f assert_fails - # helper functions inspired by bats/bats-assert run() { set +e From 4eadcbe437859d50e17b0c95de4c04dc26e12b0c Mon Sep 17 00:00:00 2001 From: Sven van Heugten Date: Tue, 3 Mar 2026 20:54:42 +0100 Subject: [PATCH 8/8] Document the helper functions --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 930e627..2d8c3f8 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,29 @@ If you use Nix with flakes, you can simply add it to your program's devshell ins ) ``` +## What do you put in your commit messages? + +Simply add a bash script enclosed in a `git-check-assertions` block to a commit message, e.g.: + +~~~ +```git-check-assertions +dotnet build +dotnet test --no-build +``` +~~~ + +This script will be run with `set -euo pipefail`, and the commit will be considered correct if the script exits succesfully. + +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): + +* `run `: run a command, capturing its exit status in `status` and combined stdout/stderr in `output`. +* `assert_success`: succeed if `run` produced a zero exit status. +* `assert_failure`: succeed if `run` produced a non-zero exit status. +* `assert_output `: succeed if `output` exactly matches the string. +* `assert_output --partial `: succeed if `output` contains the string. + +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. + ## Examples of commit messages Assert that a commit builds: @@ -65,7 +88,8 @@ Assert that a commit builds and that the tests succeed: ~~~ ```git-check-assertions dotnet build -dotnet test --no-build +run dotnet test --no-build +assert_success ``` ~~~