git-stato 2.0

Kiu ajn estas la unua matene git status tajpi kaj poste trinki kafon amos ĉi tiun malgrandan helpanton. Precipe kiam multaj retprojektoj estas disvolvitaj paralele, unu valoras ĝin kompakta superrigardo: Kie estas la laboranta arbo pura, kie estas nekunfanditaj ŝanĝoj pendantaj, kaj kie atendas tirado/puŝo? Malgranda ŝelilo faros la lertaĵon - kondiĉe ke ĝi fortike pritraktas spacojn/Unikodon en vojoj kaj ne sufokas pro pendigaj teleiloj.


#!/usr/bin/env bash

set -Eeuo pipefail
export LC_ALL=C

# Check if a command exists (no output).
have() { command -v "$1" >/dev/null 2>&1; }

# Ensure we have a `sort` that supports -z (NUL-delimited) input.
SORT_BIN="sort"
if ! "$SORT_BIN" -z </dev/null 2>/dev/null; then
  if have gsort && gsort -z </dev/null 2>/dev/null; then
    SORT_BIN="gsort"
  else
    printf 'Error: This script requires "sort -z" (GNU coreutils). Install coreutils (gsort).\n' >&2
    exit 1
  fi
fi

# Use GNU `timeout` if available; otherwise try `gtimeout` (macOS); otherwise no timeout.
TIMEOUT_BIN="timeout"
if ! have "$TIMEOUT_BIN"; then
  if have gtimeout; then
    TIMEOUT_BIN="gtimeout"
  else
    TIMEOUT_BIN=""
  fi
fi

# Require git.
if ! have git; then
  printf 'Error: "git" not found.\n' >&2
  exit 1
fi

# Remove a leading "./" from a path for cleaner output.
trim_dot_slash() {
  case "$1" in
    ./*) printf '%s\n' "${1#./}" ;;
    *)   printf '%s\n' "$1" ;;
  esac
}

# Legend + divider (as requested)
printf '\n🟢: clean\n🟡: behind/ahead\n🔴: modified\n\n----------------------------------\n\n'

# Find all .git directories, NUL-delimited; sort NUL-delimited; iterate safely.
find . -type d -name .git -print0 \
| "$SORT_BIN" -z \
| while IFS= read -r -d '' gitdir; do
    repo="${gitdir%/.git}"
    display_path="$(trim_dot_slash "$repo")"

    # Skip anything that isn't a proper work tree (safety check).
    if ! git -C "$repo" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
      continue
    fi

    # Working tree status; include untracked files for a strict "red" signal.
    status_out="$(git -C "$repo" status --porcelain=v1 || true)"

    # Upstream divergence check (only if an upstream is configured).
    ahead=0
    behind=0
    if git -C "$repo" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
      # Refresh refs; protect with timeout so a hanging remote doesn't stall the loop.
      if [ -n "$TIMEOUT_BIN" ]; then
        "$TIMEOUT_BIN" 10s git -C "$repo" fetch --all --prune >/dev/null 2>&1 || true
      else
        git -C "$repo" fetch --all --prune >/dev/null 2>&1 || true
      fi
      # Count commits only on our side (ahead) and only on upstream's side (behind).
      ahead="$(git  -C "$repo" rev-list --count --left-only  HEAD...@{u} 2>/dev/null || echo 0)"
      behind="$(git -C "$repo" rev-list --count --right-only HEAD...@{u} 2>/dev/null || echo 0)"
    fi

    # Decide the signal:
    # - RED if the working tree isn't clean
    # - YELLOW if clean but ahead/behind of upstream
    # - GREEN otherwise
    if [ -n "$status_out" ]; then
      printf '🔴 %s\n' "$display_path"
    else
      if [ "${ahead:-0}" -gt 0 ] || [ "${behind:-0}" -gt 0 ]; then
        printf '🟡 %s\n' "$display_path"
      else
        printf '🟢 %s\n' "$display_path"
      fi
    fi
  done

La manuskripto ankoraŭ bezonas esti... chmod +x ~/path/to/script.sh igi ĝin efektivigebla kaj povas agordi kaŝnomon por ŝpari valoran tajpadon: Ĉi tie vi aldonas al lia ~/.bashrc / ~/.zshrc / ~/.bash_profile la eniro alias gscan='bash /path/to/script.sh' krome. De tiam, simpla gscan en la dezirata radika dosierujo.

Vorto pri kial la dua kuro estas rimarkeble pli rapida: Dum la unua kuro, la dosiersistemo ankoraŭ devas skani tra ĉio; post tio estas metadatenoj kaj multaj .git-Strukturoj alteriĝis en la paĝkaŝmemoron de la operaciumo. Referencoj kaj konfirmgrafeoj ankaŭ jam varmiĝis. La sekva fetch Ĝi nun plejparte nur elsendas malgrandajn deltojn. Neniu instrumentpanelo, neniu kromprogramo - rapida statusa momentfoto rekte en la terminalo.

Reen