-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgit-status
executable file
·179 lines (150 loc) · 6.44 KB
/
git-status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/bin/sh
# MIT license
C_RED="\033[1;31m"
C_YELLOW="\033[1;33m"
C_BLUE="\033[1;34m"
C_PURPLE="\033[1;35m"
C_CYAN="\033[1;36m"
C_WHITE="\033[1;37m"
C_GREEN="\033[1;32m"
C_RESET="$(tput sgr0)"
C_OK="$C_GREEN"
C_NEEDS_PUSH="$C_YELLOW"
C_NEEDS_PULL="$C_BLUE"
C_NEEDS_COMMIT="$C_RED"
C_NEEDS_UPSTREAM="$C_PURPLE"
C_UNTRACKED="$C_CYAN"
DEBUG=0
# Handle commandline options
if [ "$1" = "--help" ]; then
echo "Usage: $0 [-w] [-e] [DIR] [DEPTH=2]" >&2
echo
echo "Scan for .git dirs under DIR (up to DEPTH dirs deep) and show git status"
echo
echo " -w Warn about dirs that are not Git repositories"
echo " -e Exclude repos that are 'ok'"
exit 1
fi
WARN_NOT_REPO=0
EXCLUDE_OK=0
while [ \! -z "$1" ]; do
# Stop reading when we don't recognise the arguments anymore
[ "$1" != "-w" -a "$1" != "-e" ] && break
if [ "$1" = "-w" ]; then
WARN_NOT_REPO=1
fi
if [ "$1" = "-e" ]; then
EXCLUDE_OK=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
# See if we're currently in a git repo, and include it
CUR_REPO_GITDIR="$(git rev-parse --git-dir 2>/dev/null)" && CUR_REPO="$(dirname $CUR_REPO_GITDIR)"
# Find all .git dirs, up to DEPTH levels deep
for PROJ_DIR in $CUR_REPO $(find -L $ROOT_DIR -maxdepth $DEPTH -type d); do
GIT_DIR="$PROJ_DIR/.git"
# Skip '.' and '..'
[ "$PROJ_DIR" = "." -o "$GIT_DIR" = ".." ] && continue
# 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 ]; then
printf "${PROJ_DIR}: not a git repo\n"
fi
continue
fi
[ $DEBUG -eq 1 ] && echo "${PROJ_DIR}"
# 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)
if [ $? -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 origin/$REF_HEAD 2>/dev/null)
REV_BASE=$(git --git-dir $GIT_DIR merge-base $REF_HEAD origin/$REF_HEAD 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, uncomitted or untracked changes
UNSTAGED=$(git --work-tree $(dirname $GIT_DIR) --git-dir $GIT_DIR diff-index --quiet HEAD --; 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)
# Build up the status string
IS_OK=0 # 0 = Repo needs something, 1 = Repo needs nothing ('ok')
STATUS_NEEDS=""
if [ \! -z "$NEEDS_PUSH_BRANCHES" ]; then
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_PUSH}Needs push ($NEEDS_PUSH_BRANCHES)${C_RESET} "
fi
if [ \! -z "$NEEDS_PULL_BRANCHES" ]; then
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_PULL}Needs pull ($NEEDS_PULL_BRANCHES)${C_RESET} "
fi
if [ \! -z "$NEEDS_UPSTREAM_BRANCHES" ]; then
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_UPSTREAM}Needs upstream ($NEEDS_UPSTREAM_BRANCHES)${C_RESET} "
fi
if [ "$UNSTAGED" -ne 0 -o "$UNCOMMITTED" -ne 0 ]; then
STATUS_NEEDS="${STATUS_NEEDS}${C_NEEDS_COMMIT}Uncommitted changes${C_RESET} "
fi
if [ "$UNTRACKED" != "" ]; then
STATUS_NEEDS="${STATUS_NEEDS}${C_UNTRACKED}Untracked files${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 -o "$EXCLUDE_OK" -ne 1 ]; then
printf "${PROJ_DIR}: $STATUS_NEEDS\n"
fi
done