#!/usr/bin/env bash # SessionStart hook: fast-forward current branch to origin default branch. # Non-destructive: fetch always; ff-only merge only when branch has no own # commits (covers stale main AND fresh worktree branched off stale main). # Feature branches with own work are left untouched (just fetched). set -u # Must be inside a work tree. git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0 branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) [ "$branch" = "HEAD" ] && { echo "git-autoupdate: detached HEAD, skip"; exit 0; } # Dirty tree -> fetch only, never move HEAD. if [ -n "$(git status --porcelain 2>/dev/null)" ]; then git fetch --quiet --all --prune 2>/dev/null echo "git-autoupdate: working tree dirty, fetched only (no update)" exit 0 fi git fetch --quiet origin --prune 2>/dev/null || { echo "git-autoupdate: fetch failed"; exit 0; } # Resolve origin default branch (e.g. origin/main). base=$(git symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's|^refs/remotes/||') if [ -z "$base" ]; then git remote set-head origin -a >/dev/null 2>&1 base=$(git symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's|^refs/remotes/||') fi [ -z "$base" ] && base="origin/main" git rev-parse --verify --quiet "$base" >/dev/null 2>&1 || { echo "git-autoupdate: no $base"; exit 0; } ahead=$(git rev-list --count "$base"..HEAD 2>/dev/null) behind=$(git rev-list --count HEAD.."$base" 2>/dev/null) if [ "${ahead:-0}" -gt 0 ]; then echo "git-autoupdate: '$branch' has $ahead own commit(s); fetched, not moved (base $base fresh)" exit 0 fi if [ "${behind:-0}" -eq 0 ]; then echo "git-autoupdate: '$branch' already up to date with $base" exit 0 fi if git merge --ff-only "$base" >/dev/null 2>&1; then echo "git-autoupdate: '$branch' fast-forwarded to $base (+$behind)" else echo "git-autoupdate: '$branch' could not ff to $base" fi exit 0