From a56d764c3efec0553a218ec92c2ff4b99767109a Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Mon, 9 Mar 2026 21:47:31 -0300 Subject: [PATCH 01/10] build(release): add script to publish Homebrew and Scoop pkgs --- scripts/publish-packages.sh | 111 ++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100755 scripts/publish-packages.sh diff --git a/scripts/publish-packages.sh b/scripts/publish-packages.sh new file mode 100755 index 0000000..b587e6b --- /dev/null +++ b/scripts/publish-packages.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +set -euo pipefail + +OWNER="silasvasconcelos" +REPO="simple-git-worktreee" +HOMEBREW_TAP_REPO="$OWNER/homebrew-tap" +SCOOP_BUCKET_REPO="$OWNER/scoop-bucket" + +die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } +info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } +success() { printf '\033[1;32m✔\033[0m %s\n' "$*"; } + +command -v gh >/dev/null 2>&1 || die "gh (GitHub CLI) is required" +command -v curl >/dev/null 2>&1 || die "curl is required" + +VERSION="${1:-}" +[ -z "$VERSION" ] && die "usage: $0 (e.g. 1.0.0)" + +TAG="v$VERSION" + +info "verifying release $TAG exists on ${OWNER}/${REPO}…" +gh release view "$TAG" --repo "$OWNER/$REPO" >/dev/null 2>&1 \ + || die "release $TAG not found — create the GitHub release first" + +# ── Homebrew tap ────────────────────────────────────────────────────── + +info "computing SHA256 for source tarball…" +TARBALL_URL="https://github.com/$OWNER/$REPO/archive/refs/tags/$TAG.tar.gz" +TARBALL_SHA=$(curl -sL "$TARBALL_URL" | shasum -a 256 | awk '{print $1}') +info "sha256: $TARBALL_SHA" + +FORMULA=$(cat </dev/null 2>&1; then + FORMULA_SHA=$(gh api "repos/$HOMEBREW_TAP_REPO/contents/Formula/simple-git-worktree.rb" --jq '.sha') + gh api --method PUT "repos/$HOMEBREW_TAP_REPO/contents/Formula/simple-git-worktree.rb" \ + -f message="simple-git-worktree $TAG" \ + -f content="$(printf '%s' "$FORMULA" | base64)" \ + -f sha="$FORMULA_SHA" \ + --silent +else + gh api --method PUT "repos/$HOMEBREW_TAP_REPO/contents/Formula/simple-git-worktree.rb" \ + -f message="simple-git-worktree $TAG" \ + -f content="$(printf '%s' "$FORMULA" | base64)" \ + --silent +fi + +success "homebrew-tap updated" + +# ── Scoop bucket ────────────────────────────────────────────────────── + +info "computing SHA256 for release zip…" +ZIP_URL="https://github.com/$OWNER/$REPO/releases/download/$TAG/simple-git-worktree-$TAG.zip" +ZIP_SHA=$(curl -sL "$ZIP_URL" | shasum -a 256 | awk '{print $1}') +info "sha256: $ZIP_SHA" + +MANIFEST=$(cat </dev/null 2>&1; then + MANIFEST_SHA=$(gh api "repos/$SCOOP_BUCKET_REPO/contents/simple-git-worktree.json" --jq '.sha') + gh api --method PUT "repos/$SCOOP_BUCKET_REPO/contents/simple-git-worktree.json" \ + -f message="simple-git-worktree $TAG" \ + -f content="$(printf '%s' "$MANIFEST" | base64)" \ + -f sha="$MANIFEST_SHA" \ + --silent +else + gh api --method PUT "repos/$SCOOP_BUCKET_REPO/contents/simple-git-worktree.json" \ + -f message="simple-git-worktree $TAG" \ + -f content="$(printf '%s' "$MANIFEST" | base64)" \ + --silent +fi + +success "scoop-bucket updated" + +echo "" +success "all packages published for $TAG" From 418fb35a38d8c9873edbe4cbaf4467d02fc78084 Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Tue, 7 Apr 2026 10:04:01 -0300 Subject: [PATCH 02/10] feat(wt): add hooks, root command, add --go, and v1.1.0 --- bin/git-wt | 151 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 133 insertions(+), 18 deletions(-) diff --git a/bin/git-wt b/bin/git-wt index 7b90a7f..0e90cf3 100755 --- a/bin/git-wt +++ b/bin/git-wt @@ -1,14 +1,15 @@ #!/usr/bin/env bash set -euo pipefail -VERSION="1.0.0" +VERSION="1.1.0" WORKTREE_DIR=".worktrees" +HOOKS_CONFIG=".git-wtrc" # ── Helpers ────────────────────────────────────────────────────────── -die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } -info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } -success() { printf '\033[1;32m✔\033[0m %s\n' "$*"; } +die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; } +info() { printf '\033[1;34m==>\033[0m %s\n' "$*" >&2; } +success() { printf '\033[1;32m✔\033[0m %s\n' "$*" >&2; } ensure_git_repo() { git rev-parse --git-dir >/dev/null 2>&1 || die "not a git repository" @@ -50,6 +51,35 @@ ensure_worktree_dir() { fi } +project_root() { + local common_dir + common_dir="$(cd "$(git rev-parse --git-common-dir)" && pwd)" + printf '%s\n' "${common_dir%/.git}" +} + +run_hook() { + local hook_name="$1" + shift + local root + root="$(repo_root)" + local config="$root/$HOOKS_CONFIG" + + [ -f "$config" ] || return 0 + + local cmd + cmd="$(sed -n "s/^[[:space:]]*${hook_name}[[:space:]]*=[[:space:]]*//p" "$config" | head -1)" + + [ -z "$cmd" ] && return 0 + + info "running hook '$hook_name'…" + ( export "$@" && cd "$root" && eval "$cmd" ) || { + case "$hook_name" in + pre-*) die "hook '$hook_name' failed — aborting" ;; + post-*) info "hook '$hook_name' failed (non-fatal)" ;; + esac + } +} + # ── Per-command help ────────────────────────────────────────────────── wants_help() { @@ -61,17 +91,21 @@ wants_help() { usage_add() { cat < [base] +Usage: git wt add [base] [--go] Create a new worktree in $WORKTREE_DIR/. Arguments: branch Name of the branch to create - base Base branch (defaults to main/master) + base Base branch (defaults to current branch) + +Options: + --go Print the worktree path to stdout (for cd integration) Examples: git wt add feature-login git wt add feature-login develop + cd "\$(git wt add feature-login --go)" EOF } @@ -132,20 +166,43 @@ Examples: EOF } +usage_root() { + cat </dev/null || info "fetch skipped (no remote 'origin')" @@ -165,6 +228,16 @@ cmd_add() { || die "failed to create worktree — does branch or base exist?" success "worktree created at $WORKTREE_DIR/$branch" + + run_hook "post-add" \ + GIT_WT_BRANCH="$branch" \ + GIT_WT_PATH="$target" \ + GIT_WT_BASE="$base" \ + GIT_WT_ROOT="$root" + + if $go_flag; then + printf '%s\n' "$target" + fi } cmd_list() { @@ -187,9 +260,33 @@ cmd_remove() { [ ! -d "$target" ] && die "worktree '$branch' not found at $WORKTREE_DIR/$branch" + run_hook "pre-remove" \ + GIT_WT_BRANCH="$branch" \ + GIT_WT_PATH="$target" \ + GIT_WT_ROOT="$root" + info "removing worktree '$branch'…" git worktree remove "$target" --force success "worktree '$branch' removed" + + run_hook "post-remove" \ + GIT_WT_BRANCH="$branch" \ + GIT_WT_PATH="$target" \ + GIT_WT_ROOT="$root" + + if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then + printf '\033[1;33m?\033[0m delete local branch '\''%s'\'' as well? [y/N] ' "$branch" >&2 + read -r answer [args] COMMANDS - add [base] Create a worktree in $WORKTREE_DIR/ - Base defaults to default branch (main/master) - list List all worktrees - remove Remove a worktree - prune Remove stale worktree references - path Print the absolute path of a worktree - help Show this help message - version Show version + add [base] [--go] Create a worktree in $WORKTREE_DIR/ + Base defaults to current branch + list List all worktrees + remove Remove a worktree + prune Remove stale worktree references + path Print the absolute path of a worktree + root Print the main repository root path + help Show this help message + version Show version + +HOOKS + Create a $HOOKS_CONFIG file in the repo root to run commands + on worktree events. Available hooks: + pre-add, post-add, pre-remove, post-remove + + Each hook receives: GIT_WT_BRANCH, GIT_WT_PATH, GIT_WT_ROOT + Add hooks also receive: GIT_WT_BASE EXAMPLES git wt add feature-login git wt add feature-login develop + cd "\$(git wt add feature-login --go)" git wt list git wt remove feature-login git wt prune git wt path feature-login + cd "\$(git wt root)" EOF } @@ -260,6 +374,7 @@ main() { remove|rm) cmd_remove "$@" ;; prune) cmd_prune "$@" ;; path) cmd_path "$@" ;; + root) cmd_root "$@" ;; version|--version|-v) cmd_version ;; help|--help|-h) cmd_help ;; *) die "unknown command '$cmd' — run 'git wt help'" ;; From fc84eabc25da239c86bc44b628088274eb9846ea Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Tue, 7 Apr 2026 10:04:06 -0300 Subject: [PATCH 03/10] docs(readme): document hooks, root, --go, and workflow --- README.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index da4aff6..fe2e9c8 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,12 @@ my-project/ | Command | Description | |---|---| -| `git wt add [base]` | Create a worktree. Base defaults to `main` | +| `git wt add [base] [--go]` | Create a worktree from current branch. `--go` outputs path for `cd` | | `git wt list` | List all worktrees | | `git wt remove ` | Remove a worktree | | `git wt prune` | Clean up stale worktree references | | `git wt path ` | Print the absolute path of a worktree | +| `git wt root` | Print the main repository root path | | `git wt help` | Show help | | `git wt version` | Show version | @@ -100,17 +101,34 @@ cd my-project git wt add feature-login # ==> created .worktrees/ # ==> fetching from origin… -# ==> creating worktree for 'feature-login' based on 'main'… +# ==> creating worktree for 'feature-login' based on 'develop'… # ✔ worktree created at .worktrees/feature-login cd .worktrees/feature-login # You now have a full checkout — run tests, edit code, etc. ``` -### Branch from develop instead of main +### Create and jump into the worktree with `--go` ```bash -git wt add feature-payment develop +cd "$(git wt add feature-login --go)" +# ==> creating worktree for 'feature-login' based on 'develop'… +# ✔ worktree created at .worktrees/feature-login +# You're now inside .worktrees/feature-login +``` + +> **Tip:** Add a shell function to your `.bashrc` / `.zshrc` for even less typing: +> +> ```bash +> gwt() { cd "$(git wt add "$@" --go)"; } +> ``` +> +> Then just: `gwt feature-login` + +### Branch from a specific base instead of the current branch + +```bash +git wt add feature-payment main ``` ### See all active worktrees @@ -135,6 +153,15 @@ git wt remove feature-login # ✔ worktree 'feature-login' removed ``` +### Go back to the project root + +```bash +# From any worktree, return to the main project directory +cd "$(git wt root)" +``` + +This works from anywhere — inside a worktree, a subdirectory, or the main repo itself. It always resolves to the original project root. + ### Prune orphaned references ```bash @@ -143,20 +170,99 @@ git wt prune --- +## Hooks + +You can run custom commands before and after worktree operations by creating a `.git-wtrc` file in your repository root. + +### Configuration + +Create a `.git-wtrc` file with `key = command` pairs: + +```ini +# .git-wtrc — hook configuration for git-wt + +# Runs before creating a worktree +pre-add = echo "Setting up $GIT_WT_BRANCH…" + +# Runs after creating a worktree (e.g. install dependencies) +post-add = cd $GIT_WT_PATH && npm install + +# Runs before removing a worktree +pre-remove = echo "Cleaning up $GIT_WT_BRANCH…" + +# Runs after removing a worktree +post-remove = echo "Done removing $GIT_WT_BRANCH" +``` + +### Available hooks + +| Hook | Trigger | +|---|---| +| `pre-add` | Before worktree creation | +| `post-add` | After worktree creation | +| `pre-remove` | Before worktree removal | +| `post-remove` | After worktree removal | + +### Environment variables + +Each hook receives these environment variables: + +| Variable | Description | Available in | +|---|---|---| +| `GIT_WT_BRANCH` | Branch name | All hooks | +| `GIT_WT_PATH` | Absolute path of the worktree | All hooks | +| `GIT_WT_ROOT` | Repository root path | All hooks | +| `GIT_WT_BASE` | Base branch name | `pre-add`, `post-add` | + +### Hook examples + +**Install dependencies after creating a worktree:** + +```ini +post-add = cd $GIT_WT_PATH && npm install +``` + +**Run a Python setup script:** + +```ini +post-add = python3 ./scripts/setup-worktree.py +``` + +**Use a Ruby script for cleanup:** + +```ini +post-remove = ruby ./scripts/cleanup.rb +``` + +**Backup before removal:** + +```ini +pre-remove = tar czf "/tmp/${GIT_WT_BRANCH}-backup.tar.gz" "$GIT_WT_PATH" +``` + +**Chain multiple commands:** + +```ini +post-add = cd $GIT_WT_PATH && cp ../.env.example .env && npm install && npm run db:migrate +``` + +> **Note:** A failing `pre-*` hook aborts the operation. A failing `post-*` hook logs a warning but does not roll back. + +--- + ## Real-world workflow ```bash -# 1. Start a hotfix while your feature branch is still in progress -git wt add hotfix-auth +# 1. Start a hotfix — jump straight into it with --go +cd "$(git wt add hotfix-auth --go)" # 2. Fix the bug in the hotfix worktree -cd .worktrees/hotfix-auth vim src/auth.js git add -A && git commit -m "fix: auth token expiry" git push origin hotfix-auth -# 3. Go back to main worktree — your feature branch is untouched -cd ../.. +# 3. Go back to main project root — your feature branch is untouched +cd "$(git wt root)" # 4. Clean up after the hotfix is merged git wt remove hotfix-auth From d2c870b5019132c6bf337683480b91c7f3d2d550 Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Tue, 7 Apr 2026 10:04:07 -0300 Subject: [PATCH 04/10] docs(changelog): add v1.1.0 release notes --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dde4e4..1551923 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0] - 2026-03-09 + +### Added + +- `--go` flag for `git wt add` — prints the worktree path to stdout for `cd` integration (`cd "$(git wt add branch --go)"`). +- `git wt root` command — prints the main repository root path, works from any worktree. +- Hook system via `.git-wtrc` config file — supports `pre-add`, `post-add`, `pre-remove`, and `post-remove` hooks with environment variables (`GIT_WT_BRANCH`, `GIT_WT_PATH`, `GIT_WT_ROOT`, `GIT_WT_BASE`). Hooks can be any executable: shell, Python, Ruby, etc. + +### Changed + +- Status messages (`info`, `success`) now print to stderr, keeping stdout clean for machine-readable output. + ## [1.0.0] - 2026-03-09 ### Added @@ -24,4 +36,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CI workflow with ShellCheck linting and smoke tests on Ubuntu and macOS. - Release workflow with automated GitHub Releases, Homebrew tap updates, and Scoop bucket updates. +[1.1.0]: https://github.com/silasvasconcelos/simple-git-worktreee/releases/tag/v1.1.0 [1.0.0]: https://github.com/silasvasconcelos/simple-git-worktreee/releases/tag/v1.0.0 From 3a1e77934b3ad48387a2577994058ff7494be267 Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Tue, 7 Apr 2026 10:11:10 -0300 Subject: [PATCH 05/10] fix(wt): run hooks via env for shellcheck compliance --- bin/git-wt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/git-wt b/bin/git-wt index 0e90cf3..4a44900 100755 --- a/bin/git-wt +++ b/bin/git-wt @@ -72,7 +72,10 @@ run_hook() { [ -z "$cmd" ] && return 0 info "running hook '$hook_name'…" - ( export "$@" && cd "$root" && eval "$cmd" ) || { + ( + cd "$root" || exit 1 + env "$@" bash -c "eval \"\$1\"" _ "$cmd" + ) || { case "$hook_name" in pre-*) die "hook '$hook_name' failed — aborting" ;; post-*) info "hook '$hook_name' failed (non-fatal)" ;; From 47e9ebe1e3856d3bd7721446412c4e9e01d14385 Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Wed, 8 Apr 2026 09:49:18 -0300 Subject: [PATCH 06/10] fix(cli): skip local branch delete prompt when not interactive --- bin/git-wt | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/bin/git-wt b/bin/git-wt index 4a44900..2dd4496 100755 --- a/bin/git-wt +++ b/bin/git-wt @@ -278,17 +278,21 @@ cmd_remove() { GIT_WT_ROOT="$root" if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then - printf '\033[1;33m?\033[0m delete local branch '\''%s'\'' as well? [y/N] ' "$branch" >&2 - read -r answer /dev/null; then + printf '\033[1;33m?\033[0m delete local branch '\''%s'\'' as well? [y/N] ' "$branch" >&2 + read -r answer Date: Wed, 8 Apr 2026 18:42:59 -0300 Subject: [PATCH 07/10] feat(wt): add --delete-branch and fix remove without tty --- bin/git-wt | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/bin/git-wt b/bin/git-wt index 2dd4496..4b80bc9 100755 --- a/bin/git-wt +++ b/bin/git-wt @@ -114,18 +114,23 @@ EOF usage_remove() { cat < +Usage: git wt remove [--delete-branch] Remove an existing worktree. Arguments: branch Name of the worktree branch to remove +Options: + --delete-branch, -db Also delete the local branch (skip prompt) + Aliases: rm Examples: git wt remove feature-login git wt rm feature-login + git wt remove feature-login --delete-branch + git wt rm feature-login -db EOF } @@ -250,9 +255,18 @@ cmd_list() { } cmd_remove() { - wants_help "$@" && { usage_remove; return 0; } + local delete_branch=false + local positional=() - local branch="${1:-}" + for arg in "$@"; do + case "$arg" in + --help|-h) usage_remove; return 0 ;; + --delete-branch|-db) delete_branch=true ;; + *) positional+=("$arg") ;; + esac + done + + local branch="${positional[0]:-}" [ -z "$branch" ] && { usage_remove; exit 1; } ensure_git_repo @@ -278,7 +292,10 @@ cmd_remove() { GIT_WT_ROOT="$root" if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then - if [ -t 0 ] || [ -c /dev/tty ] 2>/dev/null; then + if $delete_branch; then + git branch -D "$branch" + success "branch '$branch' deleted" + elif { true /dev/null; then printf '\033[1;33m?\033[0m delete local branch '\''%s'\'' as well? [y/N] ' "$branch" >&2 read -r answer [base] [--go] Create a worktree in $WORKTREE_DIR/ Base defaults to current branch list List all worktrees - remove Remove a worktree + remove [-db] Remove a worktree (-db deletes branch too) prune Remove stale worktree references path Print the absolute path of a worktree root Print the main repository root path From 59da582ff5642fae29ab36b97a5f9965d63ca7c5 Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Wed, 8 Apr 2026 18:43:02 -0300 Subject: [PATCH 08/10] docs(changelog): document remove flag and non-interactive fix --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1551923..ceabba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `--go` flag for `git wt add` — prints the worktree path to stdout for `cd` integration (`cd "$(git wt add branch --go)"`). - `git wt root` command — prints the main repository root path, works from any worktree. - Hook system via `.git-wtrc` config file — supports `pre-add`, `post-add`, `pre-remove`, and `post-remove` hooks with environment variables (`GIT_WT_BRANCH`, `GIT_WT_PATH`, `GIT_WT_ROOT`, `GIT_WT_BASE`). Hooks can be any executable: shell, Python, Ruby, etc. +- `--delete-branch` (`-db`) flag for `git wt remove` — automatically deletes the local branch without prompting (`git wt rm feature -db`). ### Changed - Status messages (`info`, `success`) now print to stderr, keeping stdout clean for machine-readable output. +### Fixed + +- `git wt remove` no longer fails in non-interactive environments (CI/CD) when `/dev/tty` is unavailable — the branch delete prompt is now safely skipped with an informational message. + ## [1.0.0] - 2026-03-09 ### Added From f701f70d8d827363e1e9c80fd489012bd5289ac5 Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Wed, 8 Apr 2026 18:43:04 -0300 Subject: [PATCH 09/10] ci(workflow): use --delete-branch in smoke test cleanup --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4aefd90..c208f60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,5 +52,5 @@ jobs: git-wt add test-branch git-wt list | grep -q "test-branch" git-wt path test-branch - git-wt remove test-branch + git-wt remove test-branch --delete-branch git-wt prune From 4c07f33b5b2d22aed1f1ba5795a9300dab2db999 Mon Sep 17 00:00:00 2001 From: Silas Vasconcelos Date: Wed, 8 Apr 2026 18:43:05 -0300 Subject: [PATCH 10/10] chore(make): add Makefile for shellcheck and smoke tests --- Makefile | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ab22a97 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +SHELL := /bin/bash +BINARY := bin/git-wt +SCRIPTS := $(BINARY) install.sh + +.PHONY: help lint test ci install clean + +help: ## Show available targets + @grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}' + +lint: ## Run ShellCheck on all shell scripts + @command -v shellcheck >/dev/null 2>&1 || { echo "shellcheck not found — brew install shellcheck"; exit 1; } + shellcheck $(SCRIPTS) + @printf '\033[1;32m✔\033[0m shellcheck passed\n' + +test: ## Run smoke tests (same as CI) + @chmod +x $(BINARY) + @printf '\n\033[1;34m==>\033[0m Verify help output\n' + $(BINARY) help + @printf '\n\033[1;34m==>\033[0m Verify version output\n' + $(BINARY) version | grep -q "git-wt" + @printf '\033[1;32m✔\033[0m version OK\n' + @printf '\n\033[1;34m==>\033[0m Test add/list/path/remove/prune in a temp repo\n' + @bash -c '\ + set -euo pipefail; \ + TMPDIR=$$(mktemp -d); \ + trap "rm -rf $$TMPDIR" EXIT; \ + cd "$$TMPDIR"; \ + git init -b main -q; \ + git config user.name "test"; \ + git config user.email "test@test"; \ + git commit --allow-empty -m "init" -q; \ + export PATH="$(CURDIR)/bin:$$PATH"; \ + git-wt add test-branch; \ + git-wt list | grep -q "test-branch"; \ + printf "\033[1;32m✔\033[0m add + list OK\n"; \ + git-wt path test-branch >/dev/null; \ + printf "\033[1;32m✔\033[0m path OK\n"; \ + git-wt remove test-branch; \ + printf "\033[1;32m✔\033[0m remove OK\n"; \ + git-wt add test-branch-db; \ + git-wt remove test-branch-db --delete-branch; \ + git branch --list test-branch-db | grep -q "test-branch-db" && exit 1 || true; \ + printf "\033[1;32m✔\033[0m remove --delete-branch OK\n"; \ + git-wt prune; \ + printf "\033[1;32m✔\033[0m prune OK\n"; \ + ' + @printf '\n\033[1;32m✔ All tests passed\033[0m\n' + +ci: lint test ## Run full CI pipeline (lint + test) + +install: ## Install git-wt to /usr/local/bin + bash install.sh + +clean: ## Remove build artifacts + rm -rf dist/