# zsh/.zsh/functions/git-helpers.zsh
# Small wrappers I use daily. Nothing here shells out unless necessary.
# gwip: stage everything and amend into the previous WIP commit.
gwip() {
if ! git rev-parse --git-dir >/dev/null 2>&1; then
print -u2 "not a git repo"
return 1
fi
local msg
msg="$(git log -1 --pretty=%s 2>/dev/null || true)"
git add -A
if [[ "$msg" == "wip" ]]; then
git commit --amend --no-edit
else
git commit -m wip
fi
}
# gundo: soft-reset the last commit, keep changes staged.
gundo() {
git reset --soft HEAD~"${1:-1}"
}
# gbc: checkout the branch selected via fzf; defaults to local branches.
gbc() {
local flag="" branch
if [[ "$1" == "-a" ]]; then flag="-a"; fi
branch=$(git branch $flag --color=never \
| sed -E 's/^[* ]+//' \
| grep -v '^HEAD' \
| fzf --height=40% --reverse) || return 1
branch="${branch##remotes/origin/}"
git switch "$branch"
}
# gclean: delete local branches whose remote tracking branch is gone.
gclean() {
git fetch --prune
git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads \
| awk '$2 == "[gone]" {print $1}' \
| while read -r b; do
print "delete: $b"
git branch -D "$b"
done
}
# root: cd to the repo root; useful in pipelines via $(root).
root() {
local top
top=$(git rev-parse --show-toplevel 2>/dev/null) || {
print -u2 "not in a git repo"; return 1
}
cd "$top" || return 1
}
# grp: like grep, but scoped to tracked files.
grp() {
git grep --color=always -n "$@" | less -R
}