-
Notifications
You must be signed in to change notification settings - Fork 1
/
gitell
executable file
·212 lines (196 loc) · 5.16 KB
/
gitell
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2018, deadc0de6
# print some debug
#
# $1: debug info
debug()
{
if [ -n "${debug}" ]; then
echo -e "[DEBUG] ${1}"
fi
}
# retrieve the branches
# that are tracking a remote
#
# $1: repository path
# stores the list in ${branches}
get_branches()
{
debug "getting branches for \"${1}\""
branches=""
branches=$(git -C "${1}" for-each-ref "${git_head_path}" --format='%(refname:short)' | xargs)
curbranch=$(git -C "${1}" symbolic-ref -q HEAD)
curbranch=${curbranch#refs/heads/}
}
# format git-status XYs
#
# $1: repository path
# $2: branch
# $3: current branch
# stores the string in ${status}
get_status()
{
debug "getting status for branch \"${2}\" in \"${1}\""
nok=""
status="${st_clean}"
if [ "${2}" = "${3}" ]; then
# get more info if it's the current branch
changes=$(git -C "${1}" status --short | awk '{print $1}' | sort | uniq | xargs)
for i in ${changes}; do
if [ "$i" = "??" ]; then
nok="${nok}${st_untracked}${sep}"
elif [ "$i" != "" ]; then
nok="${nok}${st_modified}${sep}"
fi
done
# check if need push
hasremote=$(git -C "${1}" branch -vv 2>/dev/null | grep -c "^..${2}.*\\[origin/.*\\].*$")
debug "branch \"${2}\" - nb remote(s): ${hasremote}"
if [ "${hasremote}" -gt 0 ]; then
changes=$(git -C "${1}" cherry -v | wc -l)
[ "${changes}" -gt "0" ] && nok="${nok}${st_push}${sep}"
[ -n "${printremote}" ] && remote="$(git -C "${1}" config remote.origin.url) "
fi
fi
branch_status "${1}" "${2}"
[ "${insync}" = false ] && nok="${nok}${st_sync}${sep}"
# strip space
nok=${nok%%"${sep}"}
[ "${nok}" != "" ] && status="${st_dirty} (${nok})"
}
# parse a branch status from origin
#
# $1: repository
# $2: branch
# stores true|false in insync
branch_status()
{
debug "getting branch status \"${2}\" in \"${1}\""
insync=true
loc=${2}
rem=$(git -C "${1}" config --get branch."${loc}".merge)
cnts=$(git -C "${1}" rev-list --left-right --pretty=oneline --count "${loc}"..."${rem}")
ahead=$(echo "${cnts}" | awk '{print $1}')
behind=$(echo "${cnts}" | awk '{print $2}')
hasremote=$(git -C "${1}" branch -vv 2>/dev/null | grep -c "^..${2}.*\\[origin/.*\\].*$")
[ "${ahead}" -gt 0 ] && insync=false
[ "${behind}" -gt 0 ] && insync=false
[ "${hasremote}" -lt 1 ] && insync=false
}
# output the result for a specific repo
# on a specific branch
#
# $1: remote
# $2: repo
# $3: branch
# $4: status
# $5: current branch
output()
{
br="${c_gray}${3}${c_reset}"
# if current branch, in white
[ "${3}" = "${5}" ] && br="${3}"
echo -e "${1}${c_blue}${2}${c_reset}/${br} ${4}"
}
# colors
#c_reset="\\e[0m"
c_reset="$(tput sgr0)"
#c_blue="\\e[34m"
c_blue="$(tput setaf 4)"
#c_yellow="\\e[33m"
c_yellow="$(tput setaf 3)"
#c_green="\\e[32m"
c_green="$(tput setaf 2)"
#c_red="\\e[31m"
c_red="$(tput setaf 1)"
#c_magenta="\\e[35m"
c_magenta="$(tput setaf 5)"
#c_gray="\\e[37m"
c_gray="$(tput setaf 7)"
# status
t_clean="clean"
t_dirty="dirty"
# status info
t_untracked="untracked files"
t_modified="modified files"
t_sync="not in sync"
t_push="need push"
# global vars
version="0.6.1"
gitsub=".git"
paths="."
depth="4"
git_head_path="refs/heads"
sep=","
# usage
usage="\\n$(basename "${0}") [-sDrv] [-d <depth>] [<path> ...]"
usage="${usage}\\n"
usage="${usage}\\n\\t-d <depth> \\tDepth to search for git directories (default: ${depth})."
usage="${usage}\\n\\t-s\\t\\tUse symbols instead of text."
usage="${usage}\\n\\t-D\\t\\tPrint debug information."
usage="${usage}\\n\\t-r\\t\\tPrint remotes."
usage="${usage}\\n\\t-v\\t\\tPrint version."
usage="${usage}\\n\\t-h\\t\\tPrint usage."
# parse arguments
args="f:d:sDrvh"
while getopts ${args} arg; do
case ${arg} in
d)
depth="${OPTARG}"
;;
s)
t_clean="[✔]"
t_dirty="[✘]"
t_untracked="*"
t_modified="+"
t_sync="⌁"
t_push="⇡"
;;
D)
debug="true"
;;
r)
printremote="true"
;;
v)
echo "$(basename "${0}") version ${version}"
echo "https://github.com/deadc0de6/gitell"
exit 0
;;
h)
echo -e "${usage}" && exit 0
;;
*)
echo -e "${usage}" && exit 1
;;
esac
done
shift "$((OPTIND - 1))"
[ "$#" -gt 0 ] && [ "$*" != "" ] && paths="$*"
# status
st_clean="${c_green}${t_clean}${c_reset}"
st_dirty="${c_red}${t_dirty}${c_reset}"
st_untracked="${c_yellow}${t_untracked}${c_reset}"
st_modified="${c_yellow}${t_modified}${c_reset}"
st_sync="${c_magenta}${t_sync}${c_reset}"
st_push="${c_magenta}${t_push}${c_reset}"
# loop through all paths
for path in ${paths}; do
if [ ! -e "${path}" ]; then
echo "[WARNING] \"${path}\" does not exist"
continue
fi
# loop through all git repositories
find -L "${path}" -maxdepth "${depth}" -name "${gitsub}" -type d | while read -r g; do
p=$(dirname "${g}")
# make sure we are in a git tree
git -C "${p}" rev-parse --is-inside-work-tree >/dev/null || continue
get_branches "${p}"
# loop through all branches
for b in ${branches}; do
get_status "${p}" "${b}" "${curbranch}"
output "${remote}" "${p}" "${b}" "${status}" "${curbranch}"
done
done
done