diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d45122c..1749a49 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,3 +31,10 @@ jobs: persist-credentials: false - name: Run the tests run: npx --yes bats@1.11.0 tests + # A shell that is not interactive loads no aliases: gitrid must be a + # command on the PATH + - name: Install with setup_linux.sh and check gitrid is on the PATH + run: | + bash setup_linux.sh + bash -c 'command -v gitrid' + [ "$(bash -c 'gitrid --version')" = "$(bash gitrid.sh --version)" ] diff --git a/README.md b/README.md index 2792fd0..fb83116 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ Without `--yes`, and with nothing to answer the prompt, gitrid deletes nothing a To decide for itself what is safe, a script reads `gitrid --list --porcelain` ([above](#listing-branches)): a branch is safe to delete if its status is `merged` or its commits on no remote are `0`. -The setup scripts for Linux, Mac, WSL and Git Bash make `gitrid` an alias in `~/.bashrc`, and a shell that is not interactive does not load aliases. If an agent or script gets "command not found", call the script by its path: `/usr/local/bin/gitrid.sh`, or `~/Scripts/gitrid.sh` in Git Bash. In PowerShell `gitrid` is a script on the PATH and works as it is. +The setup scripts make `gitrid` a command on the PATH, so it works in a shell that is not interactive (a script, a hook, an agent) as it does at the prompt. Versions before 3.1.1 made it an alias in `~/.bashrc`, which such a shell does not load: if an agent or script gets "command not found", run the setup script again. The exit code says what happened: @@ -169,13 +169,11 @@ To install gitrid, simply run the appropriate setup script for your system. The bash setup_linux.sh ``` -If gitrid is already installed, the script replaces it with this version. +This copies the script to `/usr/local/bin/gitrid.sh` and makes `/usr/local/bin/gitrid` a link to it. If gitrid is already installed, the script replaces it with this version. 2. Check it works: ```bash -source ~/.bashrc - gitrid --help ``` @@ -214,13 +212,13 @@ gitrid --help bash setup_gitbash.sh ``` -This creates a Scripts directory in the user's home directory (if one doesn't already exist) and copies the gitrid.sh script into it, makes it executable and adds an alias for easy access. The script will detect existing installations and update them automatically. +This creates a Scripts directory in the user's home directory (if one doesn't already exist), copies the gitrid.sh script into it and makes it executable. It then writes the `gitrid` command to `~/bin/gitrid`, a small script that runs `~/Scripts/gitrid.sh`. Run it again to update an existing installation. + +Git Bash puts `~/bin` on the PATH when the folder exists. If the setup script had to create it, open a new Git Bash window before using `gitrid`. 2. Check it works: ```bash -source ~/.bashrc - gitrid --help ``` diff --git a/gitrid.sh b/gitrid.sh index 4d58241..5089155 100644 --- a/gitrid.sh +++ b/gitrid.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Version information -VERSION="3.1.0" +VERSION="3.1.1" # Exit codes, so a script or an AI agent can tell the outcomes apart EXIT_OK=0 # Branches deleted, or a list, dry run, help or version shown diff --git a/llms.txt b/llms.txt index 1f1f1af..58fcd94 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # gitrid -> A bash script that deletes LOCAL git branches in bulk: by name pattern, by "merged into main", or by "upstream branch gone". Describes gitrid 3.1.0 (`gitrid --version`). This file is the reference for AI agents and scripts; `README.md` is the human guide. `gitrid --help` prints a short version of the same rules. +> A bash script that deletes LOCAL git branches in bulk: by name pattern, by "merged into main", or by "upstream branch gone". Describes gitrid 3.1.1 (`gitrid --version`). This file is the reference for AI agents and scripts; `README.md` is the human guide. `gitrid --help` prints a short version of the same rules. ## What it does and does not do @@ -37,7 +37,7 @@ Without `--yes` gitrid asks "Are you sure? (y/n)" on stdin. With no terminal and | `--yes`, `-y` | With any of the above: delete without asking | | `gitrid --list`, `-l` | List every local branch with its status. Read-only | | `gitrid --list --porcelain` | The same list in a fixed format for a program to read | -| `gitrid --version` | `gitrid version 3.1.0` | +| `gitrid --version` | `gitrid version 3.1.1` | | `gitrid --help`, `-h` | Usage | - Options can come before or after the pattern. An option that is not listed is an error (exit `1`). @@ -115,4 +115,4 @@ gitrid --list --porcelain | awk -F'\t' '$1 != "protected" && ($1 == "merged" || ## Installed copies -On Windows the PowerShell command is `gitrid.ps1`, which runs the script in WSL (`wsl --exec /usr/local/bin/gitrid.sh`) and passes the pattern through unchanged. cmd uses `gitrid.bat`, where a pattern with a `^` must be in double quotes (`gitrid "^fix/"`) or cmd drops the `^`. Git Bash uses an alias to `~/Scripts/gitrid.sh`, and aliases are not loaded in a non-interactive shell, so a script there calls `~/Scripts/gitrid.sh` by path. Linux, macOS and WSL use `/usr/local/bin/gitrid.sh`. The copies are updated separately, by rerunning the setup script for each. +On Windows the PowerShell command is `gitrid.ps1`, which runs the script in WSL (`wsl --exec /usr/local/bin/gitrid.sh`) and passes the pattern through unchanged. cmd uses `gitrid.bat`, where a pattern with a `^` must be in double quotes (`gitrid "^fix/"`) or cmd drops the `^`. In Git Bash `gitrid` is `~/bin/gitrid`, a wrapper that runs `~/Scripts/gitrid.sh`. On Linux, macOS and WSL it is `/usr/local/bin/gitrid`, a link to `/usr/local/bin/gitrid.sh`. Both are commands on the PATH, so `gitrid` works in a non-interactive shell. An installation made by an older setup script has only an alias in `~/.bashrc`, which a non-interactive shell does not load: there, call the script by its path (`~/Scripts/gitrid.sh` or `/usr/local/bin/gitrid.sh`) or rerun the setup script. The copies are updated separately, by rerunning the setup script for each. diff --git a/setup_gitbash.sh b/setup_gitbash.sh index e6b5ddc..fd42438 100644 --- a/setup_gitbash.sh +++ b/setup_gitbash.sh @@ -12,19 +12,31 @@ fi # Define the source script and destination SOURCE_SCRIPT="gitrid.sh" DESTINATION="$HOME/Scripts/gitrid.sh" +# The 'gitrid' command: Git for Windows puts ~/bin on the PATH when it exists +COMMAND="$HOME/bin/gitrid" # Ensure the script is executable chmod +x "$SOURCE_SCRIPT" # Move the script, overwriting if necessary -cp -f "$SOURCE_SCRIPT" "$DESTINATION" - -# Create an alias for easy access -if ! grep -q "alias gitrid" ~/.bashrc; then - echo "alias gitrid='~/Scripts/gitrid.sh'" >> ~/.bashrc - echo "Alias 'gitrid' added to ~/.bashrc" -else - echo "Alias 'gitrid' already exists in ~/.bashrc" -fi +cp -f "$SOURCE_SCRIPT" "$DESTINATION" || exit 1 + +echo "Script successfully installed to $DESTINATION" + +# Make 'gitrid' a command on the PATH. An alias in ~/.bashrc is not enough: a +# shell that is not interactive (a script, a hook, an AI agent) does not load +# aliases. A wrapper, not 'ln -s': Git Bash copies the file where it can not +# make a link, and the copy would go stale when gitrid is updated. +mkdir -p "$HOME/bin" +cat > "$COMMAND" <<'EOF' || exit 1 +#!/usr/bin/env bash +exec "$HOME/Scripts/gitrid.sh" "$@" +EOF +chmod +x "$COMMAND" + +echo "Command 'gitrid' installed to $COMMAND" -echo "Script successfully installed to $DESTINATION" \ No newline at end of file +case ":$PATH:" in + *":$HOME/bin:"*) ;; + *) echo "$HOME/bin is not on the PATH of this shell: open a new Git Bash window to use 'gitrid'" ;; +esac diff --git a/setup_linux.sh b/setup_linux.sh index c79da71..822008c 100644 --- a/setup_linux.sh +++ b/setup_linux.sh @@ -4,6 +4,8 @@ # Define the source script and destination SOURCE_SCRIPT="gitrid.sh" DESTINATION="/usr/local/bin/gitrid.sh" +# The 'gitrid' command, a link to the script +COMMAND="/usr/local/bin/gitrid" # Ensure the script is executable chmod +x "$SOURCE_SCRIPT" @@ -13,15 +15,14 @@ chmod +x "$SOURCE_SCRIPT" if [ -f "$DESTINATION" ]; then sudo rm -f "$DESTINATION" fi -sudo cp "$SOURCE_SCRIPT" "$DESTINATION" +sudo cp "$SOURCE_SCRIPT" "$DESTINATION" || exit 1 echo "Scripts successfully installed to $DESTINATION" -# Create an alias for easy access -if ! grep -q "alias gitrid" ~/.bashrc; then - echo "alias gitrid=$DESTINATION" >> ~/.bashrc - echo "Alias 'gitrid' added to ~/.bashrc" -else - echo "Alias 'gitrid' already exists in ~/.bashrc" -fi \ No newline at end of file +# Make 'gitrid' a command on the PATH. An alias in ~/.bashrc is not enough: a +# shell that is not interactive (a script, a hook, an AI agent) does not load +# aliases. +sudo ln -sf "$DESTINATION" "$COMMAND" || exit 1 + +echo "Command 'gitrid' installed to $COMMAND" diff --git a/tests/setup.bats b/tests/setup.bats new file mode 100644 index 0000000..235a4d0 --- /dev/null +++ b/tests/setup.bats @@ -0,0 +1,70 @@ +#!/usr/bin/env bats + +# Tests for setup_gitbash.sh, which installs into $HOME only, so each test gives +# it a throwaway home. setup_linux.sh writes to /usr/local/bin with sudo and is +# run for real by the workflow in .github/workflows/tests.yml. + +REPO="$BATS_TEST_DIRNAME/.." + +setup() { + export HOME="$BATS_TEST_TMPDIR/home" + mkdir -p "$HOME" + # The setup script takes gitrid.sh from the current directory + cd "$REPO" +} + +# What a new Git Bash window has: ~/bin on the PATH +with_home_bin() { + PATH="$HOME/bin:$PATH" "$@" +} + +@test "setup_gitbash.sh installs the script and the gitrid command" { + run bash setup_gitbash.sh + [ "$status" -eq 0 ] + [ -x "$HOME/Scripts/gitrid.sh" ] + [ -x "$HOME/bin/gitrid" ] + cmp gitrid.sh "$HOME/Scripts/gitrid.sh" +} + +@test "gitrid resolves in a non-interactive shell after setup_gitbash.sh" { + bash setup_gitbash.sh + run with_home_bin bash -c 'command -v gitrid' + [ "$status" -eq 0 ] + [ "$output" = "$HOME/bin/gitrid" ] +} + +@test "the installed gitrid command runs the installed script" { + bash setup_gitbash.sh + run with_home_bin bash -c 'gitrid --version' + [ "$status" -eq 0 ] + [ "$output" = "$(bash gitrid.sh --version)" ] +} + +@test "the installed gitrid command passes its arguments and exit code through" { + bash setup_gitbash.sh + run with_home_bin bash -c 'gitrid --no-such-option' + [ "$status" -eq 1 ] +} + +@test "setup_gitbash.sh does not touch ~/.bashrc" { + echo "# mine" > "$HOME/.bashrc" + bash setup_gitbash.sh + [ "$(cat "$HOME/.bashrc")" = "# mine" ] +} + +@test "setup_gitbash.sh run again updates the installed script" { + bash setup_gitbash.sh + echo "stale" > "$HOME/Scripts/gitrid.sh" + echo "stale" > "$HOME/bin/gitrid" + bash setup_gitbash.sh + cmp gitrid.sh "$HOME/Scripts/gitrid.sh" + run with_home_bin bash -c 'gitrid --version' + [ "$status" -eq 0 ] +} + +@test "setup_gitbash.sh says to open a new window when ~/bin is not on the PATH" { + run bash setup_gitbash.sh + [[ "$output" == *"open a new Git Bash window"* ]] + run with_home_bin bash setup_gitbash.sh + [[ "$output" != *"open a new Git Bash window"* ]] +}