diff --git a/README.md b/README.md index 0b302ca..f0479cd 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,43 @@ You include a small bash script inside your commit messages, and `git-check-asse ⚠️ Only run this on repositories and branches that you trust, since the `bash` scripts in the commit messages can do whatever they want. +## Installation + +On most systems, clone this repository and add the `bin` directory to your `PATH`. + +If you use Nix with flakes, you can simply add it to your program's devshell instead: + +```diff +@@ -2,12 +2,18 @@ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; ++ git-check-assertions = { ++ url = "git+https://codeberg.org/svenvanheugten/git-check-assertions.git?ref=main"; ++ inputs.nixpkgs.follows = "nixpkgs"; ++ inputs.flake-utils.follows = "flake-utils"; ++ }; + }; + outputs = + { + self, + nixpkgs, + flake-utils, ++ git-check-assertions, + }: + flake-utils.lib.eachDefaultSystem ( + system: +@@ -17,7 +23,10 @@ + { + packages.default = pkgs.callPackage ./default.nix { }; + devShells.default = pkgs.mkShell { +- packages = [ ]; ++ packages = [ git-check-assertions.packages.${system}.default ]; + }; + } + ) +``` + ## Examples of commit messages Assert that a commit builds: diff --git a/git-check-assertions b/bin/git-check-assertions similarity index 90% rename from git-check-assertions rename to bin/git-check-assertions index a023370..f7fe936 100755 --- a/git-check-assertions +++ b/bin/git-check-assertions @@ -25,7 +25,10 @@ set -euo pipefail assert_fails() { - ! "$@" || { echo "Expected command to fail, but it succeeded."; exit 1; } + ! "$@" || { + echo "Expected command to fail, but it succeeded." + exit 1 + } } export -f assert_fails @@ -42,9 +45,9 @@ for commit_hash in "${commits[@]}"; do 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')" + 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/^/> /' diff --git a/default.nix b/default.nix index 4dde1cf..d8055a8 100644 --- a/default.nix +++ b/default.nix @@ -8,6 +8,7 @@ shellcheck-minimal, gitMinimal, resholve, + shfmt, }: let @@ -20,8 +21,9 @@ resholve.mkDerivation { src = fs.toSource { root = ./.; fileset = fs.unions [ - ./git-check-assertions - ./test.bats + ./.editorconfig + ./bin + ./test ]; }; @@ -31,19 +33,20 @@ resholve.mkDerivation { nativeCheckInputs = [ shellcheck-minimal - bats + (bats.withLibraries (p: [ + p.bats-assert + p.bats-support + p.bats-file + ])) gitMinimal + shfmt ]; checkPhase = '' runHook preCheck - shellcheck git-check-assertions test.bats - git init - git config user.email test - git config user.name test - git checkout -b main - git commit --allow-empty -m "initial" - ./test.bats + shellcheck bin/git-check-assertions test/git-check-assertions.bats + shfmt -d bin/git-check-assertions test/git-check-assertions.bats + ./test/git-check-assertions.bats runHook postCheck ''; @@ -52,7 +55,7 @@ resholve.mkDerivation { installPhase = '' runHook preInstall mkdir -p $out/bin - cp $src/git-check-assertions $out/bin/git-check-assertions + cp $src/bin/git-check-assertions $out/bin/git-check-assertions runHook postInstall ''; diff --git a/flake.nix b/flake.nix index 55f8f46..e82af69 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,14 @@ { packages.default = pkgs.callPackage ./default.nix { }; devShells.default = pkgs.mkShell { - packages = with pkgs; [ bats ]; + packages = [ + (pkgs.bats.withLibraries (p: [ + p.bats-assert + p.bats-support + p.bats-file + ])) + pkgs.shfmt + ]; }; } ); diff --git a/test.bats b/test.bats deleted file mode 100755 index 48965fd..0000000 --- a/test.bats +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bats - -setup() { - DIR="$( cd "$( dirname "$BATS_TEST_FILENAME" )" >/dev/null 2>&1 && pwd )" - PATH="$DIR:$PATH" -} - -@test "can run" { - git-check-assertions -} diff --git a/test/git-check-assertions.bats b/test/git-check-assertions.bats new file mode 100755 index 0000000..f58c3ea --- /dev/null +++ b/test/git-check-assertions.bats @@ -0,0 +1,114 @@ +#!/usr/bin/env bats + +setup() { + set -euo pipefail + + bats_load_library bats-support + bats_load_library bats-assert + bats_load_library bats-file + + DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" >/dev/null 2>&1 && pwd)" + PATH="$DIR/../bin:$PATH" + + mkdir "$BATS_TEST_TMPDIR/repo" + cd "$BATS_TEST_TMPDIR/repo" + git init -b main + git config user.email "john.doe@example.com" + git config user.name "John Doe" + touch readme + git add readme + git commit -m "initial" +} + +commit_with_assertion() { + git commit --allow-empty -F - <<-EOF + test + + \`\`\`git-check-assertions + $1 + \`\`\` + EOF +} + +@test "should not run any assertion blocks when on main" { + commit_with_assertion "touch ../test" + + run git-check-assertions + + assert_success + assert_file_not_exists ../test +} + +@test "should not run any assertion blocks from main when on a feature branch" { + commit_with_assertion "touch ../test" + git checkout -b feature + git commit --allow-empty -m "feature" + + run git-check-assertions + + assert_success + assert_file_not_exists ../test +} + +@test "should run all succeeding assertion blocks on the feature branch and finally return to the original branch" { + git checkout -b feature + commit_with_assertion "touch ../test1" + commit_with_assertion "touch ../test2" + + run git-check-assertions + + assert_success + assert_file_exists ../test1 + assert_file_exists ../test2 + run git symbolic-ref --short HEAD + assert_output "feature" +} + +@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" + commit_with_assertion "touch ../test2" + + run git-check-assertions + + assert_failure + assert_file_exists ../test1 + assert_file_not_exists ../test2 + run git symbolic-ref --short HEAD + assert_output "feature" +} + +@test "should restore worktree when finished" { + git checkout -b feature + commit_with_assertion 'echo blah >> readme' + + run git-check-assertions + + assert_success + assert_size_zero readme +} + +@test "should restore worktree between commits" { + git checkout -b feature + commit_with_assertion 'echo blah >> readme' + commit_with_assertion 'cp readme ../test' + + run git-check-assertions + + assert_success + assert_size_zero ../test +} + +@test "assertions should run against the version of the code inside of the commit" { + git checkout -b feature + echo commit1 >readme + git add readme + commit_with_assertion 'grep commit1 readme' + echo commit2 >readme + git add readme + commit_with_assertion 'grep commit2 readme' + + run git-check-assertions + + assert_success +}