Skip to content

Instantly share code, notes, and snippets.

@Konfekt
Created April 6, 2026 11:44
Show Gist options
  • Select an option

  • Save Konfekt/064e29e0de65680d66acaa47a65f17dc to your computer and use it in GitHub Desktop.

Select an option

Save Konfekt/064e29e0de65680d66acaa47a65f17dc to your computer and use it in GitHub Desktop.
auto-install common dependencies on git worktree creation
#!/usr/bin/env bash
# git worktrees enable working with differing dependencies:
# auto-install common dependencies on worktree creation by this
# post-checkout hook; drop insto post-checkout.d with multihook :
# https://gist.github.com/Konfekt/d9e86763b0f3febd7b2f7ca589f6c482
set -Eeuo pipefail
if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4))); then
shopt -s inherit_errexit
fi
PS4='+\t '
have() { command -v "$1" >/dev/null 2>&1; }
log() { printf '%s\n' "$*"; }
notify=0
if [[ ! -t 2 && -n "${DBUS_SESSION_BUS_ADDRESS:-}" ]] && have notify-send; then
notify=1
fi
error_handler() {
local lineno="$1" cmd="$2" status="$3"
# fallback if error occurs in main body of script
local src="${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}"
local start=$((lineno > 3 ? lineno - 3 : 1))
local end=$((lineno + 3))
local body summary
body="$(
awk -v s="$start" -v e="$end" -v l="$lineno" '
NR < s { next }
NR > e { exit }
{
line = sprintf("%6d\t%s", NR, $0)
if (NR == l) line = ">> " line
print line
}
' "$src"
)"
summary="Error: ${src}:${lineno}: '${cmd}' exited with status ${status}"
printf '%s\n%s\n' "$summary" "$body" >&2
((notify)) && notify-send --urgency=critical "$summary" "$body" || true
exit "$status"
}
trap 'error_handler "$LINENO" "$BASH_COMMAND" "$?"' ERR
# ---------------------------------------------------------------------------
# Ensure a clean Git environment so that commands like `git -C <dir>` actually
# discover the repository from <dir> rather than from inherited GIT_DIR.
# See: https://github.com/git/git/commit/772f8ff826fcb15cba94bfd8f23eb0917f3e9edc
#
# The multihook dispatcher should already have done this, but we do it
# defensively in case this script is invoked directly or by a different runner.
# ---------------------------------------------------------------------------
# shellcheck disable=SC2046
unset $(git rev-parse --local-env-vars 2>/dev/null || true)
copy_item_if_missing() {
local src_root="$1" dst_root="$2" item="$3"
local src="${src_root}/${item}" dst="${dst_root}/${item}"
[[ -e "$src" && ! -e "$dst" ]] || return 0
[[ "$item" == */* ]] && mkdir -p "${dst%/*}"
cp -a -- "$src" "$dst"
}
copy_git_exclude_if_missing() {
local src_root="$1" dst_root="$2"
local src dst
src="$(git -C "$src_root" rev-parse --path-format=absolute --git-path info/exclude)"
dst="$(git -C "$dst_root" rev-parse --path-format=absolute --git-path info/exclude)"
[[ -e "$src" && ! -e "$dst" ]] || return 0
mkdir -p "${dst%/*}"
cp -a "$src" "$dst"
}
copy_important_files_from_main() {
local src_root="$1" dst_root="$2"
local items=(
".envrc"
"mise.local.toml"
".devcontainer.local.json"
"AGENTS.md"
".agents"
".vimrc"
".vscode"
)
if [[ -n "${WORKTREE_COPY_ITEMS:-}" ]]; then
mapfile -t items < <(sed '/^[[:space:]]*$/d' <<<"$WORKTREE_COPY_ITEMS")
fi
for item in "${items[@]}"; do
copy_item_if_missing "$src_root" "$dst_root" "$item"
done
copy_git_exclude_if_missing "$src_root" "$dst_root"
}
run_mise_install() {
[[ "${WORKTREE_RUN_MISE_INSTALL:-1}" == "1" ]] || return 0
have mise || return 0
[[ -f "mise.toml" || -f ".mise.toml" ]] || return 0
log "-> mise install"
mise trust --all --yes --verbose
# mise install --yes --verbose
}
node_install() {
[[ "${WORKTREE_NODE_INSTALL:-1}" == "1" ]] || return 0
[[ -f "package.json" ]] || return 0
if [[ -f "pnpm-lock.yaml" ]] && (have pnpm || have corepack); then
log "-> pnpm install --frozen-lockfile"
if have pnpm; then pnpm install --frozen-lockfile; else corepack pnpm install --frozen-lockfile; fi
return 0
fi
if [[ -f "package-lock.json" || -f "npm-shrinkwrap.json" ]]; then
log "-> npm ci"
npm ci
else
log "-> npm install"
npm install
fi
}
python_scaffold() {
[[ "${WORKTREE_PYTHON_INSTALL:-1}" == "1" ]] || return 0
[[ -f "pyproject.toml" || -f "requirements.txt" || -f "uv.lock" ]] || return 0
local venv="${WORKTREE_VENV_PATH:-.venv}"
if [[ ! -d "$venv" ]]; then
log "-> python3 -m venv ${venv}"
python3 -m venv "$venv"
fi
if have uv && [[ -f "pyproject.toml" ]]; then
log "-> uv sync"
local -a uv_flags=()
if [[ -n "${WORKTREE_UV_FLAGS:-}" ]]; then
read -ra uv_flags <<<"$WORKTREE_UV_FLAGS"
fi
UV_PROJECT_ENVIRONMENT="$venv" uv sync "${uv_flags[@]}"
return 0
fi
if [[ -f "requirements.txt" ]]; then
log "-> pip install -r requirements.txt"
"$venv/bin/python" -m pip install -U pip
"$venv/bin/python" -m pip install -r requirements.txt
return 0
fi
log "-> Python project detected, dependency install skipped"
}
precommit_scaffold() {
[[ "${WORKTREE_PRECOMMIT_INSTALL:-0}" == "1" ]] || return 0
[[ -f ".pre-commit-config.yaml" ]] || return 0
have pre-commit || return 0
log "-> pre-commit install"
pre-commit install
}
is_initial_linked_worktree_checkout() {
local prev="${1:-}" new="${2:-}" mode="${3:-}"
local only_under="${WORKTREE_SCAFFOLD_ONLY_UNDER:-.worktrees}"
[[ "$mode" == "1" ]] || return 1
# Check this is a linked worktree (not the main one)
local git_dir common_dir
git_dir="$(git rev-parse --path-format=absolute --git-dir)"
common_dir="$(git rev-parse --path-format=absolute --git-common-dir)"
[[ "$git_dir" != "$common_dir" ]] || return 1
[[ -z "$only_under" ]] && return 0
local top
top="$(git rev-parse --show-toplevel)"
case "$top" in
*"/$only_under" | *"/$only_under/"*) return 0 ;;
*) return 1 ;;
esac
}
main() {
local top git_dir stamp main_root
local only_under="${WORKTREE_SCAFFOLD_ONLY_UNDER:-.worktrees}"
is_initial_linked_worktree_checkout "$@" || return 0
top="$(git rev-parse --show-toplevel)"
git_dir="$(git rev-parse --path-format=absolute --git-dir)"
stamp="${git_dir}/.scaffolded"
if ! ( set -o noclobber; : >"$stamp" ) 2>/dev/null; then
return 0 # another invocation already claimed it
fi
# If scaffold fails, remove the stamp so it can be retried
trap 'rm -f "$stamp"' EXIT
main_root="$(git -C "$(git rev-parse --path-format=absolute --git-common-dir)/../" \
rev-parse --show-toplevel 2>/dev/null || true)"
log "-> new linked worktree detected at ${top}"
copy_important_files_from_main "$main_root" "$top"
run_mise_install
node_install
python_scaffold
precommit_scaffold
: >"$stamp"
log "-> scaffold complete"
trap - EXIT
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment