├── .gitignore
├── LICENSE.txt
├── README.md
├── git-summary
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/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 |
51 | ## Branch status
52 | 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.
53 |
54 | ## Credits
55 | A big thanks :metal: to the amazing people that contributed to the original versions of `git-summary`:
56 |
57 | * **Forked from** [lordadamson](https://github.com/lordadamson/git-summary)
58 | * [mzabriskie](https://github.com/mzabriskie) (Posted the original idea [here](https://gist.github.com/mzabriskie/6631607))
59 | * [CycleMost](https://github.com/CycleMost)
60 | * [lmj0011](https://github.com/lmj0011)
61 | * [gimbo](https://github.com/gimbo)
62 | * [zartc](https://github.com/zartc)
63 |
64 | Additional thanks go to:
65 | * [ruricolist](https://github.com/ruricolist) - Cygwin support and quiet mode.
66 |
--------------------------------------------------------------------------------
/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 &
108 | (( repo_count+=1 ))
109 | done
110 | wait
111 |
112 | if [ $quiet -eq 1 ]; then
113 | echo "Checked ${repo_count} repositories."
114 | fi
115 |
116 | }
117 |
118 | # Autodetect the OS
119 | detect_OS() {
120 | if [ "$(uname)" == "Darwin" ]; then # macOS
121 | OS=Darwin
122 | readlink_cmd="greadlink"
123 | dirname_cmd="gdirname"
124 | gawk_cmd="awk"
125 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then # Linux
126 | OS=Linux
127 | readlink_cmd="readlink"
128 | dirname_cmd="dirname"
129 | gawk_cmd="gawk"
130 | elif [ "$(expr substr $(uname -s) 1 6)" == "CYGWIN" ]; then # Cygwin
131 | OS=CYGWIN
132 | readlink_cmd="readlink"
133 | dirname_cmd="dirname"
134 | gawk_cmd="gawk"
135 | else
136 | echo "Cannot identify OS."
137 | exit 1
138 | fi
139 | }
140 |
141 | GIT4WINDOWS=1
142 |
143 | detect_Git4Windows() {
144 | if [[ "$OS" == "CYGWIN" && "$(git --version)" == *"windows"* ]]; then
145 | GIT4WINDOWS=0
146 | fi
147 | }
148 |
149 | gitC() {
150 | local ldir=$1; shift;
151 | if [ $GIT4WINDOWS -eq 0 ]; then
152 | git -C "$(cygpath -w $ldir)" "$@"
153 | else
154 | git -C "$ldir" "$@"
155 | fi
156 | }
157 |
158 |
159 | print_header () {
160 | local template="$1"
161 | local max_repo_len=$2
162 | local max_branch_len=$3
163 | print_divider () {
164 | printf '=%.0s' $(seq 1 $max_repo_len)
165 | printf ' '
166 | printf '=%.0s' $(seq 1 $max_branch_len)
167 | printf ' '
168 | printf '=%.0s' $(seq 1 5)
169 | printf '\n'
170 | };
171 |
172 | echo
173 | printf "$template\n" $NC Repository Branch State
174 | print_divider
175 | }
176 |
177 |
178 | summarize_one_git_repo () {
179 |
180 | local f=$1
181 | local template=$2
182 | local local_only=$3
183 | local quiet=$4
184 |
185 | local app_name=$f
186 | local branch_name=`gitC $f symbolic-ref HEAD | sed -e "s/^refs\/heads\///"`
187 | local numState=0
188 |
189 | ### Check remote state
190 | local rstate=""
191 | local has_upstream=`gitC $f rev-parse --abbrev-ref @{u} 2> /dev/null | wc -l`
192 | if [ $has_upstream -ne 0 ] ; then
193 | if [ $local_only -eq 0 ] ; then
194 | gitC $f fetch -q &> /dev/null
195 | fi
196 | # Unpulled and unpushed on *current* branch
197 | local unpulled=`gitC $f log --pretty=format:'%h' ..@{u} | wc -c`
198 | local unpushed=`gitC $f log --pretty=format:'%h' @{u}.. | wc -c`
199 |
200 | if [ $unpulled -ne 0 ]; then
201 | rstate="${rstate}v"
202 | numState=1
203 | else
204 | rstate="${rstate} "
205 | fi
206 |
207 | if [ $unpushed -ne 0 ]; then
208 | rstate="${rstate}^"
209 | numState=1
210 | else
211 | rstate="${rstate} "
212 | fi
213 |
214 | else
215 | rstate="--"
216 | fi
217 |
218 | ### Check local state
219 | local state=""
220 | local untracked=`LC_ALL=C gitC $f status | grep Untracked -c`
221 | local new_files=`LC_ALL=C gitC $f status | grep "new file" -c`
222 | local modified=`LC_ALL=C gitC $f status | grep modified -c`
223 |
224 | if [ $untracked -ne 0 ]; then
225 | state="${state}?"
226 | numState=2
227 | else
228 | state="${state} "
229 | fi
230 |
231 | if [ $new_files -ne 0 ]; then
232 | state="${state}+"
233 | numState=2
234 | else
235 | state="${state} "
236 | fi
237 |
238 | if [ $modified -ne 0 ]; then
239 | state="${state}M"
240 | numState=2
241 | else
242 | state="${state} "
243 | fi
244 |
245 | ### Print to stdout
246 | if [ $numState -eq 0 ]; then
247 | if [ $quiet -eq 0 ]; then
248 | printf "$template\n" $GREEN $app_name $branch_name "$state$rstate" >&1
249 | fi
250 | elif [ $numState -eq 1 ]; then
251 | printf "$template\n" $ORANGE $app_name $branch_name "$state$rstate" >&1
252 | elif [ $numState -eq 2 ]; then
253 | printf "$template\n" $RED $app_name $branch_name "$state$rstate" >&1
254 | fi
255 | }
256 |
257 |
258 | # Given the path to a git repo, compute its current branch name.
259 | repo_branch () {
260 | gitC "$1" symbolic-ref HEAD | sed -e "s/^refs\/heads\///"
261 | }
262 |
263 |
264 | # Given a path to a folder containing some git repos, compute the
265 | # names of the folders which actually do contain git repos.
266 | list_repos () {
267 | # https://stackoverflow.com/questions/23356779/how-can-i-store-find-command-result-as-arrays-in-bash
268 | git_directories=()
269 |
270 | local find_cmd
271 | if [ $deeplookup -eq 0 ]; then
272 | find_cmd="find $1 -maxdepth 2 -type d -name .git -print0"
273 | else
274 | find_cmd="find $1 -type d -name .git -print0"
275 | fi
276 |
277 | while IFS= read -r -d $'\0'; do
278 | git_directories+=("$REPLY")
279 | done < <($find_cmd 2>/dev/null)
280 |
281 | for i in ${git_directories[*]}; do
282 | if [[ ! -z $i ]]; then
283 | $dirname_cmd -z $i | xargs -0 -L1
284 | fi
285 | done
286 | }
287 |
288 |
289 | # Given the path to a folder containing git some repos, compute the
290 | # names of the current branches in the repos.
291 | repo_branches () {
292 | local path=$1
293 | local repo
294 | for repo in $(list_repos $path) ; do
295 | echo $(repo_branch $repo)
296 | done
297 | }
298 |
299 |
300 | max_len () {
301 | echo "$1" | $gawk_cmd '{ print length }' | sort -rn | head -1
302 | }
303 |
304 | trap "printf '$NC'" EXIT
305 |
306 | git_summary $@
307 |
308 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/albenik/git-summary/f882885a68e5bac64b9fd126735442f3de50c180/screenshot.png
--------------------------------------------------------------------------------