├── .gitignore
├── screenshot.png
├── LICENSE.txt
├── README.md
└── git-summary
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruricolist/git-summary/master/screenshot.png
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2018 @ Mirko Ledda
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # git-summary
2 | **If you ever experienced one of the following situations, git-summary is for you.**
3 |
4 | * I don't remember where some of my repositories are...
5 | * Did I forgot to push that commit?
6 | * Do I have a repo in my system that is outdated?
7 | * Did someone pushed new commits to `origin/master` in one of my repos?
8 | * Did I commit that quick change I made before the pizza delivery guy rang my door?
9 |
10 | git-summary is a bash script that will neatly list the current status of any git repos it founds within a directory or your entire system. See the example screenshot below:
11 |
12 |
13 |
14 | ## Requirements
15 | **Currently supported operating systems:** Linux, MacOS and Cygwin
16 |
17 | ### Linux
18 | * `sudo apt-get install gawk`
19 |
20 | ### MacOS
21 | * `brew install coreutils`
22 |
23 | ## Installation
24 | ### Via aliasing
25 | Clone this repo and alias the script. To do so, add the following line to `~\.bashrc`:
26 |
27 | ```
28 | alias git-summary='/git-summary/git-summary'
29 | ```
30 |
31 | > `` is the path to the cloned repo. Don't worry, if you ever forget where you cloned this repo, you will be able to easily find it with `git-summary` :wink:
32 |
33 | ### Via executable lookup
34 | Copy `git-summary` in `/usr/local/bin`.
35 |
36 | ## Usage
37 | General usage:
38 |
39 | ```
40 | git-summary [options] path
41 | ```
42 |
43 | `path` is optional and the current directory will be used if left blank.
44 |
45 | ### Options
46 | * **-h**: Print help and exit.
47 | * **-l**: Local summary lookup. Checks only local changes which is faster as there is no need to fetch the remote.
48 | * **-d**: Deep lookup. Look for any git repos within the entire current directory tree. Can be slowish for large trees.
49 | * **-q**: Quiet mode. Only print outdated repos.
50 | * **-s**: Sorted output. (Slower as it runs sequentially to avoid race conditions).
51 |
52 | ## Branch status
53 | Currently, `git-summary` does not list multiple branches per repo. However, for single repos [`git-branch-status`](https://github.com/bill-auger/git-branch-status) does this beautifully.
54 |
55 | ## Credits
56 | A big thanks :metal: to the amazing people that contributed to the original versions of `git-summary`:
57 |
58 | * **Forked from** [lordadamson](https://github.com/lordadamson/git-summary)
59 | * [mzabriskie](https://github.com/mzabriskie) (Posted the original idea [here](https://gist.github.com/mzabriskie/6631607))
60 | * [CycleMost](https://github.com/CycleMost)
61 | * [lmj0011](https://github.com/lmj0011)
62 | * [gimbo](https://github.com/gimbo)
63 | * [zartc](https://github.com/zartc)
64 |
65 | Additional thanks go to:
66 | * [ruricolist](https://github.com/ruricolist) - Cygwin support and quiet mode.
67 | * [romainreignier](https://github.com/romainreignier) and [tobiasw1](https://github.com/tobiasw1) - Sorted output.
68 |
--------------------------------------------------------------------------------
/git-summary:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # git-summary - summarize git repos at some path
4 | #
5 | # Forked from https://github.com/lordadamson/git-summary
6 | #
7 | # Freely distributed under the MIT license. 2018@MirkoLedda
8 |
9 | set -eu
10 |
11 | # Colorcode
12 | GREEN='\e[0;32m'
13 | ORANGE='\e[0;33m'
14 | RED='\e[0;31m'
15 | PURPLE='\e[0;35m'
16 | NC='\e[0m' # No Color
17 |
18 | usage() {
19 | sed 's/^ //' <&1 & # parallelized - first come first served stdout
113 | else
114 | summarize_one_git_repo $f "$template" "$local_only" "$quiet" >&1 # sequential - sorted stdout
115 | fi
116 | (( repo_count+=1 ))
117 | done
118 | wait
119 |
120 | if [ $quiet -eq 1 ]; then
121 | echo "Checked ${repo_count} repositories."
122 | fi
123 |
124 | }
125 |
126 | # Autodetect the OS
127 | detect_OS() {
128 | if [ "$(uname)" == "Darwin" ]; then # macOS
129 | OS=Darwin
130 | readlink_cmd="greadlink"
131 | dirname_cmd="gdirname"
132 | gawk_cmd="awk"
133 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then # Linux
134 | OS=Linux
135 | readlink_cmd="readlink"
136 | dirname_cmd="dirname"
137 | gawk_cmd="gawk"
138 | elif [ "$(expr substr $(uname -s) 1 6)" == "CYGWIN" ]; then # Cygwin
139 | OS=CYGWIN
140 | readlink_cmd="readlink"
141 | dirname_cmd="dirname"
142 | gawk_cmd="gawk"
143 | else
144 | echo "Cannot identify OS."
145 | exit 1
146 | fi
147 | }
148 |
149 | GIT4WINDOWS=1
150 |
151 | detect_Git4Windows() {
152 | if [[ "$OS" == "CYGWIN" && "$(git --version)" == *"windows"* ]]; then
153 | GIT4WINDOWS=0
154 | fi
155 | }
156 |
157 | gitC() {
158 | local ldir=$1; shift;
159 | if [ $GIT4WINDOWS -eq 0 ]; then
160 | git -C "$(cygpath -w $ldir)" "$@"
161 | else
162 | git -C "$ldir" "$@"
163 | fi
164 | }
165 |
166 |
167 | print_header () {
168 | local template="$1"
169 | local max_repo_len=$2
170 | local max_branch_len=$3
171 | print_divider () {
172 | printf '=%.0s' $(seq 1 $max_repo_len)
173 | printf ' '
174 | printf '=%.0s' $(seq 1 $max_branch_len)
175 | printf ' '
176 | printf '=%.0s' $(seq 1 5)
177 | printf '\n'
178 | };
179 |
180 | echo
181 | printf "$template\n" $NC Repository Branch State
182 | print_divider
183 | }
184 |
185 |
186 | summarize_one_git_repo () {
187 |
188 | local f=$1
189 | local template=$2
190 | local local_only=$3
191 | local quiet=$4
192 |
193 | local app_name=$f
194 | local branch_name=`gitC $f symbolic-ref HEAD | sed -e "s/^refs\/heads\///"`
195 | local numState=0
196 |
197 | ### Check remote state
198 | local rstate=""
199 | local has_upstream=`gitC $f rev-parse --abbrev-ref @{u} 2> /dev/null | wc -l`
200 | if [ $has_upstream -ne 0 ] ; then
201 | if [ $local_only -eq 0 ] ; then
202 | gitC $f fetch -q &> /dev/null
203 | fi
204 | # Unpulled and unpushed on *current* branch
205 | local unpulled=`gitC $f log --pretty=format:'%h' ..@{u} | wc -c`
206 | local unpushed=`gitC $f log --pretty=format:'%h' @{u}.. | wc -c`
207 |
208 | if [ $unpulled -ne 0 ]; then
209 | rstate="${rstate}v"
210 | numState=1
211 | else
212 | rstate="${rstate} "
213 | fi
214 |
215 | if [ $unpushed -ne 0 ]; then
216 | rstate="${rstate}^"
217 | numState=1
218 | else
219 | rstate="${rstate} "
220 | fi
221 |
222 | else
223 | rstate="--"
224 | fi
225 |
226 | ### Check local state
227 | local state=""
228 | local untracked=`LC_ALL=C gitC $f status | grep Untracked -c`
229 | local new_files=`LC_ALL=C gitC $f status | grep "new file" -c`
230 | local modified=`LC_ALL=C gitC $f status | grep modified -c`
231 |
232 | if [ $untracked -ne 0 ]; then
233 | state="${state}?"
234 | numState=2
235 | else
236 | state="${state} "
237 | fi
238 |
239 | if [ $new_files -ne 0 ]; then
240 | state="${state}+"
241 | numState=2
242 | else
243 | state="${state} "
244 | fi
245 |
246 | if [ $modified -ne 0 ]; then
247 | state="${state}M"
248 | numState=2
249 | else
250 | state="${state} "
251 | fi
252 |
253 | ### Print to stdout
254 | if [ $numState -eq 0 ]; then
255 | if [ $quiet -eq 0 ]; then
256 | printf "$template\n" $GREEN $app_name $branch_name "$state$rstate" >&1
257 | fi
258 | elif [ $numState -eq 1 ]; then
259 | printf "$template\n" $ORANGE $app_name $branch_name "$state$rstate" >&1
260 | elif [ $numState -eq 2 ]; then
261 | printf "$template\n" $RED $app_name $branch_name "$state$rstate" >&1
262 | fi
263 | }
264 |
265 |
266 | # Given the path to a git repo, compute its current branch name.
267 | repo_branch () {
268 | gitC "$1" symbolic-ref HEAD | sed -e "s/^refs\/heads\///"
269 | }
270 |
271 |
272 | # Given a path to a folder containing some git repos, compute the
273 | # names of the folders which actually do contain git repos.
274 | list_repos () {
275 | # https://stackoverflow.com/questions/23356779/how-can-i-store-find-command-result-as-arrays-in-bash
276 | git_directories=()
277 |
278 | local find_cmd
279 | if [ $deeplookup -eq 0 ]; then
280 | find_cmd="find $1 -maxdepth 2 -type d -name .git -print0"
281 | else
282 | find_cmd="find $1 -type d -name .git -print0"
283 | fi
284 |
285 | while IFS= read -r -d $'\0'; do
286 | git_directories+=("$REPLY")
287 | done < <($find_cmd | sort -z 2>/dev/null)
288 |
289 | for i in ${git_directories[*]}; do
290 | if [[ ! -z $i ]]; then
291 | $dirname_cmd -z $i | xargs -0 -L1
292 | fi
293 | done
294 | }
295 |
296 |
297 | # Given the path to a folder containing git some repos, compute the
298 | # names of the current branches in the repos.
299 | repo_branches () {
300 | local path=$1
301 | local repo
302 | for repo in $(list_repos $path) ; do
303 | echo $(repo_branch $repo)
304 | done
305 | }
306 |
307 |
308 | max_len () {
309 | echo "$1" | $gawk_cmd '{ print length }' | sort -rn | head -1
310 | }
311 |
312 | trap "printf '$NC'" EXIT
313 |
314 | git_summary $@
315 |
316 |
--------------------------------------------------------------------------------