zsh/.zsh/functions/git-helpers.zsh

# 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
}