249 lines
7.4 KiB
Bash
249 lines
7.4 KiB
Bash
#!/bin/sh
|
|
|
|
###################################################################################
|
|
# 此脚本用于检测 git 状态
|
|
# Copy from https://github.com/fboender/multi-git-status
|
|
###################################################################################
|
|
|
|
# MIT license
|
|
|
|
if [ -t 1 ]; then
|
|
# Our output is not being redirected, so we can use colors.
|
|
C_RED="\033[1;31m"
|
|
C_GREEN="\033[1;32m"
|
|
C_YELLOW="\033[1;33m"
|
|
C_BLUE="\033[1;34m"
|
|
C_PURPLE="\033[1;35m"
|
|
C_CYAN="\033[1;36m"
|
|
C_RESET="$(tput sgr0)"
|
|
fi
|
|
|
|
C_OK="$C_GREEN"
|
|
C_LOCKED="$C_RED"
|
|
C_NEEDS_PUSH="$C_YELLOW"
|
|
C_NEEDS_PULL="$C_BLUE"
|
|
C_NEEDS_COMMIT="$C_RED"
|
|
C_NEEDS_UPSTREAM="$C_PURPLE"
|
|
C_UNTRACKED="$C_CYAN"
|
|
C_STASHES="$C_YELLOW"
|
|
|
|
DEBUG=0
|
|
|
|
usage() {
|
|
cat << EOF >&2
|
|
|
|
Usage: $0 [-w] [-e] [-f] [--no-X] [DIR] [DEPTH=2]
|
|
|
|
Scan for .git dirs under DIR (up to DEPTH dirs deep) and show git status
|
|
|
|
-w Warn about dirs that are not Git repositories
|
|
-e Exclude repos that are 'ok'
|
|
-f Do a 'git fetch' on each repo (slow for many repos)
|
|
|
|
You can limit output with the following options:
|
|
|
|
--no-push
|
|
--no-pull
|
|
--no-upstream
|
|
--no-uncommitted
|
|
--no-untracked
|
|
--no-stashes
|
|
|
|
EOF
|
|
}
|
|
|
|
# Handle commandline options
|
|
WARN_NOT_REPO=0
|
|
EXCLUDE_OK=0
|
|
DO_FETCH=0
|
|
NO_PUSH=0
|
|
NO_PULL=0
|
|
NO_UPSTREAM=0
|
|
NO_UNCOMMITTED=0
|
|
NO_UNTRACKED=0
|
|
NO_STASHES=0
|
|
|
|
while [ \! -z "$1" ]; do
|
|
# Stop reading when we've run out of options.
|
|
[ "$(echo "$1" | cut -c 1)" != "-" ] && break
|
|
|
|
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
if [ "$1" = "-w" ]; then
|
|
WARN_NOT_REPO=1
|
|
fi
|
|
if [ "$1" = "-e" ]; then
|
|
EXCLUDE_OK=1
|
|
fi
|
|
if [ "$1" = "-f" ]; then
|
|
DO_FETCH=1
|
|
fi
|
|
if [ "$1" = "--no-push" ]; then
|
|
NO_PUSH=1
|
|
fi
|
|
if [ "$1" = "--no-pull" ]; then
|
|
NO_PULL=1
|
|
fi
|
|
if [ "$1" = "--no-upstream" ]; then
|
|
NO_UPSTREAM=1
|
|
fi
|
|
if [ "$1" = "--no-uncommitted" ]; then
|
|
NO_UNCOMMITTED=1
|
|
fi
|
|
if [ "$1" = "--no-untracked" ]; then
|
|
NO_UNTRACKED=1
|
|
fi
|
|
if [ "$1" = "--no-stashes" ]; then
|
|
NO_STASHES=1
|
|
fi
|
|
|
|
shift
|
|
done
|
|
|
|
if [ -z "$1" ]; then
|
|
ROOT_DIR="."
|
|
else
|
|
ROOT_DIR="$1"
|
|
fi
|
|
|
|
if [ -z "$2" ]; then
|
|
DEPTH=2
|
|
else
|
|
DEPTH="$2"
|
|
fi
|
|
|
|
# Find all .git dirs, up to DEPTH levels deep
|
|
find -L "$ROOT_DIR" -maxdepth "$DEPTH" -type d | while read -r PROJ_DIR
|
|
do
|
|
GIT_DIR="$PROJ_DIR/.git"
|
|
|
|
# If this dir is not a repo, and WARN_NOT_REPO is 1, tell the user.
|
|
if [ \! -d "$GIT_DIR" ]; then
|
|
if [ "$WARN_NOT_REPO" -eq 1 ] && [ "$PROJ_DIR" != "." ]; then
|
|
printf "${PROJ_DIR}: not a git repo\n"
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
[ $DEBUG -eq 1 ] && echo "${PROJ_DIR}"
|
|
|
|
# Check if repo is locked
|
|
if [ -f "$GIT_DIR/index.lock" ]; then
|
|
printf "${PROJ_DIR}: ${C_LOCKED}Locked. Skipping.${C_RESET}\n"
|
|
continue
|
|
fi
|
|
|
|
# Do a 'git fetch' if requested
|
|
if [ "$DO_FETCH" -eq 1 ]; then
|
|
git --work-tree "$(dirname "$GIT_DIR")" --git-dir "$GIT_DIR" fetch -q > /dev/null
|
|
fi
|
|
|
|
# Refresh the index, or we might get wrong results.
|
|
git --work-tree "$(dirname "$GIT_DIR")" --git-dir "$GIT_DIR" update-index -q --refresh > /dev/null 2>&1
|
|
|
|
# Find all remote branches that have been checked out and figure out if
|
|
# they need a push or pull. We do this with various tests and put the name
|
|
# of the branches in NEEDS_XXXX, seperated by newlines. After we're done,
|
|
# we remove duplicates from NEEDS_XXX.
|
|
NEEDS_PUSH_BRANCHES=""
|
|
NEEDS_PULL_BRANCHES=""
|
|
NEEDS_UPSTREAM_BRANCHES=""
|
|
|
|
for REF_HEAD in $(cd "$GIT_DIR/refs/heads" && find . -type 'f' | sed "s/^\.\///"); do
|
|
# Check if this branch is tracking an upstream (local/remote branch)
|
|
UPSTREAM=$(git --git-dir "$GIT_DIR" rev-parse --abbrev-ref --symbolic-full-name "$REF_HEAD@{u}" 2> /dev/null)
|
|
EXIT_CODE="$?"
|
|
if [ "$EXIT_CODE" -eq 0 ]; then
|
|
# Branch is tracking a remote branch. Find out how much behind /
|
|
# ahead it is of that remote branch.
|
|
CNT_AHEAD_BEHIND=$(git --git-dir "$GIT_DIR" rev-list --left-right --count "$REF_HEAD...$UPSTREAM")
|
|
CNT_AHEAD=$(echo "$CNT_AHEAD_BEHIND" | awk '{ print $1 }')
|
|
CNT_BEHIND=$(echo "$CNT_AHEAD_BEHIND" | awk '{ print $2 }')
|
|
|
|
[ $DEBUG -eq 1 ] && echo "CNT_AHEAD_BEHIND: $CNT_AHEAD_BEHIND"
|
|
[ $DEBUG -eq 1 ] && echo "CNT_AHEAD: $CNT_AHEAD"
|
|
[ $DEBUG -eq 1 ] && echo "CNT_BEHIND: $CNT_BEHIND"
|
|
|
|
if [ "$CNT_AHEAD" -gt 0 ]; then
|
|
NEEDS_PUSH_BRANCHES="${NEEDS_PUSH_BRANCHES}\n$REF_HEAD"
|
|
fi
|
|
if [ "$CNT_BEHIND" -gt 0 ]; then
|
|
NEEDS_PULL_BRANCHES="${NEEDS_PULL_BRANCHES}\n$REF_HEAD"
|
|
fi
|
|
|
|
# Check if this branch is a branch off another branch. and if it needs
|
|
# to be updated.
|
|
REV_LOCAL=$(git --git-dir "$GIT_DIR" rev-parse --verify "$REF_HEAD" 2> /dev/null)
|
|
REV_REMOTE=$(git --git-dir "$GIT_DIR" rev-parse --verify "$UPSTREAM" 2> /dev/null)
|
|
REV_BASE=$(git --git-dir "$GIT_DIR" merge-base "$REF_HEAD" "$UPSTREAM" 2> /dev/null)
|
|
|
|
[ $DEBUG -eq 1 ] && echo "REV_LOCAL: $REV_LOCAL"
|
|
[ $DEBUG -eq 1 ] && echo "REV_REMOTE: $REV_REMOTE"
|
|
[ $DEBUG -eq 1 ] && echo "REV_BASE: $REV_BASE"
|
|
|
|
if [ "$REV_LOCAL" = "$REV_REMOTE" ]; then
|
|
: # NOOP
|
|
else
|
|
if [ "$REV_LOCAL" = "$REV_BASE" ]; then
|
|
NEEDS_PULL_BRANCHES="${NEEDS_PULL_BRANCHES}\n$REF_HEAD"
|
|
fi
|
|
if [ "$REV_REMOTE" = "$REV_BASE" ]; then
|
|
NEEDS_PUSH_BRANCHES="${NEEDS_PUSH_BRANCHES}\n$REF_HEAD"
|
|
fi
|
|
fi
|
|
else
|
|
# Branch does not have an upstream (local/remote branch).
|
|
NEEDS_UPSTREAM_BRANCHES="${NEEDS_UPSTREAM_BRANCHES}\n$REF_HEAD"
|
|
fi
|
|
|
|
done
|
|
|
|
# Remove duplicates from NEEDS_XXXX and make comma-seperated
|
|
NEEDS_PUSH_BRANCHES=$(printf "$NEEDS_PUSH_BRANCHES" | sort | uniq | tr '\n' ',' | sed "s/^,\(.*\),$/\1/")
|
|
NEEDS_PULL_BRANCHES=$(printf "$NEEDS_PULL_BRANCHES" | sort | uniq | tr '\n' ',' | sed "s/^,\(.*\),$/\1/")
|
|
NEEDS_UPSTREAM_BRANCHES=$(printf "$NEEDS_UPSTREAM_BRANCHES" | sort | uniq | tr '\n' ',' | sed "s/^,\(.*\),$/\1/")
|
|
|
|
# Find out if there are unstaged, uncommitted or untracked changes
|
|
UNSTAGED=$(git --work-tree "$(dirname "$GIT_DIR")" --git-dir "$GIT_DIR" diff-index --quiet HEAD -- 2> /dev/null;
|
|
echo $?)
|
|
UNCOMMITTED=$(git --work-tree "$(dirname "$GIT_DIR")" --git-dir "$GIT_DIR" diff-files --quiet --ignore-submodules --;
|
|
echo $?)
|
|
UNTRACKED=$(git --work-tree "$(dirname "$GIT_DIR")" --git-dir "$GIT_DIR" ls-files --exclude-standard --others)
|
|
cd "$(dirname "$GIT_DIR")" || exit
|
|
STASHES=$(git stash list | wc -l)
|
|
cd "$OLDPWD" || exit
|
|
|
|
# Build up the status string
|
|
IS_OK=0 # 0 = Repo needs something, 1 = Repo needs nothing ('ok')
|
|
STATUS_NEEDS=""
|
|
if [ \! -z "$NEEDS_PUSH_BRANCHES" ] && [ "$NO_PUSH" -eq 0 ]; then
|
|
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_PUSH}Needs push ($NEEDS_PUSH_BRANCHES)${C_RESET} "
|
|
fi
|
|
if [ \! -z "$NEEDS_PULL_BRANCHES" ] && [ "$NO_PULL" -eq 0 ]; then
|
|
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_PULL}Needs pull ($NEEDS_PULL_BRANCHES)${C_RESET} "
|
|
fi
|
|
if [ \! -z "$NEEDS_UPSTREAM_BRANCHES" ] && [ "$NO_UPSTREAM" -eq 0 ]; then
|
|
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_UPSTREAM}Needs upstream ($NEEDS_UPSTREAM_BRANCHES)${C_RESET} "
|
|
fi
|
|
if [ "$UNSTAGED" -ne 0 ] || [ "$UNCOMMITTED" -ne 0 ] && [ "$NO_UNCOMMITTED" -eq 0 ]; then
|
|
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_COMMIT}Uncommitted changes${C_RESET} "
|
|
fi
|
|
if [ "$UNTRACKED" != "" ] && [ "$NO_UNTRACKED" -eq 0 ]; then
|
|
STATUS_NEEDS="${STATUS_NEEDS}${C_UNTRACKED}Untracked files${C_RESET} "
|
|
fi
|
|
if [ "$STASHES" -ne 0 ] && [ "$NO_STASHES" -eq 0 ]; then
|
|
STATUS_NEEDS="${STATUS_NEEDS}${C_STASHES}$STASHES stashes${C_RESET} "
|
|
fi
|
|
if [ "$STATUS_NEEDS" = "" ]; then
|
|
IS_OK=1
|
|
STATUS_NEEDS="${STATUS_NEEDS}${C_OK}ok${C_RESET} "
|
|
fi
|
|
|
|
# Print the output, unless repo is 'ok' and -e was specified
|
|
if [ "$IS_OK" -ne 1 ] || [ "$EXCLUDE_OK" -ne 1 ]; then
|
|
printf "${PROJ_DIR}: $STATUS_NEEDS\n"
|
|
fi
|
|
done
|