من هو الأول في الصباح git status الكتابة ثم شرب القهوة سوف تحب هذا المساعد الصغير. خاصة عندما يتم تطوير العديد من مشاريع الويب بالتوازي، فإن أحدها يستحق ذلك نظرة عامة مدمجة: أين تكون شجرة العمل نظيفة، وأين يتم تعليق التغييرات غير المدمجة، وأين يتم انتظار السحب/الدفع؟ ستؤدي أداة الصدفة الصغيرة المهمة - طالما أنها تتعامل بقوة مع المسافات/Unicode في المسارات ولا تختنق بأجهزة التحكم عن بعد المعلقة.
#!/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
لا يزال النص بحاجة إلى... chmod +x ~/path/to/script.sh جعله قابلاً للتنفيذ ويمكنه إعداد اسم مستعار لحفظ الكتابة القيمة: هنا يمكنك الإضافة إلى ~/.bashrc / ~/.zshrc / ~/.bash_profile المدخل alias gscan='bash /path/to/script.sh' بالإضافة إلى ذلك. ومنذ ذلك الحين، أصبح الأمر بسيطًا gscan في الدليل الجذر المطلوب.
كلمة حول سبب كون التشغيل الثاني أسرع بشكل ملحوظ: أثناء التشغيل الأول، لا يزال يتعين على نظام الملفات فحص كل شيء؛ بعد ذلك البيانات الوصفية والعديد .gitتم تخزين الهياكل في ذاكرة التخزين المؤقت للصفحات في نظام التشغيل. كما تم تهيئة المراجع ومخططات الالتزام. التالي fetch الآن، لا ينقل النظام سوى بيانات صغيرة في الغالب. لا لوحة معلومات، ولا تكاليف إضافية - لقطة سريعة للحالة مباشرةً في الجهاز.